В чем разница между шуткой и шпионажем при использовании Mockito?

137

Каков был бы случай использования шпиона Mockito?

Мне кажется, что каждый случай использования шпиона может быть обработан с помощью макета, используя callRealMethod.

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

Виктор Граци
источник

Ответы:

100

Ответ есть в документации :

Реальные частичные издевательства (с 1.8.0)

Наконец, после многих внутренних дискуссий и обсуждений в списке рассылки, в Mockito была добавлена ​​частичная поддержка. Ранее мы рассматривали частичные насмешки как запахи кода. Тем не менее, мы нашли законный вариант использования для частичной имитации.

До релиза 1.8 spy () не производил реальных частичных насмешек, и это приводило некоторых в замешательство. Узнайте больше о шпионаже: здесь или в javadoc для метода spy (Object).

callRealMethod()был введен после spy(), но spy () был оставлен там, конечно, для обеспечения обратной совместимости.

В противном случае, вы правы: все методы шпиона реальны, если не заглушки. Все методы макета заглушаются, если не callRealMethod()вызывается. В целом, я бы предпочел использовать callRealMethod(), потому что это не заставляет меня использовать doXxx().when()идиому вместо традиционногоwhen().thenXxx()

Дж. Б. Низет
источник
Проблема в том, что в этих случаях предпочтение mock-а не шпионское, заключается в том, что класс использует член, который не вводится в него (но локально инициализируется), а затем используется «реальным» методом; в макете член будет инициализирован к его значению Java по умолчанию, которое может вызвать неправильное поведение или даже исключение NullPointerException. Чтобы пройти это, нужно добавить метод init, а затем «действительно» вызвать его, но мне это кажется немного преувеличенным.
Эяль Рот
Из документа: «Шпионов следует использовать осторожно и время от времени, например, когда имеешь дело с устаревшим кодом». Пространство модульного тестирования страдает от слишком многих способов сделать то же самое.
gdbj
89

Разница между шпионом и насмешкой

Когда 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 + собственные заметки.

Саураб Патил
источник
1
Разве вы не имеете в виду size () возвращает 1?
черный
В первом примере, почему mockedList.size()возвращается, 0если этот метод не был заглушен? Это просто значение по умолчанию, учитывая тип возвращаемого значения метода?
Майк
@mike: mockedList.size()возвращает intзначение по умолчанию int0 в Java. Если вы попытаетесь выполнить assertEquals(0, mockedList.size());после mockedList.clear();, результат останется прежним.
realPK
2
Этот ответ хорошо и просто написан и помог мне, наконец, понять разницу между шуткой и шуткой. Хороший.
PesaThe
38

Если есть объект с 8 методами, и у вас есть тест, в котором вы хотите вызвать 7 реальных методов и один метод заглушки, у вас есть два варианта:

  1. Используя макет, вы должны настроить его, вызвав 7 callRealMethod и заглушив один метод.
  2. Используя, spyвы должны настроить его, заглушив один метод

Официальная документация по doCallRealMethodрекомендует использовать шпион для частичнога издевается.

См. Также javadoc spy (Object), чтобы узнать больше о частичных имитациях. Mockito.spy () - рекомендуемый способ создания частичных имитаций. Причина в том, что это гарантирует, что реальные методы вызываются против правильно сконструированного объекта, потому что вы несете ответственность за создание объекта, переданного методу spy ().

user2412398
источник
5

Шпион может быть полезен, когда вы хотите создать модульные тесты для унаследованного кода .

Я создал выполнимый пример здесь 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();

Более подробно в ссылке выше.

Surasin Tancharoen
источник
0

Mockэто голый двойной объект. Этот объект имеет те же сигнатуры методов, но реализация пуста и возвращает значение по умолчанию - 0 и ноль

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();
}

[Тест двойных типов]

yoAlex5
источник