Wprowadzenie do testów jednostkowych Marcin Dziedzic, Wiktor Żołnowski
Część I: Koncepcja testów jednostkowych
Co to jest test jednostkowy wg Wiki? Test jednostkowy (ang. unit test) to w programowaniu metoda testowania tworzonego oprogramowania poprzez wykonywanie testów weryfikujących poprawność działania pojedynczych elementów (jednostek) programu - np. metod lub obiektów.
Czym naprawdę jest test jednostkowy? Testem najmniejszego elementu systemu w izolacji Dokumentacją z przykładami użycia Kodem, równie ważnym jak kod produkcyjny Najtańszym możliwym sposobem eliminacji błędów Metodą weryfikacji założeń Efektywnym sposobem projektowania ( o tym później )
Czym z pewnością NIE jest test jednostkowy? Weryfikacją działania systemu jako całości Lekarstwem na wszystkie problemy
Dlaczego programiści zaniedbują testy jednostkowe? Nie ma na nie czasu Panuje przeświadczenie, że testy są nudne i bezcelowe Brak kompetencji w pisaniu testów Zbytnia wiara we własne umiejętności I tak ktoś to przetestuje, przecież mamy testerów Testy są napisane niechlujnie a ich utrzymanie jest kłopotliwe
Po co nam 'testerom' testy jednostkowe? Pomoże przy tworzeniu testów automatycznych Bez tego nie ma mowy o zrozumieniu TDD i BDD Umożliwi lepsze poznanie systemu Umożliwi dokonywanie rzetelnych przeglądów kodu Umożliwi weryfikację założeń Umożliwi ocenę stopnia przetestowania aplikacji Szybko zlokalizujecie błąd jeśli coś zacznie się psuć Zwiększy waszą wartość na rynku pracy
Charakterystyka dobrego testu jednostkowe F.I.R.S.T Szybkie (Fast) Niezależne (Independent) Powtarzalne (Repeatable) Samo kontrolujące się (Self-Validating) O czasie (Timely)
A poza tym... Być prosty Powinien pokazywać intencje Projektować architekturę rozwiązania na najniższym poziomie
Część II: Nieśmiertelny JUnit
Co to jest Junit? Framework umożliwiający pisanie testów - nie tylko jednostkowych
Dlaczego JUnit? Powszechnie używany a zatem świetnie przetestowany Ekstremalnie prosty w użyciu Zaspokaja praktycznie wszystkie potrzeby, nie tylko te związane z testami jednostkowymi Każde nowoczesne Java IDE wspiera go out-of-the-box Ciągle rozwijany Dojrzały Doczekał się implementacji w wielu językach (Junit, Nunit, PhpUnit, JSUnit) Stawia na niezależność testów (jedna z zasad F.I.R.S.T)
Junit 4.x adnotacje @Test @Test(expected=NullPointerException.class) @Test(timeout=10) @Before, @After @BeforeClass, @AfterClass + statyczna metoda @Ignore @Parametrized @RunWith, @Suit
Junit 4.x asercje asserttrue(condition) assertfalse(condition) assertnull(object) assertnotnull(object) assertequals(expected, actual) assertnotequals(expected, actual) assertsame(expected, actual) assertnotsame(expected, actual) assertarrayequals(expected, actual)
Jak wyglądało to w wersji 3? Klasa z testem musiała dziedziczyć z TestCase Nazwy testów musiały zaczynać się od 'test' Metody wykonywane przed testami definiowaliśmy jako @Override Wszystkie asercje były zdefiniowane bezpośrednio w klasie testu Nie istniała możliwość parametryzacji Brakowało adnotacji
Cześć III: Zwinny a raczej płynny Fest-Assert
Co to jest Fest-Assert? Biblioteka, która dostarcza płynnego interfejsu (fluent interface) do tworzenia asercji
Co wyróżnia Fest-Assert? Fluent interface płynny styl pisania asercji Stawia na czytelność assertthat(sauron).isnotin(fellowshipofthering); assertthat(frodo.getage()).isequalto(33); Zrozumiałe komunikaty błędów actual value:<'sauron'> should not be in:<['frodo', 'Sauron']> expected:<3[3]> but was:<3[4]>
Co wyróżnia Fest-Assert? Posiada wbudowane asercje do weryfikacji Typów prymitywnych Tablic Kolekcji Zawartości plików Wyrzuconych wyjątków Zbuforowanych zdjęć BufferedImage Jest łatwo rozszerzalna poprzez Condition i GenericAssert
Fest-Assert użycie W Junit napisalibyśmy by assertequals(2, list.size()); asserttrue(list.contains(element1)); asserttrue(list.contains(element2)); Po refaktoryzacji assertcontainsonlyelements(list, element1, element2)
Fest-Assert użycie Zamiast tego możemy napisać assertthat(list).hassize(2).contains(element1, element2); Lub nawet prościej assertthat(list).containsexactly(element1, element2);
Część IV: Wstęp do mockowania
Co to jest mock? Jest to obiekt, który imituje zachowanie innego obiektu w kontrolowany sposób.
Co otrzymujemy dzięki zastosowaniu mocków? Izolujemy testowaną klasę od innych elementów systemu Testy są znacznie szybsze Możemy symulować zachowania innych obiektów Możemy symulować sytuacje wyjątkowe Możemy weryfikować czy odpowiednie metody zostały wywołane i z jakimi parametrami Możliwość rozpoczęcia prac bez gotowego zaplecza bazy danych, usług zewnętrznych
Obawy związane z mockowaniem Mocki będą ciężkie w utrzymaniu Mocki nie pozwolą nam na dokładne przetestowanie systemu Nie sprawdzimy czy nasza baza danych funkcjonuje poprawnie Nie sprawdzimy czy nasze usługi funkcjonują poprawnie
Typy mocków
Stuby (własnoręcznie wyrzeźbione klasy)
Stuby (własnoręcznie wyrzeźbione klasy) Nie mamy łatwego sposobu aby dostosowywać ich zachowanie pomiędzy testami Dodajemy niepotrzebną warstwę złożoności do systemu, którą musimy utrzymywać Tworzymy zbędny kod, który potem musimy utrzymywać
Expect - Run Verify (Record - Replay)
Expect - Run Verify (Record - Replay) Test jest podzielony na trzy części: Utworzenie mocka i nagranie oczekiwań (stubujemy) Uruchomienie mocka Weryfikacja jego zachowania Przez co: Praktycznie stubujemy Zawczasu musimy myśleć o wewnętrznej implementacji Dodajemy wiele niepotrzebnych linii kodu
Arrange - Act - Assert
Arrange - Act - Assert Test jest podzielony na trzy części: Utworzenie mocka Wykonanie testu Opcjonalna weryfikacja wykonanych metod Eliminuje to parę problemów: Nie musimy myśleć o szczegółach przed implementacją Nie musimy stubować metod nic nie zwracających Ograniczamy ilość potrzebnego kodu, zajmujemy się tylko tym co istotne
Przypomnijcie mi bo nie pamiętam, co to jest? Stub Record Replay Arrange - Act - Assert
Część V: Mockito
Co to jest Mockito? Mockito to framework do mockowania napisany w Javie przez Szczepana Fabera wraz z przyjaciółmi.
We decided during the main conference that we should use JUnit 4 and Mockito because we think they are the future of TDD and mocking in Java" Dan North
Dlaczego właśnie Mockito? Jest trendy, wszyscy chcą jej teraz używać :-) Bardzo prosta w użyciu Posiada minimalistyczne API Umożliwia wybiórczą weryfikację wywołań metod Wyraźnie odseparowuje deklaracje zachowań od ich weryfikacji Nie posiada kodu wspierającego framework record(), replay() Umożliwia mockowanie zarówno klas i interfejsów
import static org.mockito.mockito.*; mock(java.lang.class<t> classtomock) when(t methodcall) verify(t mock) spy(t object) doreturn(java.lang.object tobereturned) dothrow(java.lang.throwable tobethrown) doanswer(answer answer) times(int wantednumberofinvocations) atleast(int minnumberofinvocations) atmost(int maxnumberofinvocations)