Как вы тестируете приватные методы?

184

Я работаю над проектом Java. Я новичок в модульном тестировании. Каков наилучший способ модульного тестирования частных методов в классах Java?

Винот Кумар СМ
источник
1
Проверьте этот вопрос на StackOverflow. Несколько методов упоминаются и обсуждаются. Каков наилучший способ модульного тестирования частных методов?
Chiron
34
Я всегда считал, что частные методы не нуждаются в тестировании, поскольку вы должны тестировать то, что доступно. Публичный метод. Если вы не можете сломать публичный метод, действительно ли имеет значение, что делают приватные методы?
Рог
1
Как государственные, так и частные методы должны быть проверены. Следовательно, тестовый драйвер обычно должен находиться внутри класса, который он тестирует. как это .
сверхобмена
2
@Rig - +1 - вы должны иметь возможность вызывать все необходимые действия приватного метода из ваших открытых методов - если вы не можете этого сделать, то функциональность никогда не может быть вызвана так или иначе, поэтому нет смысла тестировать ее.
Джеймс Андерсон
Используйте @Jailbreakиз среды Manifold для прямого доступа к частным методам. Таким образом, ваш тестовый код остается типобезопасным и читаемым. Прежде всего, никаких конструктивных компромиссов, никаких переэкспонированных методов и полей ради тестов.
Скотт

Ответы:

239

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

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

Адам Лир
источник
39
+1 базиллион. И если приватный метод никогда не вызывается, не тестируйте его, удалите!
Стивен А. Лоу
246
Я не согласен. Иногда закрытый метод - это просто деталь реализации, но он все еще достаточно сложен, чтобы его можно было проверить, чтобы убедиться, что он работает правильно. Открытый интерфейс может предлагать слишком высокий уровень абстракции для написания теста, который непосредственно нацелен на этот конкретный алгоритм. Не всегда возможно выделить его в отдельный класс из-за общих данных. В этом случае я бы сказал, что можно тестировать закрытый метод.
quant_dev
10
Конечно, удалите его. Единственная возможная причина не удалять функцию, которую вы не используете, - это если вы думаете, что она может понадобиться в будущем, и даже тогда вам следует удалить ее, но записать эту функцию в журналах фиксации или, по крайней мере, закомментировать ее, чтобы люди знают, что это лишние вещи, которые они могут игнорировать.
Джоккинг
38
@quant_dev: Иногда класс нужно реорганизовать в несколько других классов. Ваши общие данные также могут быть выделены в отдельный класс. Скажем, «контекстный» класс. Тогда ваши два новых класса могут ссылаться на класс контекста для своих общих данных. Это может показаться необоснованным, но если ваши частные методы достаточно сложны, чтобы требовать индивидуального тестирования, это запах кода, который указывает, что ваш граф объектов должен стать немного более детализированным.
Фил
43
Этот ответ НЕ отвечает на вопрос. Вопрос заключается в том, как следует частные методы тестирования, не является ли они должны быть проверены. Вопрос о том, должен ли частный метод подвергаться модульному тестированию, является интересным вопросом и заслуживает обсуждения, но не здесь . Надлежащим ответом является добавление комментария в вопросе о том, что тестирование частных методов может быть не очень хорошей идеей, и предоставление ссылки на отдельный вопрос, который углубился бы в этот вопрос.
TallGuy
118

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

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

Столкнувшись с выбором между инкапсуляцией и тестируемостью, я бы предпочел второе. Более важно иметь правильный код (т. Е. Производить правильный вывод), чем хороший дизайн ООП, который не работает правильно, потому что он не был должным образом протестирован. В Java вы можете просто дать методу доступ по умолчанию и поместить модульный тест в тот же пакет. Модульные тесты - это просто часть разрабатываемого вами пакета, и вполне нормально иметь зависимость между тестами и кодом, который тестируется. Это означает, что когда вы меняете реализацию, вам может потребоваться изменить свои тесты, но это нормально - каждое изменение реализации требует повторного тестирования кода, и если для этого нужно изменить тесты, то вы просто делаете Это.

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

quant_dev
источник
9
+1 Интересный момент. В конце концов, речь идет о ремонтопригодности, а не о соблюдении «правил» хорошего ООП.
Фил
9
+1 Я полностью согласен с тестируемостью над инкапсуляцией.
Ричард
31

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

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

Если это связано с аннотацией @VisibleForTesting в библиотеке Google Guava, вы четко помечаете этот закрытый метод пакета как видимый только для тестирования, и поэтому его не следует вызывать никаким другим классам.

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

Приватный метод перед тестированием:

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

Закрытый метод пакета готов к тестированию:

@VisibleForTesting
int add(int a, int b){
    return a + b;
}

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

Ричард
источник
14

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

MyObject obj = new MyObject();
Method privateMethod = MyObject.class.getDeclaredMethod("getFoo", null);
privateMethod.setAccessible(true);
String returnValue = (String) privateMethod.invoke(obj, null);
System.out.println("returnValue = " + returnValue);

Обратитесь к руководству по Java http://docs.oracle.com/javase/tutorial/reflect/ или API Java http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/package-summary. HTML для получения дополнительной информации.

Как сказал @kij в своем ответе, бывают случаи, когда простое решение с использованием рефлексии действительно хорошо для тестирования частного метода.

Густаво Коэльо
источник
9

Модульный тест - это тестирование кода. Это не означает тестирование интерфейса, потому что, если вы тестируете интерфейс, это не значит, что вы тестируете модуль кода. Это становится своего рода испытанием черного ящика. Кроме того, лучше находить проблемы на уровне самого маленького модуля, чем определять проблемы на уровне интерфейса, а затем пытаться отлаживать то, какая часть не работала. Таким образом, тестовый блок должен быть проверен независимо от их объема. Ниже приведен способ тестирования частных методов.

Если вы используете java, вы можете использовать jmockit, который предоставляет Deencapsulation.invoke для вызова любого частного метода тестируемого класса. Он использует отражение, чтобы вызвать его в конце концов, но обеспечивает хорошую оболочку вокруг него. ( https://code.google.com/p/jmockit/ )

agrawalankur
источник
как это отвечает на заданный вопрос?
комнат
@gnat, вопрос был в том, что является лучшим способом юнит-тестирования приватных методов в классах Java? И мой первый пункт отвечает на вопрос.
agrawalankur
Ваш первый пункт просто рекламирует инструмент, но он не объясняет, почему вы считаете, что он лучше, чем альтернативы, такие как, например, косвенное тестирование частных методов, как было предложено и объяснено в верхнем ответе
gnat
jmockit переместился, и на старой официальной странице появилась статья о «синтетической моче и искусственной мочеиспускании». Кстати, он все еще имеет отношение к насмешкам, но не имеет отношения здесь :) Новая официальная страница: jmockit.github.io
Гийом
8

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

В .NET вы можете преобразовать его в метод «Internal» и сделать пакет « InternalVisible » для вашего проекта модульного тестирования.

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

Благодарю.

Budda
источник
7

Я обычно делаю такие методы защищенными. Допустим, ваш класс находится в:

src/main/java/you/Class.java

Вы можете создать тестовый класс как:

src/test/java/you/TestClass.java

Теперь у вас есть доступ к защищенным методам, и вы можете выполнять их модульное тестирование (JUnit или TestNG на самом деле не имеют значения), но вы сохраняете эти методы от тех вызывающих, которые вам не нужны.

Обратите внимание, что это предполагает исходное дерево в стиле maven.

gyorgyabraham
источник
3

Если вам действительно нужно протестировать закрытый метод, я имею в виду Java, вы можете использовать fest assertи / или fest reflect. Это использует отражение.

Импортируйте библиотеку с помощью maven (я думаю, что указанные версии не самые последние) или импортируйте ее прямо в classpath:

<dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-assert</artifactId>
     <version>1.4</version>
    </dependency>

    <dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-reflect</artifactId>
    <version>1.2</version>
</dependency>

Например, если у вас есть класс с именем «MyClass» с закрытым методом с именем «myPrivateMethod», который принимает в качестве параметра String и обновляет его значение до «Это классное тестирование!», Вы можете выполнить следующий тест junit:

import static org.fest.reflect.core.Reflection.method;
...

MyClass objectToTest;

@Before
public void setUp(){
   objectToTest = new MyClass();
}

@Test
public void testPrivateMethod(){
   // GIVEN
   String myOriginalString = "toto";
   // WHEN
   method("myPrivateMethod").withParameterTypes(String.class).in(objectToTest).invoke(myOriginalString);
   // THEN
   Assert.assertEquals("this is cool testing !", myOriginalString);
}

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

Кий
источник
2
Хотя рефлексия позволит вам тестировать частные методы, она не подходит для обнаружения их использования. Если вы измените имя частного метода, это нарушит ваш тест.
Ричард
1
Тест будет прерван, да, но это небольшая проблема с моей точки зрения. Конечно, я полностью согласен с вашим объяснением ниже о видимости пакета (с тестовыми классами в отдельных папках, но с одинаковыми пакетами), но иногда вы этого не делаете действительно есть выбор. Например, если у вас действительно короткое задание на предприятии, которое не применяет «хорошие» методы (тестовые классы не в одном пакете и т. Д.), Если у вас нет времени на рефакторинг существующего кода, над которым вы работаете / тестирование, это все еще хорошая альтернатива.
Kij
2

Что я обычно делаю в C #, это делаю мои методы защищенными, а не приватными. Это чуть менее закрытый модификатор доступа, но он скрывает метод от всех классов, которые не наследуются от тестируемого класса.

public class classUnderTest
{
   //this would normally be private
   protected void methodToTest()
   {
     //some code here
   }
}

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

class TestingClass : classUnderTest
{
   public void methodToTest()
   {
     //this returns the method i would like to test
     return base.methodToTest();
   }
}

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

астронавт
источник
4
protectedявляется частью общедоступного API и имеет те же ограничения (не может быть изменено, должно быть задокументировано, ...). В C # вы можете использовать internalвместе с InternalsVisibleTo.
CodesInChaos
1

Вы можете легко протестировать закрытые методы, если поместите свои модульные тесты во внутренний класс класса, который вы тестируете. Используя TestNG, ваши модульные тесты должны быть открытыми статическими внутренними классами с аннотацией @Test, например:

@Test
public static class UserEditorTest {
    public void test_private_method() {
       assertEquals(new UserEditor().somePrivateMethod(), "success");
    }
}

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

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

$ mvn test -Dtest=*UserEditorTest

Источник: http://www.ninthavenue.com.au/how-to-unit-test-private-methods

Роджер Кис
источник
4
Вы предлагаете включить тестовый код (и дополнительные внутренние классы) в реальный код развернутого пакета?
1
Тестовый класс является внутренним классом класса, который вы хотите протестировать. Если вы не хотите, чтобы эти внутренние классы были развернуты, вы можете просто удалить файлы * Test.class из вашего пакета.
Роджер Кис
1
Я не люблю этот вариант, но для многих это, вероятно, правильное решение, не стоящее отрицательных голосов.
Билл К
@RogerKeays: Технически, когда вы делаете развертывание, как вы удаляете такие «тестовые внутренние классы»? Пожалуйста, не говорите, что вырезали их вручную.
gyorgyabraham
Я не удаляю их. Да. Я развертываю код, который никогда не выполняется во время выполнения. Это действительно так плохо.
Роджер Кис