Использование Mockito для тестирования абстрактных classов

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

Могу ли я сделать это, используя насмешливую структуру (я использую Mockito) вместо того, чтобы рисовать мой макет? Как?

Следующее предложение позволит вам тестировать абстрактные classы без создания «реального» подclassа – Mock является подclassом.

используйте 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()); } } 

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

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

Если вам просто нужно проверить некоторые конкретные методы, не касаясь каких-либо рефератов, вы можете использовать CALLS_REAL_METHODS (см . Ответ Мортена ), но если конкретный метод под тестированием вызывает некоторые абстрактные или нереализованные методы интерфейса, это не будет работа – Mockito будет жаловаться «Невозможно вызвать реальный метод на java-интерфейсе».

(Да, это паршивый дизайн, но некоторые frameworks, например Tapestry 4, навязывают вам силу).

Обходной путь состоит в том, чтобы отменить этот подход – использовать обычное макет поведения (т. doCallRealMethod() Все издевательство / обрезать) и использовать 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(); } } , 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(); } } , 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() методов вам нужно будет использовать thenCallRealMethod() , например:

 when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod(); 

В противном случае Мокито будет жаловаться «Обнаружен незавершенный контроль».

Вы можете добиться этого, используя шпион (хотя использовать последнюю версию 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())); , 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())); 

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

При тестировании абстрактного classа вы хотите выполнить не абстрактные методы Subject Under Test (SUT), так что насмешливая структура не то, что вы хотите.

Часть путаницы заключается в том, что ответ на вопрос, который вы связали с упомянутым, применительно к ручному макету, который простирается от вашего абстрактного classа. Я бы не назвал такой class фальшивым. Макет – это class, который используется как замена для зависимости, запрограммирован с ожиданиями и может быть запрошен, чтобы убедиться, что эти ожидания выполнены.

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

Альтернативным решением было бы сделать ваш тестовый пример абстрактным, с абстрактным методом для создания SUT (другими словами, тестовый пример будет использовать шаблон шаблона метода шаблона).

Попробуйте использовать настраиваемый ответ.

Например:

 import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; public class CustomAnswer implements Answer { public Object answer(InvocationOnMock invocation) throws Throwable { Answer answer = null; if (isAbstract(invocation.getMethod().getModifiers())) { answer = Mockito.RETURNS_DEFAULTS; } else { answer = Mockito.CALLS_REAL_METHODS; } return answer.answer(invocation); } } 

Он вернет макет для абстрактных методов и вызовет реальный метод для конкретных методов.

Что действительно заставляет меня плохо относиться к насмешливым абстрактным classам, так это тот факт, что ни конструктор по умолчанию, который вызывается MyAbstractClass (), не вызывается (отсутствует super () в mock), ни, похоже, не может быть каким-либо образом в Mockito для инициализации свойств макета по умолчанию (например, свойства списка с пустым ArrayList или LinkedList).

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

Только атрибуты classа используют инициализацию по умолчанию: private List dep1 = new ArrayList; private Список dep2 = новый ArrayList

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

Жаль, что только PowerMock поможет здесь дальше.

Предполагая, что ваши тестовые classы находятся в одном пакете (под другим исходным корнем) в качестве тестируемых classов, вы можете просто создать макет:

 YourClass yourObject = mock(YourClass.class); 

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

Вы должны обеспечить ожидания для каждого метода, который вызывается с ожиданием по любым конкретным методам, вызывающим супер-метод, – не уверен, как вы это сделаете с Mockito, но я считаю, что это возможно с помощью EasyMock.

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

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

Вы можете расширить абстрактный class с помощью анонимного classа в своем тесте. Например (с помощью Junit 4):

 private AbstractClassName classToTest; @Before public void preTestSetup() { classToTest = new AbstractClassName() { }; } // Test the AbstractClassName methods. 

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

 @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 абстрактного classа ClassUnderTest .

Whitebox.invokeMethod (..) может пригодиться в этом случае.

Mockito позволяет издеваться над абстрактными classами посредством annotations @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(); // ... } } 

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

  • Как включить тестовые classы в банку Maven и выполнить их?
  • Тестирование модуля ASP.net Веб-сайт Код проекта, хранящийся в App_Code
  • Как запустить все тесты, принадлежащие определенной категории в JUnit 4
  • ASP.NET MVC: Контроллеры модhive тестирования, использующие UrlHelper
  • Как тестировать асинхронные API-интерфейсы?
  • C # «внутренний» модификатор доступа при выполнении модульного тестирования
  • C ++ модульная система тестирования
  • Модульный ввод / вывод файлов
  • Изменение тайм-аута по умолчанию для мокко
  • Как я могу написать единичный тест, чтобы определить, можно ли собирать мусор?
  • Что делает хороший модульный тест?
  • Давайте будем гением компьютера.