Публикация частного метода для unit test это... хорошая идея?

Замечание модератора: Здесь уже отправлено 39 ответов (некоторые из них были удалены). Прежде чем отправлять свой ответ, подумайте над тем, можете ли вы добавить что-то значимое в обсуждение. Вы, скорее всего, просто повторяете то, что уже сказал кто-то еще.


Иногда мне приходится делать приватный метод в классе public, просто чтобы написать для него некоторые модульные тесты.

Обычно это было бы потому, что этот метод содержит логику, разделяемую между другими методами в классе, и она более аккуратная для проверки логики сама по себе, или может быть возможна другая причина: я хочу протестировать логику, используемую в синхронных потоках, не беспокоясь о проблемах с резьбой.

Делают ли другие люди это, потому что мне это не нравится? Я лично считаю, что бонусы перевешивают проблемы создания публичного метода, который на самом деле не предоставляет никаких услуг за пределами класса...

UPDATE

Спасибо за ответы всем, кажется, заинтересовали людей. Я думаю, что общий консенсус в том, что тестирование должно происходить через публичный API, поскольку это единственный способ использования класса, и я согласен с этим. Несколько случаев, о которых я упоминал выше, где я хотел бы сделать это выше, были необычными случаями, и я думал, что польза от этого стоит того.

Тем не менее, я могу видеть, что все это никогда не должно происходить. И когда я думаю об этом немного больше, я думаю, что изменение кода для проведения тестов - плохая идея. В конце концов, я полагаю, что тестирование - это инструмент поддержки и изменение системы для поддержки инструмента поддержки, если вы это сделаете, вопиющее плохая практика.

Ответ 1

Примечание:
Этот ответ был первоначально отправлен на вопрос Является ли единичное тестирование только когда-либо веской причиной, чтобы подвергать частные переменные экземпляра через геттеры?, который был объединен с этим, поэтому он может быть немного специфичным для представленной там информации.

Как правило, я обычно использую рефакторинг "производственного" кода, чтобы упростить тестирование. Однако, я не думаю, что это был бы хороший звонок здесь. Хороший unit test (обычно) не должен заботиться о деталях реализации класса, только о его видимом поведении. Вместо того, чтобы подвергать внутренние стеки тесту, вы можете проверить, что класс возвращает страницы в том порядке, в котором вы ожидаете, после вызова first() или last().

Например, рассмотрим этот псевдокод:

public class NavigationTest {
    private Navigation nav;

    @Before
    public void setUp() {
        // Set up nav so the order is page1->page2->page3 and
        // we've moved back to page2
        nav = ...;
    }

    @Test
    public void testFirst() {
        nav.first();

        assertEquals("page1", nav.getPage());

        nav.next();
        assertEquals("page2", nav.getPage());

        nav.next();
        assertEquals("page3", nav.getPage());
    }

    @Test
    public void testLast() {
        nav.last();

        assertEquals("page3", nav.getPage());

        nav.previous();
        assertEquals("page2", nav.getPage());

        nav.previous();
        assertEquals("page1", nav.getPage());
    }
}

Ответ 2

Лично я предпочел бы unit test использовать общедоступный API, и я бы никогда не стал публичным методом публичного доступа просто для того, чтобы было легко протестировать.

Если вы действительно хотите изолировать частный метод, в Java вы можете использовать Easymock/Powermock, чтобы вы могли это сделать.

Вы должны прагматично относиться к этому, и вы также должны знать о причинах, по которым трудно тестировать.

'Прослушать тесты' - если это сложно проверить, то это говорит вам что-то о вашем дизайне? Не могли бы вы реорганизовать, где тест на этот метод будет тривиальным и легко покрываться тестированием через публичный api?

Здесь, что Майкл Перс должен сказать в Эффективно работая с устаревшим кодом

"Многие люди тратят много времени, пытаясь выяснить, как обойти эту проблему... реальный ответ заключается в том, что, если у вас есть желание проверить частный метод, этот метод не должен быть приватным; метод общественного беспокоит вас, скорее всего, потому, что он является частью отдельной ответственности, он должен быть в другом классе". [Эффективно работает с устаревшим кодом (2005) М. Персе].

Ответ 3

Как говорили другие, несколько подозрительно - это частные методы тестирования частных устройств; unit test открытый интерфейс, а не частные детали реализации.

Тем не менее, метод, который я использую, когда хочу unit test что-то, что является частным в С#, - это понизить защиту доступности от частного до внутреннего, а затем пометить сборку тестирования модуля как сборку друзей, используя InternalsVisibleTo. Затем блоку тестирования модулей будет разрешено рассматривать внутренности как общедоступные, но вам не нужно беспокоиться о случайном добавлении к вашей публичной площади.

Ответ 4

Множество ответов предлагает только тестирование открытого интерфейса, но IMHO это нереально - если метод делает что-то, что занимает 5 шагов, вам нужно протестировать эти пять шагов отдельно, а не все вместе. Это требует тестирования всех пяти методов, которые (кроме тестирования) могли бы быть private.

Обычный способ тестирования методов "private" - дать каждому классу собственный интерфейс и сделать "private" методы public, но не включать их в интерфейс. Таким образом, они все еще могут быть протестированы, но они не раздувают интерфейс.

Да, это приведет к загрузке файлов и классов.

Да, это делает ненужные спецификаторы public и private.

Да, это боль в заднице.

Это, к сожалению, одна из многих жертвоприношений, которые мы делаем для проверки кода. Возможно, будущий язык (или даже будущая версия С#/Java) будет обладать функциями, позволяющими сделать класс и тестируемость более удобными; но тем временем мы должны прыгать через эти обручи.


Некоторые утверждают, что каждый из этих шагов должен быть собственным классом, но я не согласен - если все они разделяют состояние, нет причин создавать пять отдельных классов, в которых будут выполняться пять методов. Хуже того, это приводит к разрыву файлов и классов. Кроме того, он заражает открытый API вашего модуля - все эти классы обязательно должны быть public, если вы хотите протестировать их из другого модуля (либо это, либо включить тестовый код в том же модуле, что означает отправку тестового кода с помощью ваш продукт).

Ответ 5

unit test должен проверить публичный контракт, единственный способ использования класса в других частях кода. Частный метод - это детали реализации, вы не должны его проверять, поскольку открытый API работает правильно, реализация не имеет значения и может быть изменена без изменений в тестовых случаях.

Ответ 6

IMO, вы должны написать свои тесты, не делая глубоких предположений о том, как ваш класс реализован внутри. Вероятно, вы захотите реорганизовать его позже, используя другую внутреннюю модель, но при этом сохраняя те же гарантии, что и предыдущая реализация.

Помня об этом, я предлагаю вам сосредоточиться на тестировании того, что ваш контракт по-прежнему выполняется независимо от внутренней реализации вашего класса в настоящее время. Проверка на основе свойств ваших общедоступных API.

Ответ 7

Как сделать его приватным? Тогда ваш тестовый код может увидеть его (и другие классы в вашем пакете), но он по-прежнему скрыт от ваших пользователей.

Но на самом деле вы не должны тестировать частные методы. Это детали реализации, а не часть контракта. Все, что они делают, должно быть покрыто вызовом публичных методов (если у них есть код, который не используется общественными методами, тогда это должно идти). Если частный код слишком сложный, класс, вероятно, делает слишком много вещей и не хочет рефакторинга.

Создание публичного метода - это большая приверженность. Как только вы это сделаете, люди смогут его использовать, и вы больше не можете их изменять.

Ответ 8

Обновить: Я добавил более расширенный и более полный ответ на этот вопрос во многих других местах. Это можно найти в моем блоге.

Если мне когда-либо понадобится сделать что-то публичное, чтобы проверить его, обычно это означает, что тестируемая система не следует принципу единственной неопровержимости. Следовательно, отсутствует класс, который должен быть введен. После извлечения кода в новый класс сделайте его общедоступным. Теперь вы можете легко протестировать, и вы следуете за SRP. Ваш другой класс просто должен вызвать этот новый класс через композицию.

Использование методов public/использование тэгов langauge, таких как код маркировки, видимый для тестирования сборки, всегда должно быть последним.

Например:

public class SystemUnderTest
{
   public void DoStuff()
   {
      // Blah
      // Call Validate()
   }

   private void Validate()
   {
      // Several lines of complex code...
   }
}

Рефакторируйте это, введя объект валидатора.

public class SystemUnderTest
{
    public void DoStuff()
    {
       // Blah
       validator.Invoke(..)
    }
}

Теперь все, что нам нужно сделать, это проверить, правильно ли вызывается валидатор. Фактический процесс проверки (ранее приватная логика) может быть протестирован в чистой изоляции. Не будет необходимости в сложной тестовой настройке, обеспечивающей ее проверку.

Ответ 9

Некоторые отличные ответы. Одна вещь, о которой я не упоминал, - это то, что при тестировании (TDD) на этапе рефакторинга создаются частные методы (посмотрите Извлечь метод на примере шаблон рефакторинга), и поэтому он должен иметь необходимое покрытие для тестирования. Если все сделано правильно (и, конечно же, вы получите смешанную оценку мнений, когда речь идет о правильности), вам не придется беспокоиться о том, чтобы публиковать приватный метод, чтобы вы могли его протестировать.

Ответ 10

Почему бы не разделить алгоритм управления стеком на класс утилиты? Класс полезности может управлять стеками и предоставлять общедоступные устройства доступа. Его модульные тесты могут быть сфокусированы на деталях реализации. Глубокие тесты для алгоритмически сложных классов очень полезны при сглаживании крайних случаев и обеспечении покрытия.

Затем ваш текущий класс может чисто делегировать класс утилиты, не подвергая детализации какие-либо детали реализации. Его тесты будут относиться к требованию разбивки на страницы, как рекомендовали другие.

Ответ 11

Частные методы обычно используются как "вспомогательные" методы. Поэтому они возвращают только базовые значения и никогда не работают с конкретными экземплярами объектов.

У вас есть пара опций, если вы хотите их протестировать.

  • Использовать отражение
  • Предоставить доступ к пакетам методов

В качестве альтернативы вы можете создать новый класс со вспомогательным методом в качестве общедоступного метода, если он является достаточно хорошим кандидатом для нового класса.

Здесь есть очень хорошая статья здесь.

Ответ 12

В java также есть возможность сделать закрытый пакет (т.е. оставить модификатор видимости). Если ваши модульные тесты находятся в том же пакете, что и тестируемый класс, он должен иметь возможность видеть эти методы и немного безопаснее, чем сделать этот метод полностью открытым.

Ответ 13

Если вы используете С#, вы можете сделать метод внутренним. Таким образом, вы не загрязняете открытый API.

Затем добавьте атрибут в dll

[сборка: InternalsVisibleTo ( "MyTestAssembly" )]

Теперь все методы видны в вашем проекте MyTestAssembly. Может быть, это не идеально, но лучше, чем приватный метод, чтобы проверить его.

Ответ 14

Используйте отражение для доступа к приватным переменным, если вам нужно.

Но на самом деле, вам не важно внутреннее состояние класса, вы просто хотите проверить, что публичные методы возвращают то, что вы ожидаете в ситуациях, которые вы можете ожидать.

Ответ 15

в терминах модульного тестирования, вы определенно не должны добавлять больше методов; Я считаю, что вам лучше сделать тестовый пример только для вашего метода first(), который будет вызываться перед каждым тестом; то вы можете вызывать несколько раз - next(), previous() и last(), чтобы увидеть, соответствуют ли результаты вашим ожиданиям. Я думаю, если вы не добавите больше методов в свой класс (только для целей тестирования), вы будете придерживаться принципа тестирования "черного ящика";

Ответ 16

Я бы сказал, что это плохая идея, потому что я не уверен, получаете ли вы какую-либо выгоду и потенциально проблемы в этой области. Если вы меняете контракт на вызовы, просто для проверки частного метода вы не тестируете класс в том, как он будет использоваться, но создайте искусственный сценарий, который вы никогда не планировали.

Кроме того, объявив метод общедоступным, что сказать, что через шесть месяцев (после того, как вы забыли, что единственная причина для создания метода для тестирования), что вы (или если вы передали проект) кто-то совершенно разные, не будут использовать его, что приведет к потенциально непреднамеренным последствиям и/или кошмару обслуживания.

Ответ 17

Сначала посмотрим, должен ли этот метод быть извлечен в другой класс и опубликован. Если это не так, сделайте его защищенным пакетом и в Java аннотируйте @VisibleForTesting.

Ответ 18

В своем обновлении вы говорите, что хорошо протестировать с помощью общедоступного API. Здесь действительно две школы.

  • Проверка черного ящика

    Школа черного ящика говорит, что класс следует рассматривать как черный ящик, который никто не видит в его реализации. Единственный способ проверить это - через открытый API - так же, как пользователь этого класса будет использовать его.

  • тестирование белого ящика.

    Школа белого ящика естественно использует знания о реализации класса, а затем тестирует класс, чтобы знать, что он работает так, как должен.

Я действительно не могу принять участие в обсуждении. Я просто подумал, что было бы интересно узнать, что есть два разных способа проверить класс (или библиотеку или что-то еще).

Ответ 19

Существуют ситуации, когда вы должны это делать (например, когда вы выполняете некоторые сложные алгоритмы). Просто сделайте это с пакетом-частным, и этого будет достаточно. Но в большинстве случаев, вероятно, у вас слишком сложные классы, которые требуют разложения логики в другие классы.

Ответ 20

Частные методы, которые вы хотите протестировать изолированно, указывают на то, что в вашем классе похож другой "концепт". Извлеките эту "концепцию" в свой класс и протестируйте ее как отдельную "единицу".

Посмотрите это видео для действительно интересного взятия на эту тему.

Ответ 21

Я склонен согласиться с тем, что бонусы за его тестирование единицы перевешивают проблемы повышения видимости некоторых членов. Небольшое улучшение заключается в том, чтобы сделать его защищенным и виртуальным, а затем переопределить его в тестовом классе, чтобы разоблачить его.

В качестве альтернативы, если это функциональность, которую вы хотите протестировать отдельно, она не предполагает отсутствие объекта из вашего проекта? Возможно, вы можете поместить его в отдельный класс, который можно проверить... тогда ваш существующий класс просто делегирует экземпляр этого нового класса.

Ответ 22

Я обычно держу тестовые классы в том же проекте/сборке, что и тестируемые классы.
Таким образом, мне нужна только видимость internal для проверки работоспособности функций/классов.

Это несколько усложняет процесс сборки, который должен отфильтровывать тестовые классы. Я достигаю этого, называя все мои тестовые классы TestedClassTest и используя регулярное выражение для фильтрации этих классов.

Это, конечно, относится только к части С#/.NET вашего вопроса.

Ответ 23

Я часто добавляю метод, называемый как validate, verify, check и т.д., для того, чтобы его можно было вызвать для проверки внутреннего состояния объекта.

Иногда этот метод заверяется в блок ifdef (я пишу в основном на С++), поэтому он не компилируется для выпуска. Но часто полезно в релизе предоставлять методы проверки, которые просматривают деревья объектов программы, проверяя вещи.

Ответ 24

Guava имеет аннотацию @VisibleForTesting для меток маркировки, которые увеличили объем (пакет или публикацию), которые они в противном случае. Я использую аннотацию @Private для того же самого.

Хотя публичный API должен быть протестирован, иногда удобно и разумно получать информацию, которая обычно не была бы общедоступной.

Когда:

  • класс становится значительно менее удобочитаемым, то есть, разбивая его на несколько классов,
  • чтобы сделать его более проверяемым,
  • и предоставление некоторого тестового доступа к внутренним файлам выполнит трюк

Похоже, что религия - это передовая техника.

Ответ 25

Нет, потому что есть лучшие способы скинирования этой кошки.

Некоторые жгуты unit test полагаются на макросы в определении класса, которые автоматически расширяются для создания перехватов при построении в тестовом режиме. Очень C-стиль, но он работает.

Более легкая OO-идиома - сделать все, что вы хотите протестировать "защищенный", а не "private". Испытательный жгут наследуется от тестируемого класса и может затем получить доступ ко всем защищенным членам.

Или вы идете для опции "друг". Лично это особенность С++. Мне нравится меньше всего, потому что она нарушает правила инкапсуляции, но бывает, что С++ реализует некоторые функции, поэтому hey ho.

В любом случае, если вы участвуете в модульном тестировании, вам, скорее всего, придется вводить значения этим членам. Белые текстовые сообщения отлично действуют. И это действительно нарушит вашу инкапсуляцию.

Ответ 26

Вы никогда не должны позволять вашим тестам диктовать свой код. Я не говорю о TDD или других DD, я имею в виду, именно то, что вы просите. Вам нужно, чтобы ваши приложения были общедоступными. Если это произойдет, проверьте их. Если это не так, тогда не делайте их общедоступными только для тестирования. То же самое с переменными и другими. Пусть ваше приложение должно диктовать код, и пусть ваши тесты проверяют, что это необходимо. (Снова я не имею в виду тестирование сначала или нет, я имею в виду изменение структуры классов для достижения цели тестирования).

Вместо этого вы должны "проверить выше". Проверьте метод, который вызывает частный метод. Но ваши тесты должны тестировать ваши приложения, а не ваши "решения по реализации".

Например (псевдо-код bod здесь);

   public int books(int a) {
     return add(a, 2);
   }
   private int add(int a, int b) {
     return a+b;
   } 

Нет причин для проверки "добавить", вы можете протестировать "книги".

Никогда не позволяйте вашим тестам принимать решения по разработке кода для вас. Проверьте, что вы получаете ожидаемый результат, а не как получить этот результат.

Ответ 27

Как подробно отмечается в комментариях других, модульные тесты должны быть сосредоточены на публичном API. Однако, плюсы/минусы и оправдание в стороне, вы можете вызвать частные методы в unit test, используя отражение. Разумеется, вы должны убедиться, что безопасность JRE позволяет это сделать. Вызов частных методов - это то, что использует Spring Framework ReflectionUtils (см. Метод makeAccessible(Method)).

Вот небольшой примерный класс с методом частного экземпляра.

public class A {
    private void doSomething() {
        System.out.println("Doing something private.");
    }
}

И примерный класс, который выполняет метод частного экземпляра.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class B {
    public static final void main(final String[] args) {
        try {
            Method doSomething = A.class.getDeclaredMethod("doSomething");
            A o = new A();
            //o.doSomething(); // Compile-time error!
            doSomething.setAccessible(true); // If this is not done, you get an IllegalAccessException!
            doSomething.invoke(o);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
}

Выполнение B, напечатает Doing something private. Если вам это действительно нужно, в модульных тестах можно использовать отражение для доступа к частным методам экземпляра.

Ответ 28

Я обычно оставляю эти методы как protected и помещаю unit test в один и тот же пакет (но в другой проект или исходную папку), где они могут получить доступ ко всем защищенным методам, потому что загрузчик классов будет помещать их в один и тот же Пространство имен.

Ответ 29

В .NET есть специальный класс PrivateObject, специально предназначенный для доступа к приватным методам класса.

Подробнее об этом см. MSDN или здесь на Переполнение стека

(Мне интересно, что никто не упомянул об этом до сих пор.)

Есть ситуации, которые этого недостаточно, и в этом случае вам придется использовать отражение.

Тем не менее, я бы придерживался общей рекомендации не тестировать частные методы, однако, как обычно, всегда есть исключения.

Ответ 30

Лично у меня такие же проблемы при тестировании частных методов, и это связано с тем, что некоторые из инструментов тестирования ограничены. Это не очень хорошо, что ваш дизайн управляется ограниченными инструментами, если они не реагируют на ваши потребности, а не на инструмент, а не на дизайн. Поскольку ваш запрос на С# я не могу предложить хорошие инструменты тестирования, но для Java существуют два мощных инструмента: TestNG и PowerMock, и вы можете найти соответствующие инструменты тестирования для платформы .NET.