Testy powtarzane (ang. repeated tests) pozwalają wykonywać ten sam przypadek testowy wielokrotnie i są szczególnie przydatne na przykład gdy chcemy zweryfikować, czy dla tych samych parametrów wejściowych system zwraca taki sam rezultat niezależnie od ilości wywołań.
W przykładzie wykorzystam mechanizm testów powtarzanych do stworzenia kilku zadań na liście, a następnie usunę te zadania za pomocą metody testowej, która zostanie wykonana jako ostatnia metoda w klasie testowej. Przykład ten wybiega poza klasyczne użycie testów powtarzanych, ale pozwoli na zademonstrowanie dodatkowych mechanizmów dostępnych w JUnit 5, takich jak zmiana cyklu życia klasy testowej oraz określenie kolejności wykonania metod testowych w klasie testowej.
O serii
Czytasz część 6 serii artykułów na temat automatyzacji testów aplikacji internetowej TodoMVC. Pozostałe części:
- Część 1 - JUnit 5 i Selenium - Wprowadzenie do projektu automatycznych testów aplikacji internetowej
- Część 2 - JUnit 5 i Selenium - Odpowiedzialność metod testowych
- Część 3 - JUnit 5 i Selenium - Wprowadzenie do wzrorca Page Object
- Część 4 - JUnit 5 i Selenium - Optymalizacja konfiguracji i uruchomienia projektu
- Część 5 - JUnit 5 i Selenium - klasa PageFactory z pakietu support biblioteki Selenium
Kod źródłowy
Kod źródłowy opracowany w poprzednim artykule znajduje się w repozytorium Git
: https://gitlab.com/qalabs/blog/junit5-selenium-todomvc-example w branchu 5-selenium-page-factory
. Zmiany opisywane w tym artykule znajdują się w pakiecie pl.qalabs.blog.junit5.selenium.todomvc
projektu, w branchu 6-repeated-tests
.
Scenariusz zarządzania zadaniem
Przygotowanie scenariusza testowego rozpocznę od stworzenia metody testowej manageTodo()
, która będzie obejmowała następujące kroki:
- Utworzenie zadania
- Zmiana nazwy zadania
- Zakończenie zadania
- Pokazanie listy aktywnych zadań
- Pokazanie listy ukończonych zadań
- Powrót na listę wszystkich zadań
Dzięki wykorzystaniu wzorca Page Object
, utworzenie testu nie stanowi problemu:
1 | class ManageTodoTest { |
Więcej na temat wzorca
Page Object
znajdziesz w poprzednich artykułach serii: JUnit 5 i Selenium - Wprowadzenie do wzrorca Page Object oraz JUnit 5 i Selenium - klasa PageFactory z pakietu support biblioteki Selenium.
Testy powtarzane (@RepeatedTest
)
Powyższy scenariusz można bardzo łatwo wykonywać wielokrotnie, wystarczy dla każdego kolejnego wykonania zmienić nazwę zadania oraz w jakiś sposób zapisywać jego aktualny numer. W tym celu możemy użyć wbudowanego w JUnit 5 mechanizmu testów powtarzanych. Aby stworzyć taki test, wystarczy zamienić adnotację @Test
na @RepeatedTest
, podając przy tym liczbę powtórzeń jako jej atrybut value
:
1 |
|
Natomiast aby śledzić aktualny stan dla testów powtarzanych, a dokładnie numer powtórzenia oraz liczbę powtórzeń, możemy skorzystać z obiektu RepetitionInfo
, który wstrzykujemy do metody testowej jako parametr. Obiekt repetitionInfo
dostarcza dwie metody: getCurrentRepetition()
oraz getTotalRepetitions()
, zwracające odpowiednio aktualny numer wykonania testu oraz całkowitą liczbę wykonań.
1 |
|
Rezultat wykonania testów:
1 | $ ./gradlew test --tests ManageTodoTest |
Nietrudno zauważyć, że dla każdego powtórzenia testu została utworzona nowa metoda testowa zawierająca wygenerowaną nazwę testu.
Dostosowanie nazwy wygenerowanych metod testowych
Nazwę testu powtarzanego można dostosować do własnych potrzeb. Wystarczy użyć atrybutu name
adnotacji @RepeatedTest
, tak jak zostało to pokazane poniżej:
1 |
|
Na dzień pisania tego artykułu w przedstawionym mechanizmie występuje błąd, który powoduje, że nazwy testów wypisywane w konsoli (przez ConsoleLauncher) są nieprawidłowe. Natomiast zarówno w IDE, jak i w wygenerowanym raporcie HTML, nazwy przedstawiane są prawidłowo.
Poniżej fragment raportu HTML (build/reports
):
…oraz zrzut ekranu z Intellij:
Zmiana cyklu życia klasy testowej (@TestInstance
)
Łatwo zauważyć, że czas wykonania testów jest dość długi. Dzieje się tak, ponieważ przed każdym testem inicjalizujemy nową instancję sterownika, co powoduje uruchomienie nowej instancji przeglądarki dla każdego testu. Możemy to zmienić inicjalizując sterownik tylko raz - przed wszystkimi testami. W tym celu musimy przenieść kod z metod @BeforeEach
i @AfterEach
odpowiednio do metod @BeforeAll
i @AfterAll
, które domyślnie muszą być metodami statycznymi. To spowoduje konieczność dalszych modyfikacji - pola instancyjne driver
i page
wówczas również muszą być statyczne. Wynika to z faktu, że JUnit 5 dla każdego testu tworzy nową instancję obiektu klasy testowej.
Jest to domyślny cykl życia testu w JUnit 5, który można dość łatwo zmienić używając adnotacji @TestInstance
na klasie testowej:
1 |
|
Cykl życia testu dla wszystkich testów można zmienić dodając do pliku konfiguracyjnego
junit-platform.properties
zmiennąjunit.jupiter.testinstance.lifecycle.default = per_class
lub za pomocą zmiennj systemowej o tej samej nazwie.
Kolejnym krokiem będzie modyfikacja metody testowej, tak aby wykorzystywała parametr repetitionInfo
w celu dynamicznego tworzenia nazwy zadania na liście:
1 |
|
Warto zauważyć, że po uruchomieniu powyższego testu zostaną utworzone trzy zadania i wszystkie zostaną oznaczone jako zakończone, ale żadne z nich nie zostanie usunięte. Wynika to z faktu, że sterownik, jak również obiekt strony, tworzone są tylko raz - przed uruchomieniem wszystkich testów.
W celu usunięcia zadań po testach moglibyśmy wykorzystać metodę @AfterEach
lub @AfterAll
(w zależności od potrzeb). Możemy też utworzyć test usuwający wszystkie zakończone zadania, który będzie działał następująco:
- Uruchomi się tylko, gdy na liście istnieją zadania (
assumeTrue()
) - Uruchomi się dokładnie po wykonaniu testów
manageTodo()
Implementująca te założenia metoda testowa removeAllCompletedTodos()
została dodana jako ostatnia metoda do klasy testowej:
1 |
|
Po uruchomieniu testów okazuje się jednak, że nowoutworzona metoda testowa nie została wykonana:
1 | $ ./gradlew test --tests ManageTodoTest |
Metoda removeAllCompletedTodos()
została wybrana do uruchomienia jako pierwsza, ale jej wykonanie zostało pominięte, ponieważ na liście nie było wówczas żadnego zadania. Stało się tak, ponieważ kolejność wykonywania testów w JUnit 5 jest ustalana przez framework i nie wynika z kolejności deklaracji metod testowych w klasie testowej.
Domyślna kolejność wykonywania testów między buildami w JUnit 5 jest powatrzalna, zatem deterministyczna, jednak algorytm celowo jest nieoczywisty, o czy wspominają autorzy biblioteki.
Kolejność wykonywania metod testowych (@TestMethodOrder
)
Kolejność wykonywania metod testowych w klasie testowej można określić samemu, oznaczając klasę adnotacją @TestMethodOrder
, podając jako argument typ sortowania metod. W naszym przykładzie możemy użyć sortowania za pomocą adnotacji @Order
:
Więcej o kolejności wykonywania metod testowych w JUnit 5 dowiesz się z mojego artykułu na blogu codeleak.pl: Test Execution Order in JUnit 5.
1 |
|
Dzięki powyższemu rozwiązaniu mamy gwarancję, że kolejność wykonywania metod testowych będzie zgodna z oczekiwaniami.
1 | $ ./gradlew test --tests ManageTodoTest |
Warto pamiętać, że dobre praktyki tworzenia testów automatycznych wskazują na konieczność zachowania niezależności metod testowych - bez względu na kolejność uruchomienia testy powinny działać zawsze tak samo. Nie mniej, są sytuacje, w których kontretna kolejność może mieć swoje uzasadnienie, a decydując się na takie rozwiązanie, zawsze należy rozważyć za i przeciw.
Podsumowanie
Tworzenie testów powtarzanych sprowadza się do użycia adnotacji @RepeatedTest
. Tego typu testy mogą być przydatne na przykład, gdy chcemy zweryfikować, czy dla tych samych parametrów wejściowych system zwraca taki sam rezultat niezależnie od ilości wywołań. Mechanizm tworzenie testów powtarzanych w połączeniu z innymi mechanizmami biblioteki JUnit 5 pozwolił na stworzenie prostego testu parametryzowanego.
Szerzej o tworzeniu testów parametryzowanych przeczytasz w kolejnym artykule.
Repozytorium Git projektu
Kod źródłowy opracowany w artykule znajduje się w repozytorium Git
: https://gitlab.com/qalabs/blog/junit5-selenium-todomvc-example w pakiecie pl.qalabs.blog.junit5.selenium.todomvc
, w branchu 6-repeated-tests
.