JUnit 5
to framework nowej generacji do tworzenia automatycznych testów jednostkowych w technologii Java, oferujący wiele ciekawych funkcji, takich jak np. testy parametryzowane, wstrzykiwanie zależności, całkowite wsparcie dla Javy 8, czy w końcu nowe API, umożliwiające łatwiejsze niż kiedykolwiek rozszerzanie możliwości frameworka i dostosowanie go do potrzeb projektowych.
Frameworki takie jak JUnit 5
wykorzystywane są dzisiaj nie tylko w testach jednostkowych. Mają również szerokie zastosowanie w testach wyższego poziomu, w tym w testach funkcjonalnych z użyciem takich bibliotek jak Selenium WebDriver
czy REST Assured
.
W tym wpisie przedstawię najważniejsze cechy JUnit 5
, dzięki czemu nauczysz się samodzielnie przygotować projekt w oparciu o ten framework.
Dokumentacja JUnit 5
Dokumentacja JUnit 5
jest wyjątkowo dobrze napisana. Nie tylko zawiera szczegółową dokumentację frameworka, ale również wiele przykładów, które możesz wykorzystać tworząc swój projekt. To strona, która definitywnie powinna znaleźć się w Twoich ulubionych, jeśli zamierzasz uczyć się, a później korzystać z JUnit 5
:
http://junit.org/junit5/docs/current/user-guide/
Przygotowanie środowiska
Do pracy z projektem opartym o JUnit 5
koniecznie jest środowisko z zainstalowanym Java Development Kit
w wersji co najmniej 8 (JDK 8
), zintegrowane środowisko programistyczne (Intellij IDEA Community
lub Ultimate
) oraz opcjonalnie system kontroli wersji Git
.
Tip: Jeżeli pracujesz w systemie Windows 10, zastanów się nad użyciem menadżera pakietów takiego jak Chocolatey, który umożliwia zarządzanie wszystkimi aspektami związanymi z oprogramowaniem: instalacją, konfiguracją, aktualizacją i odinstalowaniem. Wystarczy kilka komend, aby przygotować środowisko z aktualnymi wersjami oprogramowania.
Jeśli chcesz, możesz skorzystać z opisu konfiguracji środowiska w systemie Windows
, opracowanych na potrzeby projektu automatyzacji testów aplikacji internetowej z wykorzystaniem Selenium
. Wiele spośród zawartych tam porad można zastosować również w opisywanym tutaj podstawowym projekcie:
- Narzędzia - system kontroli wersji Git i emulator konsoli Cmder
- Narzędzia - Selenium i przeglądarki internetowe
- Narzędzia - Java, Gradle i IntelliJ
Jeśli używasz systemu macOS
, możesz skorzystać ze wskazówek dotyczących przygotowania środowiska zebranych w tym artykule:
Konfiguracja projektu
JUnit 5
składa się z kilku artefaktów zgrupowanych w JUnit Platform
, JUnit Juipter
i JUnit Vintage
- w sumie z kilku bibliotek (architektura modularna), które wymagane są do prawidłowej kompilacji i uruchamiania projektów.
Pobierz junit5-gradle-template z repozytorium
Aby uprościć proces tworzenia projektu, przygotowałem gotowy do ściągnięcia szkielet aplikacji. Zaletą tego podejścia jest brak konieczności instalacji Gradle
. Projekt zawiera tzw. Gradle Wrapper
, który lokalnie zainstaluje zależności Gradle wymagane do jego uruchomienia.
git clone
Jeżeli masz zainstalowany system kontroli wersji Git
, wystarczy sklonować repozytorium:
git clone https://gitlab.com/qalabs/blog/junit5-gradle-template.git
Tip: Dla systemu Windows istnieje sporo interesujących emulatorów konsoli, które ułatwiają codzienną pracę z wierszem poleceń. Warto zainteresować się takimi narzędziami, jak cmder lub Babun.
Nie masz Gita?
Pobierz ZIP
ze strony projektu: https://gitlab.com/qalabs/blog/junit5-gradle-template i rozpakuj go na swoim komputerze.
Pierwsze budowanie projektu
Używając wiesza poleceń, przejdź do katalogu projektu (junit5-gradle-template
) i wykonaj polecenie:
gradlew clean test
Komenda skompiluje kod projektu i uruchomi testy z użyciem Gradle
. Jeśli wszystko zostało poprawnie skonfigurowane, powinieneś zobaczyć podsumowanie podobne do tego jak poniżej:
1 | Starting a Gradle Daemon (subsequent builds will be faster) |
Gdy utworzysz więcej testów, możesz używać powyższej komendy do wykonywania ich wsadowo (czyli właśnie z użyciem wiersza poleceń).
Import projektu do IntelliJ
Aby zaimportować projekt w IntelliJ, wybierz opcję File | Open
i wskaż plik gradle.build
. Otwórz plik jako projekt i poczekaj chwilę, aż zaimportuje się wraz ze wszystkimi zależnościami.
Wprowadzenie do JUnit 5
Zależności
Konfiguracja zależności znajduje się w pliku build.gradle
. Najważniejsze z nich to plugin JUnit Platform
dla Gradle
, pozwalający uruchamiać testy, oraz Jupiter Engine
i Jupiter API
.
Tip: Przeczytaj więcej o definiowaniu zależności w
Gradle
: https://docs.gradle.org/4.5.1/userguide/artifact_dependencies_tutorial.html
Podstawowe adnotacje
Podstawowe adnotacje używane w testach pochodzą z pakietu org.junit.jupiter.api
i są to:
@BeforeAll
- metoda oznaczona tą adnotacją będzie wykonana przed wszystkimi innymi metodami w klasie@BeforeEach
- metoda oznaczona tą adnotacją będzie wykonana przed każdym kolejnym testem@Test
- właściwa metoda testowa@AfterEach
- metoda oznaczona tą adnotacją będzie wykonana po każdym kolejnym teście@AfterAll
- metoda oznaczona tą adnotacją będzie wykonana po wszystkich innych metodach w klasie
Inne przydatne adnotacje:
@DisplayName
- pozwala na dostosowanie wyświetlanej nazwy testu@Disabled
- wyłącza test@RepeatedTest
- wykonuje test konfigurowalną liczbę powtórzeń@Tag
- pozwala na tagowanie testów
Podstawowy przykład:
1 | import org.junit.jupiter.api.*; |
Test możesz uruchomić bezpośrednio z IntelliJ lub możesz użyć podanej wcześniej komendy gradlew clean test
.
Cykl życia instancji testu (@TestInstance
)
W JUnit 5
domyślnie dla każdej metody testowej w klasie testowej zostaje utworzony nowy obiekt tej klasy. To tak zwany cykl życia per metoda (w nomenklaturze JUnit 5
to Lifecycle.PER_METHOD
). Nie mniej, cykl życia instancji testu może zostać zmieniony na cykl życia per klasa (Lifecycle.PER_CLASS
) za pomocą adnotacji @TestIntance
. W trybie PER_CLASS
pojedyncza instancja testu jest tworzona dla każdej metody testowej, a metody oznaczone adnotacjami@BeforeAll
i @After
nie muszą być statyczne.
Daje to bardzo ciekawe możliwości, szczególnie w testach, gdzie każda metoda testowa musi mieć dostęp do tej samej instancji danego obiektu. Takim obiektem może być np. instancja org.openqa.selenium.WebDriver
w Selenium
, gdzie raz zainicjowany sterownik do wybranej przeglądarki powinien być wykorzystany przez wszystkie testy w klasie testowej.
Przykład:
1 | import org.junit.jupiter.api.*; |
Konfiguracja cyklu życia może być również zmieniona globalnie dla wszystkich testów. Jednym ze sposobów jest użycie konfiguracji z pliku junit-platform.properties
:
junit.jupiter.testinstance.lifecycle.default = per_class
Wstrzykiwanie parametrów do testów
W JUnit 5 metody testowe oraz inne metody klasy testowej mogą przyjmować parametry typu org.junit.jupiter.api.TestInfo
, org.junit.jupiter.api.RepetitionInfo
czy org.junit.jupiter.api.TestReporter
. Dodatkowo, dzięki bardzo prostemu Extension API
, definiowanie własnych typów parametrów w JUnit 5
staje się bardzo proste.
Zwróć uwagę na metody przyjmujące parametry. Te parametry są wstrzykiwane automatycznie podczas uruchomienia testu i dają programiście dodatkowe metody, z których może korzystać:
1 | class JUnit5BuiltInParameterResolution { |
Poza obiektem typu TestReporter
, który daje możliwość publikowania wpisów do raportu z testów, pozostałe obiekty wbudowane (TestInfo
, RepetitionInfo
) mają raczej niewielkie zastosowanie w praktyce.
Nie mniej, jeżeli rozszerzymy JUnit 5
i pozwolimy mu na wstrzykiwanie własnych zależności, zastosowań praktycznych będzie znacznie więcej. Możliwe zastosowania to na przykład wstrzykiwanie obiektów konfiguracyjnych w testach oraz wstrzykiwanie obiektów dostarczających dane testowe.
Info: Rozszerzanie
JUnit 5
z użyciemExtension API
jest poza zakresem tego artykułu. Jeżeli jesteś zainteresowany, to znajdziesz informacje na ten temat w dokumentacjiJUnit 5
.
Asercje
Asercje w testach służą do weryfikacji zachowania lub stanu testowanego systemu (SUT - System Under Test
). Niepowodzenie asercji kończy się w JUnit 5
wyjątkiem, który przerywa wykonanie aktualnego testu.
JUnit 5 dostarcza wiele wbudowanych asercji, których należy szukać w klasie org.junit.jupiter.api.Assertions
.
Tip: Oprócz wbudowanych asercji, możesz też korzystać z bibliotek zewnętrznych. Polecam szczególnie
AssertJ
. Więcej o integracjiAssertJ
iJUnit 5
możesz przeczytać tutaj: JUnit meets AssertJ
Asercje podstawowe
Do podstawowych asercji należą: assertEquals
, assertArrayEquals
, assertSame
, assertNotSame
, assertTrue
, assertFalse
, assertNull
, assertNotNull
, assertLinesMatch
, assertIterablesMatch
Przykład:
1 | import static org.junit.jupiter.api.Assertions.assertNotNull; |
Assert all
Assertions.assertAll
weryfikuje, czy żadna z grupy asercji nie kończy się wyjątkiem:
1 |
|
Zwróć uwagę na wykorzystanie wyrażeń Lambda
w przykładzie. Wyrażenia Lambda
pozwalają na tworzenie instancji klas anonimowych, implementujących interfejs z jedną metodą w bardzo zwięzły sposób. Pozwalają one na traktowanie funkcjonalności jako argumentu do metody.
W powyższym przykładzie do metody assertAll
przekazywane są dwa argumenty, które są anonimowymi implementacjami interfejsu org.junit.jupiter.api.function.Executable
, wyrażonymi za pomocą wyrażenia Lambda.
Tip: Przeczytaj więcej o wyrażeniach
Lamda
tutaj: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
Zasada działania assertAll
jest dość prosta: metoda weryfikuje, czy żadna z przekazanych do tej metody asercji nie zakończy się błędem. Co istotne, w przypadku assertAll
wszystkie asercje przekazane jako argumenty zostaną wykonane, nawet jeśli jedna z pierwszych da wynik negatywny, a jeśli choć jedna zakończy się wyjątkiem, to cały test zakończy się błędem.
Dla powyższego przykładu zaraportowany zostanie następujący błąd:
1 | org.opentest4j.MultipleFailuresError: Multiple Failures (2 failures) |
Funkcjonalność ta może mieć szerokie zastosowanie w testach z wykorzystaniem Selenium
, gdzie zatrzymanie testu w wyniku pierwszego napotkanego błędu może spowodować, że inne problemy nie zostaną wykryte, a ponowne uruchomienie testów w celu weryfikacji pozostałych asercji może zająć dużo czasu.
Podsumowanie
JUnit 5
oferuje jeszcze wiele innych funkcji, takich jak choćby weryfikacja czasu wykonania metod, testy parametryzowane, czy znakomite Extension API.