Jaka jest różnica między kpiną a szpiegowaniem podczas korzystania z Mockito?

137

Jaki byłby przypadek użycia szpiega Mockito?

Wydaje mi się, że każdy przypadek użycia szpiega można obsłużyć z makietą, używając callRealMethod.

Jedyną różnicą, jaką widzę, jest to, że jeśli chcesz, aby większość wywołań metod była rzeczywista, oszczędza kilka linii kodu, aby użyć makiety zamiast szpiega. Czy to wszystko, czy brakuje mi szerszego obrazu?

Victor Grazi
źródło

Odpowiedzi:

100

Odpowiedź znajduje się w dokumentacji :

Prawdziwe częściowe makiety (od 1.8.0)

Wreszcie, po wielu wewnętrznych debatach i dyskusjach na liście mailingowej, do Mockito dodano częściowe pozorowane wsparcie. Wcześniej rozważaliśmy częściowe kpiny jako zapachy kodu. Jednak znaleźliśmy uzasadniony przypadek użycia częściowych prób.

Przed wydaniem 1.8 spy () nie produkował rzeczywistych częściowych prób i było to mylące dla niektórych użytkowników. Przeczytaj więcej o szpiegowaniu: tutaj lub w javadoc dla metody spy (Object).

callRealMethod()został wprowadzony później spy(), ale oczywiście spy () tam pozostawiono, aby zapewnić wsteczną kompatybilność.

W przeciwnym razie masz rację: wszystkie metody szpiega są prawdziwe, chyba że zostaną skrócone. Wszystkie metody makiety są odrzucane, chyba że callRealMethod()zostanie wywołane. Generalnie wolałbym używać callRealMethod(), ponieważ nie zmusza mnie to do używania doXxx().when()idiomu zamiast tradycyjnegowhen().thenXxx()

JB Nizet
źródło
Problem z preferowaniem makiety zamiast szpiegowania w tych przypadkach polega na tym, że klasa używa elementu członkowskiego, który nie jest do niej wstrzyknięty (ale zainicjowany lokalnie) i jest później używany przez metodę „real”; w makiecie element członkowski zostanie zainicjowany z domyślną wartością Java, co może spowodować nieprawidłowe zachowanie lub nawet wyjątek NullPointerException. Sposobem na to jest dodanie metody „init”, a następnie „naprawdę” wywołanie jej, ale wydaje mi się to trochę przesadą.
Eyal Roth
Z dokumentu: „Ze szpiegów należy korzystać ostrożnie i okazjonalnie, na przykład podczas korzystania ze starszego kodu”. Przestrzeń do testów jednostkowych cierpi na zbyt wiele sposobów robienia tego samego.
gdbj
89

Różnica między Szpiegiem a Mockiem

Kiedy Mockito tworzy makietę - robi to z klasy typu, a nie z rzeczywistej instancji. Makieta po prostu tworzy szkieletową instancję klasy Class, w całości przystosowaną do śledzenia interakcji z nią. Z drugiej strony szpieg zapakuje istniejącą instancję. Będzie nadal zachowywał się w taki sam sposób, jak normalna instancja - jedyną różnicą jest to, że będzie ona również wyposażona w narzędzia do śledzenia wszystkich interakcji z nią.

W poniższym przykładzie - tworzymy makietę klasy ArrayList:

@Test
public void whenCreateMock_thenCreated() {
    List mockedList = Mockito.mock(ArrayList.class);

    mockedList.add("one");
    Mockito.verify(mockedList).add("one");

    assertEquals(0, mockedList.size());
}

Jak widać - dodanie elementu do makiety w rzeczywistości niczego nie dodaje - po prostu wywołuje metodę bez żadnego innego efektu ubocznego. Z drugiej strony szpieg zachowa się inaczej - faktycznie wywoła rzeczywistą implementację metody add i doda element do listy bazowej:

@Test
public void whenCreateSpy_thenCreate() {
    List spyList = Mockito.spy(new ArrayList());
    spyList.add("one");
    Mockito.verify(spyList).add("one");

    assertEquals(1, spyList.size());
}

Tutaj możemy z pewnością powiedzieć, że prawdziwa wewnętrzna metoda obiektu została wywołana, ponieważ wywołanie metody size () daje rozmiar 1, ale ta metoda size () nie została wyszydzona! Skąd więc pochodzę? Wewnętrzna metoda real size () jest wywoływana, ponieważ size () nie jest wyszydzana (ani ukryta), dlatego możemy powiedzieć, że wpis został dodany do rzeczywistego obiektu.

Źródło: http://www.baeldung.com/mockito-spy + notatki własne.

Saurabh Patil
źródło
1
Czy nie masz na myśli, że size () zwraca 1?
czarny
W pierwszym przykładzie, dlaczego mockedList.size()zwraca, 0jeśli ta metoda również nie została usunięta? Czy to tylko wartość domyślna, biorąc pod uwagę zwracany typ metody?
Mike
@mike: mockedList.size()zwraca intwartość domyślną int0 w Javie. Jeśli spróbujesz wykonać assertEquals(0, mockedList.size());później mockedList.clear();, wynik pozostanie taki sam.
realPK
2
Ta odpowiedź jest dobrze i prosto napisana i pomogła mi w końcu zrozumieć różnicę między symulacją a szpiegiem. Niezłe.
PesaThe
38

Jeśli istnieje obiekt z 8 metodami i masz test, w którym chcesz wywołać 7 prawdziwych metod, a jedną z nich usunąć, masz dwie opcje:

  1. Używając makiety, musiałbyś go ustawić przez wywołanie 7 callRealMethod i odcięcie jednej metody
  2. Używając a spy, musisz go ustawić, odrzucając jedną metodę

Oficjalna dokumentacja na doCallRealMethodzaleca stosowanie szpiegiem częściowych mocks.

Zobacz także javadoc spy (Object), aby dowiedzieć się więcej o częściowych mockach. Mockito.spy () to zalecany sposób tworzenia częściowych mocków. Powodem jest to, że gwarantuje wywołanie prawdziwych metod przeciwko poprawnie skonstruowanemu obiektowi, ponieważ jesteś odpowiedzialny za skonstruowanie obiektu przekazanego do metody spy ().

user2412398
źródło
5

Szpieg może być przydatny, gdy chcesz tworzyć testy jednostkowe dla starszego kodu .

Utworzyłem tutaj działający przykład https://www.surasint.com/mockito-with-spy/ , część z niego kopiuję tutaj.

Jeśli masz coś takiego jak ten kod:

public void transfer(  DepositMoneyService depositMoneyService, WithdrawMoneyService withdrawMoneyService, 
             double amount, String fromAccount, String toAccount){
    withdrawMoneyService.withdraw(fromAccount,amount);
    depositMoneyService.deposit(toAccount,amount);
}

Możesz nie potrzebować szpiega, ponieważ możesz po prostu udawać usługę DepositMoneyService i WithdrawMoneyService.

Ale w przypadku niektórych starszych kodów zależność jest w kodzie w następujący sposób:

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

Tak, możesz przejść do pierwszego kodu, ale potem API jest zmieniane. Jeśli ta metoda jest używana w wielu miejscach, musisz zmienić je wszystkie.

Alternatywą jest to, że możesz wyodrębnić zależność w następujący sposób:

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

Następnie możesz użyć szpiega i wstrzyknąć zależność w następujący sposób:

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

Więcej szczegółów w powyższym linku.

Surasin Tancharoen
źródło
0

Mockjest nagim, podwójnym obiektem. Ten obiekt ma te same sygnatury metod, ale realizacja jest pusta i zwraca wartość domyślną - 0 i null

Spyjest sklonowanym podwójnym obiektem. Nowy obiekt jest klonowany na podstawie prawdziwego obiektu, ale masz możliwość kpiny

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

[Testuj podwójne typy]

yoAlex5
źródło