Я хочу протестировать абстрактный класс. Конечно, я могу вручную написать макет, унаследованный от класса.
Могу ли я сделать это с помощью фреймворка для фиксации (я использую Mockito) вместо того, чтобы создавать свой макет вручную? Как?
Я хочу протестировать абстрактный класс. Конечно, я могу вручную написать макет, унаследованный от класса.
Могу ли я сделать это с помощью фреймворка для фиксации (я использую Mockito) вместо того, чтобы создавать свой макет вручную? Как?
Следующее предложение позволяет вам тестировать абстрактные классы без создания «настоящего» подкласса - Mock является подклассом.
используйте Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS)
, а затем имитируйте любые вызываемые абстрактные методы.
Пример:
public abstract class My {
public Result methodUnderTest() { ... }
protected abstract void methodIDontCareAbout();
}
public class MyTest {
@Test
public void shouldFailOnNullIdentifiers() {
My my = Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS);
Assert.assertSomething(my.methodUnderTest());
}
}
Примечание: прелесть этого решения в том, что у вас нет необходимости для реализации абстрактных методов, если они никогда не вызываются.
По моему честному мнению, это более аккуратно, чем использование шпиона, поскольку шпиону требуется экземпляр, а это означает, что вы должны создать экземпляр подкласса вашего абстрактного класса.
Если вам просто нужно протестировать некоторые из конкретных методов, не касаясь каких-либо рефератов, вы можете использовать CALLS_REAL_METHODS
(см. Ответ Мортена), но если конкретный тестируемый метод вызывает некоторые абстракции или нереализованные методы интерфейса, это не сработает - Mockito будет жаловаться:" Невозможно вызвать реальный метод на java-интерфейс ".
(Да, это паршивый дизайн, но некоторые фреймворки, например Tapestry 4, как бы навязывают его вам.)
Обходной путь состоит в том, чтобы отменить этот подход - использовать обычное фиктивное поведение (т. Е. Все имитация / заглушка) и использовать doCallRealMethod()
для явного вызова конкретного тестируемого метода. Например.
public abstract class MyClass {
@SomeDependencyInjectionOrSomething
public abstract MyDependency getDependency();
public void myMethod() {
MyDependency dep = getDependency();
dep.doSomething();
}
}
public class MyClassTest {
@Test
public void myMethodDoesSomethingWithDependency() {
MyDependency theDependency = mock(MyDependency.class);
MyClass myInstance = mock(MyClass.class);
// can't do this with CALLS_REAL_METHODS
when(myInstance.getDependency()).thenReturn(theDependency);
doCallRealMethod().when(myInstance).myMethod();
myInstance.myMethod();
verify(theDependency, times(1)).doSomething();
}
}
Обновлено, чтобы добавить:
Для непустых методов вам необходимо использовать thenCallRealMethod()
, например:
when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod();
В противном случае Mockito сообщит: «Обнаружены незавершенные заглушки».
Вы можете добиться этого с помощью шпиона (хотя используйте последнюю версию Mockito 1.8+).
public abstract class MyAbstract {
public String concrete() {
return abstractMethod();
}
public abstract String abstractMethod();
}
public class MyAbstractImpl extends MyAbstract {
public String abstractMethod() {
return null;
}
}
// your test code below
MyAbstractImpl abstractImpl = spy(new MyAbstractImpl());
doReturn("Blah").when(abstractImpl).abstractMethod();
assertTrue("Blah".equals(abstractImpl.concrete()));
Фреймворки имитации призваны упростить имитацию зависимостей тестируемого класса. Когда вы используете фреймворк для имитации класса, большинство фреймворков динамически создают подкласс и заменяют реализацию метода кодом для обнаружения вызова метода и возврата фальшивого значения.
При тестировании абстрактного класса вы хотите выполнить неабстрактные методы тестируемого объекта (SUT), поэтому фреймворк имитирования - это не то, что вам нужно.
Отчасти путаница заключается в том, что в ответе на вопрос, с которым вы связались, говорилось, что нужно вручную создать макет, который унаследован от вашего абстрактного класса. Я бы не назвал такой урок издевательством. Мок - это класс, который используется в качестве замены зависимости, запрограммирован с учетом ожиданий и может быть запрошен, чтобы увидеть, оправдаются ли эти ожидания.
Вместо этого я предлагаю определить в вашем тесте не абстрактный подкласс вашего абстрактного класса. Если это приводит к слишком большому количеству кода, это может быть признаком того, что ваш класс трудно расширить.
Альтернативным решением было бы сделать сам тестовый пример абстрактным с помощью абстрактного метода для создания SUT (другими словами, в тестовом примере использовался бы шаблонный метод).
Попробуйте использовать собственный ответ.
Например:
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
public class CustomAnswer implements Answer<Object> {
public Object answer(InvocationOnMock invocation) throws Throwable {
Answer<Object> answer = null;
if (isAbstract(invocation.getMethod().getModifiers())) {
answer = Mockito.RETURNS_DEFAULTS;
} else {
answer = Mockito.CALLS_REAL_METHODS;
}
return answer.answer(invocation);
}
}
Он вернет макет для абстрактных методов и вызовет реальный метод для конкретных методов.
Что действительно заставляет меня чувствовать себя плохо по поводу имитации абстрактных классов, так это тот факт, что ни конструктор по умолчанию YourAbstractClass()
не вызывается (отсутствует super()
в макете), и, похоже, в Mockito нет никакого способа инициализировать фиктивные свойства по умолчанию (например, свойства List
с пустым ArrayList
или LinkedList
).
Мой абстрактный класс (в основном создается исходный код класса) НЕ предоставляет установку установщика зависимостей для элементов списка или конструктор, в котором он инициализирует элементы списка (которые я пытался добавить вручную).
Только атрибуты класса используют инициализацию по умолчанию:
private List<MyGenType> dep1 = new ArrayList<MyGenType>();
private List<MyGenType> dep2 = new ArrayList<MyGenType>();
Таким образом, НЕТ способа издеваться над абстрактным классом без использования реальной реализации объекта (например, определения внутреннего класса в классе модульного тестирования, переопределения абстрактных методов) и отслеживания реального объекта (который выполняет правильную инициализацию поля).
Жаль, что только PowerMock здесь может помочь.
Предполагая, что ваши тестовые классы находятся в том же пакете (под другим исходным корнем), что и ваши тестируемые классы, вы можете просто создать макет:
YourClass yourObject = mock(YourClass.class);
и вызовите методы, которые вы хотите протестировать, так же, как и любой другой метод.
Вам необходимо предоставить ожидания для каждого метода, который вызывается с ожиданием любых конкретных методов, вызывающих супер-метод - не знаю, как вы это сделаете с Mockito, но я считаю, что это возможно с EasyMock.
Все это создает конкретный экземпляр YouClass
и избавляет вас от необходимости предоставлять пустые реализации каждого абстрактного метода.
Кстати, я часто нахожу полезным реализовать абстрактный класс в моем тесте, где он служит примером реализации, которую я тестирую через его общедоступный интерфейс, хотя это зависит от функциональности, предоставляемой абстрактным классом.
Вы можете расширить абстрактный класс анонимным классом в своем тесте. Например (с использованием Junit 4):
private AbstractClassName classToTest;
@Before
public void preTestSetup()
{
classToTest = new AbstractClassName() { };
}
// Test the AbstractClassName methods.
Mockito позволяет имитировать абстрактные классы с помощью аннотации @Mock
:
public abstract class My {
public abstract boolean myAbstractMethod();
public void myNonAbstractMethod() {
// ...
}
}
@RunWith(MockitoJUnitRunner.class)
public class MyTest {
@Mock(answer = Answers.CALLS_REAL_METHODS)
private My my;
@Test
private void shouldPass() {
BDDMockito.given(my.myAbstractMethod()).willReturn(true);
my.myNonAbstractMethod();
// ...
}
}
Недостатком является то, что его нельзя использовать, если вам нужны параметры конструктора.
Вы можете создать экземпляр анонимного класса, внедрить свои макеты, а затем протестировать этот класс.
@RunWith(MockitoJUnitRunner.class)
public class ClassUnderTest_Test {
private ClassUnderTest classUnderTest;
@Mock
MyDependencyService myDependencyService;
@Before
public void setUp() throws Exception {
this.classUnderTest = getInstance();
}
private ClassUnderTest getInstance() {
return new ClassUnderTest() {
private ClassUnderTest init(
MyDependencyService myDependencyService
) {
this.myDependencyService = myDependencyService;
return this;
}
@Override
protected void myMethodToTest() {
return super.myMethodToTest();
}
}.init(myDependencyService);
}
}
Имейте в виду, что видимость должна быть protected
для свойства myDependencyService
абстрактного класса ClassUnderTest
.
В этом случае может пригодиться Whitebox.invokeMethod(..)
PowerMock.
SomeAbstract spy = spy(SomeAbstract.class);
- person pesche   schedule 30.12.2014mock(MyAbstractClass.class, withSettings().useConstructor(arg1, arg2).defaultAnswer(CALLS_REAL_METHODS))
- person Gediminas Rimsa   schedule 13.07.2018