Mockito - rozumiem, że szpieg wywołuje prawdziwe metody na obiekcie, podczas gdy makieta wywołuje metody na podwójnym obiekcie. Należy również unikać szpiegów, chyba że wyczuwa się zapach kodu. Jednak jak działają szpiedzy i kiedy właściwie należy ich używać? Czym się różnią od kpiny?
103
Odpowiedzi:
Z technicznego punktu widzenia zarówno „kpiny”, jak i „szpiedzy” to szczególny rodzaj „testowych dubletów”.
Mockito niestety czyni to rozróżnienie dziwnym.
Mock w mockito jest normalnym mockiem w innych frameworkach do mockowania (pozwala na zastępowanie wywołań; to znaczy zwracanie określonych wartości z wywołań metod).
Szpieg w mockito jest częściowym mockiem w innych frameworach mockujących (część obiektu będzie mockowana, a część będzie używać rzeczywistych wywołań metod).
źródło
Obie mogą służyć do kpiny z metod lub pól. Różnica polega na tym, że w makiecie tworzysz kompletną próbę lub fałszywy obiekt podczas szpiegowania, istnieje prawdziwy obiekt i po prostu szpiegujesz lub odrzucasz określone metody.
Oczywiście w przypadku obiektów szpiegowskich, ponieważ jest to prawdziwa metoda, kiedy nie blokujesz metody, wywoła ona prawdziwe zachowanie metody. Jeśli chcesz zmienić i kpić z metody, musisz ją zablokować.
Rozważ poniższy przykład jako porównanie.
import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.runners.MockitoJUnitRunner; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class MockSpy { @Mock private List<String> mockList; @Spy private List<String> spyList = new ArrayList(); @Test public void testMockList() { //by default, calling the methods of mock object will do nothing mockList.add("test"); Mockito.verify(mockList).add("test"); assertEquals(0, mockList.size()); assertNull(mockList.get(0)); } @Test public void testSpyList() { //spy object will call the real method when not stub spyList.add("test"); Mockito.verify(spyList).add("test"); assertEquals(1, spyList.size()); assertEquals("test", spyList.get(0)); } @Test public void testMockWithStub() { //try stubbing a method String expected = "Mock 100"; when(mockList.get(100)).thenReturn(expected); assertEquals(expected, mockList.get(100)); } @Test public void testSpyWithStub() { //stubbing a spy method will result the same as the mock object String expected = "Spy 100"; //take note of using doReturn instead of when doReturn(expected).when(spyList).get(100); assertEquals(expected, spyList.get(100)); } }
Kiedy powinieneś używać kpiny lub szpiega? Jeśli chcesz być bezpieczny i unikać wywoływania usług zewnętrznych i po prostu chcesz przetestować logikę wewnątrz urządzenia, użyj makiety. Jeśli chcesz zadzwonić do usługi zewnętrznej i wykonać wywołanie rzeczywistej zależności, lub po prostu powiedzieć, że chcesz uruchomić program tak, jak jest i po prostu usunąć określone metody, użyj szpiega. Taka jest różnica między szpiegiem a kpiną w mockito.
źródło
Wersja TL; DR,
Za pomocą mock tworzy dla Ciebie instancję typu bare-bone shell.
Dzięki szpiegowi możesz częściowo kpić z istniejącej instancji
List<String> spyList = Mockito.spy(new ArrayList<String>());
Typowy przypadek użycia dla Spy: klasa ma sparametryzowany konstruktor, najpierw chcesz utworzyć obiekt.
źródło
Utworzyłem tutaj działający przykład https://www.surasint.com/mockito-with-spy/
Część z nich tu skopiuję.
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 jakiegoś starszego kodu 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 wtedy API zostanie zmienione. 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.
źródło
Najlepszym miejscem na rozpoczęcie jest prawdopodobnie dokumentacja dotycząca mockito .
Ogólnie rzecz biorąc, mockito mock pozwala na tworzenie stubów.
Utworzyłbyś metodę pośredniczącą, jeśli na przykład ta metoda wykonuje kosztowną operację. Powiedzmy, że uzyskuje połączenie z bazą danych, pobiera wartość z bazy danych i zwraca ją do dzwoniącego. Uzyskanie połączenia z bazą danych może zająć 30 sekund, spowalniając wykonanie testu do momentu, w którym prawdopodobnie przełączysz kontekst (lub przestaniesz wykonywać test).
Jeśli testowana logika nie dba o połączenie z bazą danych, możesz zastąpić tę metodę kodem pośredniczącym, który zwraca wartość zakodowaną na stałe.
Szpieg mockito pozwala sprawdzić, czy metoda wywołuje inne metody. Może to być bardzo przydatne podczas próby pobrania starszego kodu w trakcie testu.
Jest to przydatne, jeśli testujesz metodę, która działa poprzez skutki uboczne, wtedy użyjesz szpiega mockito. To deleguje wywołania do rzeczywistego obiektu i pozwala zweryfikować wywołanie metody, liczbę wywołań itp.
źródło
W skrócie:
@Spy
i@Mock
są często używane w testowaniu kodu, ale programiści mylą się w przypadkach, gdy używają jednego z nich, a zatem programiści używają go,@Mock
aby być bezpiecznym.@Mock
jeśli chcesz po prostu przetestować funkcjonalność zewnętrznie, bez wywoływania tej metody.@Spy
gdy chcesz przetestować funkcjonalność zewnętrznie + wewnętrznie, przy użyciu samej wywoływanej metody.Poniżej znajduje się przykład, w którym wziąłem scenariusz Election20xx w Ameryce.
Głosujących można podzielić według
VotersOfBelow21
iVotersOfABove21
.Idealna exit poll mówi, że Trump wygra wybory bo
VotersOfBelow21
iVotersOfABove21
jak będzie głosować na Trump mówiąc: „ Mamy wybrany prezydent Trump ”Ale to nie jest prawdziwy scenariusz:
Jak więc to przetestować?
public class VotersOfAbove21 { public void weElected(String myVote){ System.out.println("Voters of above 21 has no Choice Than Thrump in 20XX "); } }
public class VotersOfBelow21 { public void weElected(String myVote){ System.out.println("Voters of below 21 has no Choice Than Thrump in 20XX"); } }
public class ElectionOfYear20XX { VotersOfAbove21 votersOfAbove21; VotersOfBelow21 votersOfBelow21; public boolean weElected(String WeElectedTrump){ votersOfAbove21.weElected(WeElectedTrump); System.out.println("We elected President Trump "); votersOfBelow21.weElected(WeElectedTrump); System.out.println("We elected President Trump "); return true; } }
Teraz uwaga w powyższych dwóch pierwszych klasach, obie grupy wiekowe mówią, że nie mają lepszego wyboru niż atut. Co wyraźnie oznacza, że głosowali na Trumpa tylko dlatego, że nie mieli wyboru.
Teraz
ElectionOfYear20XX
mówi, że Trump wygrał, ponieważ obie grupy wiekowe głosowały na niego w przeważającej mierze.Gdybyśmy mieli przetestować
ElectionOfYear20XX
pomocą @Mock, moglibyśmy nie być w stanie znaleźć prawdziwego powodu, dla którego Trump wygrał, po prostu przetestujemy powód zewnętrzny.Jeśli przetestujemy
ElectionOfYear20XX
z @Spy, otrzymamy prawdziwy powód, dla którego Trump wygrał z wynikami zewnętrznego sondażu, tj. Wewnętrznie + zewnętrznie.Nasza
ELectionOfYear20XX_Test
klasa:@RunWith(MockitoJUnitRunner.class) public class ELectionOfYear20XX_Test { @Mock VotersOfBelow21 votersOfBelow21; @Mock VotersOfAbove21 votersOfAbove21; @InjectMocks ElectionOfYear20XX electionOfYear20XX; @Test public void testElectionResults(){ Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice")); } }
Powinno to wyprowadzić tylko wyniki testów logicznych, tj. Sprawdzenie zewnętrzne:
Testowanie zarówno
@Spy
zewnętrznie, jak i wewnętrznie z rzeczywistym wywołaniem metody.@RunWith(MockitoJUnitRunner.class) public class ELectionOfYear20XX_Test { @Spy VotersOfBelow21 votersOfBelow21; @Spy VotersOfAbove21 votersOfAbove21; @InjectMocks ElectionOfYear20XX electionOfYear20XX; @Test public void testElectionResults(){ Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice")); } }
Wynik:
Voters of above 21 has no Choice Than Thrump in 20XX We elected President Trump Voters of below 21 has no Choice Than Thrump in 20XX We elected President Trump
źródło
Podoba mi się prostota tej rekomendacji:
Źródło: https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/
Typowa różnica to:
źródło