Я работаю над проектом Java. Я новичок в модульном тестировании. Каков наилучший способ модульного тестирования частных методов в классах Java?
Я работаю над проектом Java. Я новичок в модульном тестировании. Каков наилучший способ модульного тестирования частных методов в классах Java?
Ответы:
Обычно вы не тестируете приватные методы напрямую. Поскольку они являются частными, рассмотрим их подробно реализации. Никто никогда не собирается звонить одному из них и ожидать, что он будет работать определенным образом.
Вместо этого вы должны проверить свой публичный интерфейс. Если методы, которые вызывают ваши приватные методы, работают так, как вы ожидаете, то вы, следовательно, предполагаете, что ваши приватные методы работают правильно.
В общем, я бы этого избегал. Если ваш закрытый метод настолько сложен, что требует отдельного модульного теста, это часто означает, что он заслуживает своего собственного класса. Это может побудить вас написать это способом, который можно использовать повторно. Затем вы должны протестировать новый класс и вызвать его открытый интерфейс в вашем старом классе.
С другой стороны, иногда разделение деталей реализации на отдельные классы приводит к классам со сложными интерфейсами, большому количеству данных, передаваемых между старым и новым классами, или к дизайну, который может выглядеть хорошо с точки зрения ООП, но не сопоставить интуиции, поступающие из проблемной области (например, разделение модели ценообразования на две части просто для того, чтобы избежать тестирования частных методов, не очень интуитивно понятно и может впоследствии привести к проблемам при обслуживании / расширении кода). Вы не хотите иметь «классы близнецов», которые всегда меняются вместе.
Столкнувшись с выбором между инкапсуляцией и тестируемостью, я бы предпочел второе. Более важно иметь правильный код (т. Е. Производить правильный вывод), чем хороший дизайн ООП, который не работает правильно, потому что он не был должным образом протестирован. В Java вы можете просто дать методу доступ по умолчанию и поместить модульный тест в тот же пакет. Модульные тесты - это просто часть разрабатываемого вами пакета, и вполне нормально иметь зависимость между тестами и кодом, который тестируется. Это означает, что когда вы меняете реализацию, вам может потребоваться изменить свои тесты, но это нормально - каждое изменение реализации требует повторного тестирования кода, и если для этого нужно изменить тесты, то вы просто делаете Это.
В общем, класс может предлагать более одного интерфейса. Существует интерфейс для пользователей и интерфейс для сопровождающих. Второй может раскрыть больше, чтобы гарантировать, что код адекватно протестирован. Это не должен быть модульный тест для частного метода - это может быть, например, регистрация. Ведение журнала также «нарушает инкапсуляцию», но мы все еще делаем это, потому что это очень полезно.
Тестирование частных методов будет зависеть от их сложности; некоторые однострочные частные методы на самом деле не требуют дополнительных усилий по тестированию (это также можно сказать о публичных методах), но некоторые приватные методы могут быть такими же сложными, как публичные методы, и их трудно тестировать через публичный интерфейс.
Мой предпочтительный метод - сделать приватный пакет методов закрытым, что позволит получить доступ к модульному тесту в том же пакете, но он все равно будет инкапсулирован из всего остального кода. Это даст преимущество непосредственного тестирования логики закрытого метода вместо того, чтобы полагаться на открытый тест метода, чтобы охватить все части (возможно) сложной логики.
Если это связано с аннотацией @VisibleForTesting в библиотеке Google Guava, вы четко помечаете этот закрытый метод пакета как видимый только для тестирования, и поэтому его не следует вызывать никаким другим классам.
Противники этого метода утверждают, что это нарушит инкапсуляцию и откроет приватные методы для кодирования в том же пакете. Хотя я согласен с тем, что это нарушает инкапсуляцию и открывает закрытый код для других классов, я утверждаю, что тестирование сложной логики важнее строгой инкапсуляции, и отказ от использования закрытых методов пакета, которые четко помечены как видимые только для тестирования, должен быть обязанностью разработчиков. использование и изменение кодовой базы.
Приватный метод перед тестированием:
private int add(int a, int b){
return a + b;
}
Закрытый метод пакета готов к тестированию:
@VisibleForTesting
int add(int a, int b){
return a + b;
}
Примечание. Помещение тестов в один и тот же пакет не эквивалентно размещению их в одной и той же физической папке. Разделение вашего основного кода и тестового кода на отдельные структуры физических папок является хорошей практикой в целом, но этот метод будет работать до тех пор, пока классы определены как в одном пакете.
Если вы не можете использовать внешние 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 в своем ответе, бывают случаи, когда простое решение с использованием рефлексии действительно хорошо для тестирования частного метода.
Модульный тест - это тестирование кода. Это не означает тестирование интерфейса, потому что, если вы тестируете интерфейс, это не значит, что вы тестируете модуль кода. Это становится своего рода испытанием черного ящика. Кроме того, лучше находить проблемы на уровне самого маленького модуля, чем определять проблемы на уровне интерфейса, а затем пытаться отлаживать то, какая часть не работала. Таким образом, тестовый блок должен быть проверен независимо от их объема. Ниже приведен способ тестирования частных методов.
Если вы используете java, вы можете использовать jmockit, который предоставляет Deencapsulation.invoke для вызова любого частного метода тестируемого класса. Он использует отражение, чтобы вызвать его в конце концов, но обеспечивает хорошую оболочку вокруг него. ( https://code.google.com/p/jmockit/ )
Прежде всего, как предлагали другие авторы: подумайте дважды, действительно ли вам нужно протестировать закрытый метод. И если так, ...
В .NET вы можете преобразовать его в метод «Internal» и сделать пакет « InternalVisible » для вашего проекта модульного тестирования.
В Java вы можете сами писать тесты в тестируемом классе, и ваши тестовые методы также должны вызывать закрытые методы. У меня нет большого опыта работы с Java, так что это, вероятно, не лучшая практика.
Благодарю.
Я обычно делаю такие методы защищенными. Допустим, ваш класс находится в:
src/main/java/you/Class.java
Вы можете создать тестовый класс как:
src/test/java/you/TestClass.java
Теперь у вас есть доступ к защищенным методам, и вы можете выполнять их модульное тестирование (JUnit или TestNG на самом деле не имеют значения), но вы сохраняете эти методы от тех вызывающих, которые вам не нужны.
Обратите внимание, что это предполагает исходное дерево в стиле maven.
Если вам действительно нужно протестировать закрытый метод, я имею в виду 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 или любым другим фреймворком действительно здорово. Единственное, что вы должны знать в данный момент (не знаю, будет ли это лучше в следующих версиях), это имя целевого поля / метода, которым вы хотите манипулировать, и его сигнатура.
Что я обычно делаю в 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();
}
}
Этот класс существует только в моем тестовом проекте. Его единственная цель - предоставить доступ к этому единственному методу. Это позволяет мне получить доступ к местам, которых нет в большинстве других классов.
protected
является частью общедоступного API и имеет те же ограничения (не может быть изменено, должно быть задокументировано, ...). В C # вы можете использовать internal
вместе с InternalsVisibleTo
.
Вы можете легко протестировать закрытые методы, если поместите свои модульные тесты во внутренний класс класса, который вы тестируете. Используя 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