Testerzy automatyzujący często spotykają się z niestabilnymi testami. Nie jest łatwo uniknąć tego zjawiska, problematycznym czynią je również trudności z naprawą tego typu testów. Zanim jednak przejdziemy do przyczyn ich powstawania, przyjrzyjmy się definicji niestabilnego testu.
Test niestabilny — test jest uważany za niestabilny (ang. flaky test), kiedy może raz przejść a przy kolejnych powtórzeniach nie przejść, mimo żadnych zmian w kodzie.
Na przykład wykonanie testu kończy się niepowodzeniem, a podczas ponownego wykonania, bez zmiany kodu, test udaje się przeprowadzić.
Niestabilne testy mają istotny wpływ na proces ulepszania jakości oprogramowania. A ich wpływ na postępy w projekcie widać między innymi w następujących obszarach:
Jeśli mamy testy, które raz nam przechodzą, a raz nie przechodzą, to w pewnym momencie nie wiemy, czy ten test nie przeszedł z powodu błędu w kodzie źródłowym aplikacji, czy był to po prostu flaky test, który przy kolejnym uruchomieniu zapewne zakończy się wynikiem pozytywnym.
W efekcie musimy ponownie uruchamiać testy na CI lub uruchamiać cały build aplikacji, gdzie jednym z ostatnich kroków jest przeprowadzanie testów, które pozwolą ponownie sprawdzić, czy były to błędy w aplikacji, czy problem z testami.
Jest to jeszcze bardziej dotkliwe, gdy korzystamy z usług chmurowych, gdzie jesteśmy rozliczani z czasu i wykorzystanych zasobów: kilkukrotne uruchomienie tych samych testów na tej samej wersji zwiększa koszt takiej operacji.
Niestabilne testy absorbują bardzo dużo uwagi i czasu testerów automatyzujących, ponieważ musi on sprawdzić, czy poprawiony test zawsze przechodzi, czy tylko czasami, ale częściej niż poprzednio.
Dodatkowo musi to zostać zweryfikowane na CI na docelowej maszynie i środowisku uruchomieniowym.
Przy dużej ilości niestabilnych testów nie wiemy, czy duża ilość „czerwonego” oznacza, że mamy dużo błędów czy po prostu flaky testy nie przeszły tym razem. Przez to wzrasta obojętność na czerwone wyniki i takie rezultaty są brane coraz mniej serio i zaczynają padać stwierdzenia: że te błędy to pewnie niestabilne testy, które znowu nie przeszły.
Dlatego bardzo istotne jest, aby testy zwracały jasny komunikat: albo przechodzą pozytywnie, albo jest błąd w aplikacji i testy w związku z tym nie przechodzą.
Kolejnym problemem związanym z flaky testami jest to, że podczas wykonywania niestabilnego testu prawdziwy błąd pozostanie niewychwycony, ponieważ zostanie ukryty pod flaky testami.
Kilka zrzutów ekranu z runnera Cypressa i błędami związanymi z Document Object Modelem:
Podstawowym problemem z niestabilnymi testami, kiedy przyczyny są związane z siecią, są wolne odpowiedzi API, które sprawiają, że elementy nie ładują się w zadanym czasie.
Kolejnym problemem są wolne odpowiedzi od systemów zależnych np. do płatności, obsługi logowania czy wyświetlania elementów osadzonych na stronie.
Zdarza się również, że niestabilność w testach jest spowodowana przez „zimny start serwisu” – gdzie pierwsze zapytanie jest wolniejsze i nie mieści się w zadanym maksymalnym czasie, natomiast wszystkie kolejne są już odpowiednio szybkie.
Kolejną grupą przyczyn niestabilności testów są problemy ze środowiskiem. Jednym z częstszych problemów są przekierowania load balancera na różne maszyny docelowe. Jeśli ruch będzie przekierowany raz na wydajniejszą maszynę, to testy przejdą pozytywnie, innym razem load balancer może przekierować na wolniejszą maszynę, gdzie testy nie zakończą się powodzeniem.
Test runy muszą być od siebie odizolowane i nie wchodzić sobie w drogę, aby nie doszło do zjawiska, gdzie jeden test zmienia dane/parametry, które są wykorzystywane przez drugi test.
Jest to jedna z najbardziej podstawowych zasad, o jakich trzeba pamiętać, aby zapewnić stabilność testów.
Np. jeżeli mamy różne zespoły działające nad jednym produktem i zespoły puszczają testy w tym samym czasie, wtedy może dojść do „kolizji” i losowych niepowodzeń w testach.
Wprawdzie jest to jedna z podstawowych dobrych praktyk tworzenia testów automatycznych, jednak niestety nie każdy tester o niej pamięta. Wciąż istnieją projekty, w których zespół zmaga się z testami, które nie przechodzą. Wcale nie tak rzadko okazuje się, że są one napisane tak, że kolejne testy są zależne od poprzednich.
Jest to najważniejsza i najczęściej powtarzana zasada. Nigdy nie używaj cy.wait(), ponieważ:
Używanie statycznie zadeklarowanego oczekiwania jest złym podejściem i nie jest zgodne z dobrymi praktykami programowania!
Użycie cy.wait(liczba) powoduje stałe spowolnienie testów:
Można to wykorzystać do takiego stworzenia testów, aby były bardziej stabilne.
Jeżeli przy asercji tekstu znajdzie element np. h1, to potem będzie próbował porównać teksty na znalezionym elemencie. Jeżeli element zmieni się w międzyczasie, to nie będzie to wzięte pod uwagę i asercja będzie robiona na pierwszej znalezionej wersji.
a) nie używać łączenia ze sobą komend
b) wykorzystywać komendy, które łączą w sobie potrzebne komendy i całość jest odświeżana np. używanie alternatywnych asercji z should
Przykład:
Pozwala to na oczekiwanie na wykonanie się wolnego zapytania sieciowego.
Zdarzają się testy, które wymagają dużo czasu na ich przepisywanie od nowa. Bywa też, że mamy do poprawienia bardzo dużą ilość testów.
Co możemy wtedy zrobić z naszymi niestabilnymi testami?
Najprostszym i najszybszym sposobem jest ustawienie automatycznego, ponownego ich uruchamiania. W Cypressie od wersji 5.0 posiadamy natywne wsparcie tego i jest to bardzo proste i szybkie w implementacji.
Do pliku cypress.json dodaj:
Można zdefiniować ilość powtórzeń dla różnych rodzajów uruchomień testów:
Poza globalnymi ustawieniami można ustawić powtarzalność dla pojedynczych testów:
Niestabilne testy można również zebrać do Test Suitów i nadawać im odpowiednio parametry powtórzeń: