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.