W arsenale przykładowych projektów, które wykorzystuję podczas szkoleń, mam projekt pytest-intro, który zawiera pięć bardzo prostych testów w trzech scenariuszach testowych. Ostatnio podczas zajęć na uczelni zorientowałem się, że jeden z testów zawiera nieprawidłowy kod, który w przypadku błędnego działania testowanego serwisu może doprowadzić do błędnego wykonania testu (test error) zamiast do błędnego wyniku testu (test failure). Czym to się różni? Dobrze jest uczyć się na błędach, a najlepiej na cudzych, więc spieszę opisać wspomniany przypadek ze szczegółami.
Testy zaimplementowane w projekcie pytest-intro
weryfikują spójność danych zapisanych w pliku CSV
, działanie REST API
serwisu GitLab
oraz podstawową konfigurację aplikacji internetowej. Błąd znajdował się w jednym z testów dla API
. Przewidywał on zweryfikowanie, czy w przesłanych w odpowiedzi danych w formacie JSON
znajduje się niepusty element stats
. Błędna implementacja była następująca:
1 | import pytest |
Gdzie jest błąd?
Błąd znajduje się w ostatniej linii powyższego kodu. Dane zwrócone przez fixture json_content
są w postaci słownika, więc sprawdzenie assert json_content["stats"]
będzie prawdą i test zakończy się poprawnym wynikiem tylko jeśli element stats
będzie istniał i będzie zawierał niepuste dane (o wartości logicznej True
). Jeśli element stats
będzie zawierał puste dane (o wartości logicznej False
, na przykład pusty ciąg znaków ""
), to test zakończy się błędnym wynikiem, to znaczy pojawi się AssertionError
. Ale zaraz… Jest jeszcze trzecia możliwość. Co jeśli elementu stats
nie będzie w ogóle?
Wystarczy w danych payload
zamienić wartość with_stats
na False
żeby przekonać się, że test zakończy się wówczas błędem wykonania w postaci KeyError
. Stanie się tak dlatego, ponieważ w słowniku zawierającym json_content
nie będzie wówczas w ogóle elementu o kluczu stats
i kod próbujący odczytać wartość dla tego klucza zakończy się błędem:
Jak ten kod ulepszyć?
Należy użyć metody słownika get()
, która również odczytuje wartość dla podanego klucza, ale w przypadku braku klucza w słowniku nie kończy się błędem, a zwraca alternatywną wartość, którą domyślnie jest None
. Czyli kod assert json_content.get("stats")
będzie fałszem zarówno wówczas, kiedy element stats
będzie zawierał puste dane, jak i wtedy, kiedy nie będzie go wcale, bo metoda get()
zwróci wówczas None
. Wtedy test zakończy się błędnym wynikiem w postaci AssertionError
:
W praktyce automatyzacji testów zawsze powinniśmy dążyć do tego, aby w dających się przewidzieć błędnych sytuacjach testy kończyły się błędnym wynikiem (test failure), to znaczy błędem asercji. Test zakończony błędem wykonania (test error) świadczy o wystąpieniu pewnych niespodziewanych okoliczności, które uniemożliwiły wykonanie właściwego testu, co z natury rzeczy wymaga dodatkowej analizy i niepotrzebnie marnuje nasz czas.