Skip to content

Mockito, czyli testy z procentami

przez Łukasz Picur - Czerwiec 26th, 2011

Podczas pisania testów jednostkowych naszej aplikacji bardzo często wymagane jest mockowanie (lub stubowanie) klas, czyli najogólniej mówiąc tworzenie obiektów tych klas z częściowo zmienionym zachowaniem. Wszystko to po to, aby dało się przeprowadzić test w całkowicie kontrolowanych i znanych warunkach, co jest niezbędne do określenia poprawności działania sprawdzanej funkcjonalności. Można oczywiście próbować dziedziczyć po wybranej klasie i przeciążać interesujące nas metody, ale w większości przypadków łatwiej i szybciej jest użyć jednego z gotowych frameworków przeznaczonych właśnie do tego celu. Narzędziem takim jest Mockito, o którym chciałbym napisać kilka słów.

Możliwości Mockito najłatwiej przedstawić i opisać na praktycznym przykładzie.

import static org.mockito.Mockito.*;

public class MockitoExampleTest
{
	@Test
	public void test() {

		Point p = mock(Point.class);

		when(p.getX()).thenReturn(2.0);
		when(p.getY()).thenReturn(3.0);
		doCallRealMethod().when(p).setLocation(anyDouble(), anyDouble());
		when(p.getLocation()).thenCallRealMethod();

		p.setLocation(1.0, 5.0);
		System.out.println("P(" + p.getX() + "," + p.getY() + "), LOCATION: " + p.getLocation());

		verify(p, never()).distance(any(Point2D.Double.class));
		verify(p, times(1)).getY();
		verify(p, times(1)).getX();
	}
}

W powyższym kodzie pominięte zostały importy (prócz jednego statycznego) oraz definicja pakietu, ale dodanie ich nie powinno stanowić żadnego problemu. Sam test jest napisany przy użyciu JUnit 4 – metoda testowa oznaczona jest adnotacją @Test. Nas najbardziej interesować będzie jednak to, co znajduje się wewnątrz niej. Analizując ciało metody test(), zacznijmy od pierwszej linii:

Point p = mock(Point.class);

Tworzymy tutaj mock korzystając z dostarczonej przez Mockito metody. Konstrukcja odbywa się w oparciu o wybrany przez nas typ – będzie on także typem zwróconego przez metodę mock() obiektu. W przykładzie użyta została klasa Point. Warte zauważenia jest to, że po utworzeniu mocka, będzie on przechowywał informacje o wszystkich wywołanych na nim metodach. Do czego to się przyda powiem już niedługo, a tymczasem przyjrzyjmy się dwóm kolejnym liniom:

when(p.getX()).thenReturn(2.0);
when(p.getY()).thenReturn(3.0);

Pokazują one, że możemy w wygodny sposób modelować zachowanie utworzonej „zaślepki”. API Mockito zostało stworzone w taki sposób, że powyższe wywołania przypominają budową logicznie skonstruowane zdania. Ich działanie jest dokładnie takie, jakiego można oczekiwać – instruujemy framework co należy zrobić w przypadku wywołania metod getX()getY(). W przykładzie zdecydowaliśmy się na zwrócenie za każdym razem stałej wartości. Możliwości jest jednak bardzo wiele:

  • wywołanie rzeczywistej metody (w tym przypadku klasy Point)
    when(p.getY()).thenCallRealMethod();
    
  • wyrzucenie wyjątku
    when(p.getY()).thenThrow(new UnsupportedOperationException());
    
  • zwracanie kolejnych elementów podanej sekwencji
    when(p.getY()).thenReturn(1.0, 2.0, 3.0, 4.0, 5.0);
    
  • wykonanie pewnej logiki w celu obliczenia wartości do zwrócenia
    when(p.getY()).thenAnswer(new Answer<Double>() {
    	public Double answer(InvocationOnMock invocation) throws Throwable {
    
    		Double random = Math.random();
    		return random;
    	}
    });
    

Jeśli mockowana metoda przyjmuje parametry, możemy dodatkowo określić, czy przechwytujemy jej każde wywołanie, czy tylko te z określonymi wartościami atrybutów.

when(p.distance(any(Point.class))).thenReturn(5.0);
when(p.distance(anyDouble(), anyDouble())).thenCallRealMethod();
when(p.distance(5.0, 5.0)).thenCallRealMethod();

Mockito pozwala na dopasowanie konkretnych wartości (linia 3 w powyższym listingu), lub też wszystkich argumentów danego typu (linie 1 i 2). W drugim przypadku należy użyć jednej z wielu dostępnych metod pokroju anyXxx() – istnieją gotowe implementacje dla wszystkich typów prostych oraz najpopularniejszych klas i interfejsów (Collection, List, String, etc.). Jeśli potrzebujemy czegoś bardziej wyszukanego, dostępna jest także metoda any() przyjmująca odpowiedni obiekt typu Class jako argument. Należy jednak pamiętać o jednej rzeczy: jeśli choć jeden argument funkcji będzie wieloznacznikiem, pozostałe także muszą nimi być. Co jednak zrobić, jeśli dla niektórych argumentów chcemy przechwycić jedynie konkretną wartość? Do tego celu stworzona została metoda eq().

when(p.distance(anyDouble(), 5.0)).thenCallRealMethod();      // ŹLE
when(p.distance(anyDouble(), eq(5.0))).thenCallRealMethod();  // OK

Pełną listę metod pozwalających na dopasowanie argumentów możemy znaleźć w dokumentacji klasy Matchers. Nam tymczasem został do prześledzenia jeszcze jeden przypadek związany z modelowaniem mocka – zmiana zachowania metod zwracających void. Sytuacja ta wymaga odmiennego podejścia, ponieważ kompilator nie pozwoli na użycie wywołania takiej metody jako argumentu innej. W tym celu API Mockito oddaje w nasze ręce garść metod: doThrow(), doAnswer(), doNothing(), doReturn() oraz doCallRealMethod(). Sposób ich użycia pokazany jest w 12 linii przytoczonego na samym początku tekstu przykładu. Oczywiście metody doAnswer() i doReturn() nie mają zastosowania przy metodach zwracających void, ale zdecydowałem się wymienić je tu, ponieważ należą do tej samej grupy, co pozostałe. Używa się ich w bardziej specyficznych przypadkach, o których opowiem w innym artykule.

Drugą, prócz modelowania zachowania metod, główną funkcjonalnością Mockito jest kontrola ich wywołań. Wcześniej napisałem, że stworzony mock pamięta wszystkie wywołane na nim metody. Ta jego cecha umożliwia pisanie testów pod kątem wykonania, lub nie, określonych metod. Linie 18 – 20 pokazują jak odbywa się to w praktyce. Pierwsza z nich demonstruje jak zweryfikować brak wywołań wybranej metody mocka. Dwie kolejne sprawdzają konkretną ilość wywołań korzystając z metody times(). Oprócz metod never() i times() pokazanych w przykładzie, mamy także do dyspozycji szereg innych:

  • atLeastOnce() - co najmniej jedno wywołanie
  • atLeast(x) – co najmniej x wywołań
  • atMost(x) - co najwyżej x wywołań

Prócz opisanej wyżej podstawowej walidacji, możemy także posunąć się do bardziej wyszukanych testów, jak np. kontrola kolejności wywołań wybranych metod. Technikę tą obrazuje poniższy przykład.

InOrder order = inOrder(p);
order.verify(p).setLocation(1.0, 2.0);
order.verify(p).distanceSq(2.0, 3.0);

Fragment ten należy traktować jako rozwinięcie kodu z początku tekstu. W pierwszej kolejności należy utworzyć obiekt klasy InOrder. Służy do tego metoda inOrder(), której przekazujemy wszystkie mocki, których kolejność wywołania metod mamy zamiar sprawdzać. Następnie w pożądanej kolejności sprawdzamy poszczególne metody korzystając z metody verify() stworzonego przed chwilą obiektu. Warto pamiętać o tym, że nie musimy sprawdzać kolejności wywołania wszystkich metod mocka – wystarczy zrobić to tylko w przypadku tych, na których kolejności nam zależy.

Mockito pozwala również na wykrycie zbędnych wywołań – jest to możliwe przy wykorzystaniu metody verifyNoMoreInteractions().

Point p = mock(Point.class);

when(p.getX()).thenReturn(2.0);
when(p.getY()).thenReturn(3.0);
doCallRealMethod().when(p).setLocation(anyDouble(), anyDouble());
when(p.getLocation()).thenCallRealMethod();

double x = p.getX();
double y = p.getY();

verify(p, times(1)).getX();
verifyNoMoreInteractions(p);

W powyższym przypadku weryfikacja nie zakończy się pomyślnie, gdyż metoda getX() nie była jedyną wywołaną na mocku. W linii 9 wywoływana jest także getY(). Tego typu sprawdzenia nie należy jednak nadużywać – w wielu przypadkach wystarczające i bardziej czytelne będzie użycie wspomnianej wcześniej metody never().

Tym sposobem dobrnęliśmy do końca opisu najważniejszych i najczęściej używanych funkcjonalności Mockito. Powinny one okazać się wystarczające w ogromnej większości przypadków. Niemniej jednak w kolejnym wpisie poświęconym opisywanemu narzędziu przybliżę pozostałe, bardziej specyficzne i rzadziej używane techniki. Tymczasem zachęcam do eksperymentowania z Mockito we własnym zakresie i dodania odrobiny procentów do swoich testów.

Kategoria → Java

Jeden komentarz

Trackbacks & Pingbacks

  1. Mockito - testy z procentami - develway.pl

Zostaw komentarz

Info: XHTML jest dozwolony. Twój adres email nigdy nie będzie opublikowany.

Obserwuj komentarze poprzez RSS