Какая разница между издевательством и шпионажем при использовании Мокито?
Каков был бы случай использования шпиона Mockito?
Мне кажется, что каждый случай использования шпиона может быть обработан с помощью макета, используя callRealMethod.
Одно отличие, которое я вижу, состоит в том, что если вы хотите, чтобы большинство вызовов методов были реальными, это экономит некоторые строки кода для использования имитатора против шпиона. Это или я скучаю по большей картине?
4 ответа
Ответ есть в документации:
Реальные частичные издевательства (С 1.8.0)
Наконец, после многих внутренних дискуссий и обсуждений в списке рассылки, в Mockito была добавлена частичная поддержка. Ранее мы рассматривали частичные насмешки как запахи кода. Тем не менее, мы нашли законный вариант использования для частичных имитаций - подробнее читайте здесь
До релиза 1.8 spy() не производил реальных частичных насмешек, и это приводило некоторых в замешательство.
callRealMethod()
был введен после spy()
, но spy () был оставлен там, конечно, для обеспечения обратной совместимости.
В противном случае, вы правы: все методы шпиона реальны, если они не заглушены. Все методы макета заглушены, если только callRealMethod()
называется. В общем, я бы предпочел использовать callRealMethod()
потому что это не заставляет меня использовать doXxx().when()
идиома вместо традиционного when().thenXxx()
Разница между шпионом и насмешкой
Когда Mockito создает макет - он делает это из класса типа, а не из фактического экземпляра. Макет просто создает пустой экземпляр оболочки класса, полностью оснащенный для отслеживания взаимодействий с ним. С другой стороны, шпион обернет существующий экземпляр. Он по-прежнему будет вести себя так же, как и обычный экземпляр, с той лишь разницей, что он также будет оснащен инструментами для отслеживания всех взаимодействий с ним.
В следующем примере - мы создаем макет класса ArrayList:
@Test
public void whenCreateMock_thenCreated() {
List mockedList = Mockito.mock(ArrayList.class);
mockedList.add("one");
Mockito.verify(mockedList).add("one");
assertEquals(0, mockedList.size());
}
Как вы можете видеть - добавление элемента в макетированный список на самом деле ничего не добавляет - он просто вызывает метод без других побочных эффектов. С другой стороны, шпион будет вести себя по-другому - он на самом деле вызовет реальную реализацию метода add и добавит элемент в базовый список:
@Test
public void whenCreateSpy_thenCreate() {
List spyList = Mockito.spy(new ArrayList());
spyList.add("one");
Mockito.verify(spyList).add("one");
assertEquals(1, spyList.size());
}
Здесь мы можем с уверенностью сказать, что был вызван настоящий внутренний метод объекта, потому что когда вы вызываете метод size(), вы получаете размер как 1, но этот метод size() не подвергается насмешке! Так откуда же взялся 1? Внутренний реальный метод size() вызывается как size(), но он не является насмешливым (или заглушенным), поэтому мы можем сказать, что запись была добавлена к реальному объекту.
Источник: http://www.baeldung.com/mockito-spy + собственные заметки.
Если есть объект с 8 методами, и у вас есть тест, в котором вы хотите вызвать 7 реальных методов и заглушить один метод, у вас есть два варианта:
- Используя макет, вы должны настроить его, вызвав 7 callRealMethod и заглушив один метод.
- Используя
spy
Вы должны настроить его, заглушив один метод
Официальная документация по doCallRealMethod
рекомендует использовать шпиона для частичных издевательств.
См. Также javadoc spy(Object), чтобы узнать больше о частичных имитациях. Mockito.spy() - рекомендуемый способ создания частичных имитаций. Причина в том, что это гарантирует, что реальные методы вызываются против правильно сконструированного объекта, потому что вы несете ответственность за создание объекта, переданного методу spy ().
Шпион может быть полезен, когда вы хотите создать модульные тесты для унаследованного кода.
Я создал выполнимый пример здесь https://www.surasint.com/mockito-with-spy/, я скопировал некоторые из них здесь.
Если у вас есть что-то вроде этого кода:
public void transfer( DepositMoneyService depositMoneyService, WithdrawMoneyService withdrawMoneyService,
double amount, String fromAccount, String toAccount){
withdrawMoneyService.withdraw(fromAccount,amount);
depositMoneyService.deposit(toAccount,amount);
}
Вам может не понадобиться шпион, потому что вы можете просто издеваться над DepositMoneyService и WithdrawMoneyService.
Но с некоторым устаревшим кодом зависимость находится в коде так:
public void transfer(String fromAccount, String toAccount, double amount){
this.depositeMoneyService = new DepositMoneyService();
this.withdrawMoneyService = new WithdrawMoneyService();
withdrawMoneyService.withdraw(fromAccount,amount);
depositeMoneyService.deposit(toAccount,amount);
}
Да, вы можете перейти к первому коду, но затем API будет изменен. Если этот метод используется во многих местах, вы должны изменить все из них.
Альтернативой является то, что вы можете извлечь зависимость следующим образом:
public void transfer(String fromAccount, String toAccount, double amount){
this.depositeMoneyService = proxyDepositMoneyServiceCreator();
this.withdrawMoneyService = proxyWithdrawMoneyServiceCreator();
withdrawMoneyService.withdraw(fromAccount,amount);
depositeMoneyService.deposit(toAccount,amount);
}
DepositMoneyService proxyDepositMoneyServiceCreator() {
return new DepositMoneyService();
}
WithdrawMoneyService proxyWithdrawMoneyServiceCreator() {
return new WithdrawMoneyService();
}
Затем вы можете использовать шпион для вставки зависимости следующим образом:
DepositMoneyService mockDepositMoneyService = mock(DepositMoneyService.class);
WithdrawMoneyService mockWithdrawMoneyService = mock(WithdrawMoneyService.class);
TransferMoneyService target = spy(new TransferMoneyService());
doReturn(mockDepositMoneyService)
.when(target).proxyDepositMoneyServiceCreator();
doReturn(mockWithdrawMoneyService)
.when(target).proxyWithdrawMoneyServiceCreator();
Более подробно в ссылке выше.
Mock
- голый двойной объект. Этот объект имеет те же сигнатуры методов, но реализация пуста и возвращает значение по умолчанию - 0 и null.
Spy
это клонированный двойной объект. Новый объект клонируется на основе реального объекта, но у вас есть возможность имитировать его
class A {
String foo1() {
foo2();
return "RealString_1";
}
String foo2() {
return "RealString_2";
}
void foo3() {
foo4();
}
void foo4() {
}
}
@Test
public void testMockA() {
//given
A mockA = Mockito.mock(A.class);
Mockito.when(mockA.foo1()).thenReturn("MockedString");
//when
String result1 = mockA.foo1();
String result2 = mockA.foo2();
//then
assertEquals("MockedString", result1);
assertEquals(null, result2);
//Case 2
//when
mockA.foo3();
//then
verify(mockA).foo3();
verify(mockA, never()).foo4();
}
@Test
public void testSpyA() {
//given
A spyA = Mockito.spy(new A());
Mockito.when(spyA.foo1()).thenReturn("MockedString");
//when
String result1 = spyA.foo1();
String result2 = spyA.foo2();
//then
assertEquals("MockedString", result1);
assertEquals("RealString_2", result2);
//Case 2
//when
spyA.foo3();
//then
verify(spyA).foo3();
verify(spyA).foo4();
}