-
Notifications
You must be signed in to change notification settings - Fork 0
Porównanie Framework'ów Testowych Mockito oraz EasyMock
Przeglądając różne artykuły internetowe zawierające informacje o tym, który framework do mockowania jest najlepszy najczęściej trafiałem na opis i porównania frameworków Mockito i EasyMock. Dlatego też postanowiłem wybrać owe biblioteki do wspomagania testów wykonywanych za pomocą JUnit oraz TestNG i porównać je same pod względem użyteczności, różnic czy też poziomu skomplikowania.
Tutaj zaprezentowane zostaną przykładowe schematy testowe, ukazujące jak wykonuje się testy dla frameworku Mockito oraz EasyMock i ukazujące ich różnice.
Prosty test wykonany za pomocą Mockito
import static org.mockito.Mockito.*;
List mock = mock(List.class);
when(mock.get(0)).thenReturn("one");
when(mock.get(1)).thenReturn("two");
someCodeThatInteractsWithMock();
verify(mock).clear();
Prosty test wykonany za pomocą EasyMock
import static org.easymock.classextension.EasyMock.*;
List mock = createNiceMock(List.class);
expect(mock.get(0)).andStubReturn("one");
expect(mock.get(1)).andStubReturn("two");
mock.clear();
replay(mock);
someCodeThatInteractsWithMock();
verify(mock);
Tu przedstawione zostaną różnice w implementacji różnych przypadków, w których wykorzystuje się frameworki testowe Mockito oraz EasyMock.
Stubbing metod typu void - Mockito
List mock = mock(List.class);
doThrow(new RuntimeException()).when(mock).clear();
Stubbing metod typu void - EasyMock
List mock = createNiceMock(List.class);
mock.clear();
expectLastCall().andThrow(new RuntimeException());
replay(mock);
Rzucanie wyjątków - Mockito
Zgłaszanie wyjątków można wyszydzać za pomocą .thenThrow (ExceptionClass.class) po Mockito.when (mock.method (args)).
@Test
public void mockExceptionThrowin() {
UserForm userForm = new UserForm();
Mockito.when(loginService.login(userForm)).thenThrow(IllegalArgumentException.class);
String login = loginController.login(userForm);
Assert.assertEquals("ERROR", login);
Mockito.verify(loginService).login(userForm);
Mockito.verifyZeroInteractions(loginService);
}
Rzucanie wyjątków - EasyMock
Rzucanie wyjątków można wyśmiewać za pomocą .andThrow (new ExceptionClass ()) po wywołaniu EasyMock.expect (…).
@Test
public void mockExceptionThrowing() {
UserForm userForm = new UserForm();
EasyMock.expect(loginService.login(userForm)).andThrow(new IllegalArgumentException());
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("ERROR", login);
EasyMock.verify(loginService);
}
Dopasowywanie argumentów niestandardowych - Mockito
Czasami dopasowywanie argumentów dla wywołań pozorowanych musi być trochę bardziej złożone niż tylko ustalona wartość lub anyString (). W takich przypadkach Mockito ma swoją klasę dopasowującą, która jest używana z argThat (ArgumentMatcher <>).
@Test
public void argumentMatching() {
UserForm userForm = new UserForm();
userForm.username = "foo";
// default matcher
Mockito.when(loginService.login(Mockito.any(UserForm.class))).thenReturn(true);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
Mockito.verify(loginService).login(userForm);
// complex matcher
Mockito.verify(loginService).setCurrentUser(ArgumentMatchers.argThat(
new ArgumentMatcher<String>() {
@Override
public boolean matches(String argument) {
return argument.startsWith("foo");
}
}
));
}
Dopasowywanie argumentów niestandardowych - EasyMock
Dopasowanie niestandardowych argumentów jest nieco bardziej skomplikowane w EasyMock, ponieważ musisz utworzyć statyczną metodę, w której tworzysz rzeczywisty element dopasowujący, a następnie raportujesz go za pomocą EasyMock.reportMatcher (IArgumentMatcher).
Po utworzeniu tej metody używasz jej na pozorowanym oczekiwaniu z wywołaniem metody (jak widać w przykładzie w wierszu).
@Test
public void argumentMatching() {
UserForm userForm = new UserForm();
userForm.username = "foo";
// default matcher
EasyMock.expect(loginService.login(EasyMock.isA(UserForm.class))).andReturn(true);
// complex matcher
loginService.setCurrentUser(specificArgumentMatching("foo"));
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
EasyMock.verify(loginService);
}
private static String specificArgumentMatching(String expected) {
EasyMock.reportMatcher(new IArgumentMatcher() {
@Override
public boolean matches(Object argument) {
return argument instanceof String
&& ((String) argument).startsWith(expected);
}
@Override
public void appendTo(StringBuffer buffer) {
//NOOP
}
});
return null;
}
Mockito ma fajną funkcję zwaną szpiegowaniem. Tworzysz rzeczywisty obiekt (nie makietę) i szpiegujesz go. Zasadniczo oznacza to, że obiekt będzie się zachowywał normalnie, ale wszystkie wywołania metod będą rejestrowane, co pozwoli na ich późniejszą weryfikację. Oto przykład:
Przykład użycia dla Mockito
List list = new LinkedList(); // real object
List spy = spy(list); // spy wrapper
//optionally, you can stub out some methods:
when(spy.size()).thenReturn(100);
//this method call is spied
spy.add("one");
// since a real add was done, get(0) will return "one"
assertEquals("one", spy.get(0));
//size() method was stubbed and to return 100
assertEquals(100, spy.size());
//optionally, you can verify that add was called as expected
verify(spy).add("one");
W EasyMock nie ma szpiegowania jako takiego. Próbowałem jednak pomyśleć o przypadkach użycia i doszedłem do wniosku, że używając podzbioru funkcji przechwytywania EasyMock, częściowego mockowania i delegowania, powinienem być w stanie objąć je wszystkie. Może to być jednak bardziej uciążliwe. Ale myślę, że takie przypadki użycia są dość rzadkie. Nie jestem przeciwny dodawaniu funkcji szpiegowskiej, jeśli nie okaże się to błędne.
Jeśli chodzi o powyższy przykład, musimy pomyśleć o celu tego testu. Powiedzmy, że w tym przypadku mamy jakiś stary kod, dla którego chcemy się upewnić, że metoda add jest wywoływana z odpowiednim parametrem, ale chcemy zachować jej zwykłe zachowanie. Jest to dość rzadkie, ale może się zdarzyć. Możemy to zrobić w ten sposób:
Przykład użycia dla EasyMock
// Real object
List real = new LinkedList();
// create the mock
List list = createMock(List.class);
// spy the parameter and perform the normal behavior
Capture c = new Capture();
expect(list.add(capture(c))).andDelegateTo(real);
replayAll();
// the actual test
list.add("one");
// get will return what was expected
assertEquals("one", real.get(0));
// check the capture (will throw an error if nothing was captured)
assertEquals("one", c.getValue());
// this is unnecessary since checking the capture is enough
verifyAll();
Tak. Tak to powinno byc. Przynajmniej taki jest mój punkt widzenia. To naprawdę jedna z głównych różnic. Z EasyMock będziesz musiał oczekiwać wszystkiego. Więc twoje testy się zepsują, gdy tylko coś zmienisz. Z Mockito zweryfikujesz, czego chcesz i tylko te weryfikacje mogą się zepsuć.
Co za różnica? EasyMock zmusza Cię do oczekiwania wszystkiego. Twoje testy się wtedy zepsują i będziesz zmuszony wrócić do każdego z nich i zadać sobie pytanie „Hum, czy to miało się zepsuć?”. Mockito nie zmusi Cię do niczego. Tak więc test zweryfikuje tylko to, co pierwotna myśl programisty byłaby istotna. Ale wtedy, pracując nad inną częścią kodu, nie będziesz wiedział, że faktycznie brakuje weryfikacji. Nie będziesz zmuszony spojrzeć na stare testy. Wszystkie testy pozostaną zielone, a paskudne błędy wkradną się do środka. Dla mnie to główna różnica. Kompromis między szybszym napisaniem testu a testem sprawdzającym nieoczekiwane. Spójrzmy na przykład, który to ilustruje.
Przykład użycia dla Mockito
//mock creation
List mockedList = mock(List.class);
// call the mock in some business code
mockedList.add("one"); // the return value isn't used nor specified
// mockedList.clear(); // adding this call in the business code WON'T break the test
//verification that add() was called with the right parameter
verify(mockedList).add("one");
Przykład użycia dla EasyMock
//mock creation
List mockedList = createMock(List.class);
// We are forced to return a meaningful value
expect(mockedList.add("one").andReturn(true);
replayAll();
// call the mock in some business code
mockedList.add("one"); // the return value isn't used by was specified
// mockedList.clear(); // adding this call in the business code will break the test
//verification that add() was called with the right parameter
// and that no other methods were called
verifyAll();
Przykład EasyMock testuje to, co było zamierzone, ale robi również dwie rzeczy za kulisami.
Upewnienie się, że makieta dokładnie mockuje listę, wymuszając zwrócenie wartości Zapobieganie działaniu testu, jeśli inne wywołania makiet są wykonywane przez kod biznesowy. To zmusi nas do zastanowienia się, czy złamanie tego testu było oczekiwane przez naszą refaktoryzację.
-
Etap 1
-
Etap 2
-
Etap 3
-
Etap 4
-
Etap 5
-
Etap 6
-
Etap 7