Ponoć kiedy kod jest już napisany nie da się stwierdzić, czy był pisany w modelu TDD, a jedynym sposobem na to, jest patrzeć programiście na ręce. Zasadniczo prawda, jednak jest kilka śladów zbrodni, które pozostają. Jednym z nich są testy sprawdzające zbyt wiele rzeczy na raz i zakładające, że czytelnik posiada wiedzę tajemną. Tak jak poniżej.
Albo tak: test_csv_export_correct
, ale nie bądźmy aż tak okrutni 😉.
Jeśli to wygląda dla Ciebie legitnie, to proszę… nie przestawaj czytać, bo jest z tym kilka problemów.
Test „czy czy jest dobrze”
Przede wszystkim – co, do diaska, znaczy “all”?
Po tym wiem, że kodu nie napisano w modelu TDD. Nie zrozumcie mnie źle – “test first” jest tu całkowicie prawdopodobne. Ostatecznie nic nie powstrzyma Cię przed napisaniem takiego testu przed implementacją, ale TDD to nie będzie.
Dla przypomnienia: zamysłem programowania sterowanego testami jest by zawsze pisać najmniejszy nieprzechodzący test, który nadal przekazuje jakość informację (czyli assert False
się nie liczy). Każde z 20 pól może nie istnieć i każde może mieć błędną wartość, więc mamy co najmniej 40 sposobów, na które ten test może nie przejść. Czyli za dużo się tu dzieje.
Ale jest z tym większy problem. Nie mogło to sterować programowaniem, bo nic to nie mówi o strukturze wymaganej CSVki. Zestaw testów w TDD jest specyfikacją – zestawem hipotez stawianych względem kodu.
Ktoś by się kłócił, że nazwa bez znaczenia, bo asercje mówią wszystko. Zgoda, ale skąd wiemy, że są poprawne…?
Kto pilnuje pilnujących?
Witamy odwieczne pytanie – jak testować testy? No testować się nie da, ale można je zwalidować porównując ciało z nazwą, a żeby się dało, to nazwa musi zawierać informacje. To jest tym ważniejsze, kiedy kod ewoluuje w czasie, bo nikt nie ruszy testu, który reklamuje się jako sprawdzający wszystko. Wszystko to wszystko, po co drążyć temat?
Więc jak powinno to wyglądać? Cóż, na początek każde pole powinno być testowane osobno. W zasadzie, każde założenie dotyczące każdego z pól powinno być testowane osobno, skąd wzięła się mantra “jeden test – jedna asercja”. Jest ona, niestety, trochę myląca, bo w praktyce jedno, niepodzielne założenie może wymagać 2 czy 3 asercji, zależnie od języka programowania, jednak kiedy masz ich 20 to coś tu śmierdzi.
Otrzymujemy więc coś takiego:
Zauważ jedną rzecz: nie potrzebujesz ani kawałka ciała testu, żeby załapać o co w nim chodzi. Nazwa w zupełności wystarczy. To znaczy, że masz jak sprawdzić, czy ciało testu zgadza się z intencją.
Możesz się też sporo dowiedzieć o architekturze i kontraktach. Przykładowo, system mądrze używa dedykowanego UUID, zamiast ID z bazy, do komunikacji ze światem. Po drugie, imię powinno być napisane dużą literą, więc lepiej żeby była na to asercja!
Nie wszystko na raz
Najczęściej wątpliwości względem takiego schematu wynikają z nietestowania wszystkiego jednocześnie, więc nie wiadomo czy te założenia są spełnione razem. Ale czy faktycznie? To jak powiedzieć, że all(foo)
to nie to samo co foo[0] and foo[1] and … and foo[n]
.
Tym niemniej, musisz zapewnić, że input (inaczej given, inaczej arrange) i operacja (inaczej when, inaczej act) będą te same we wszystkich przypadkach. Wtedy deterministyczna natura komputera załatwi resztę.
Tu właśnie przydają się fikstury i setUp
. Jednocześnie jest to chyba jedyny przypadek, kiedy DRY ma sens w testach – kiedy musisz upewnić się, że warunki początkowe i operacje będą spójne podczas testowania różnych założeń.
Użyjmy więc fikstury w pytest
:
Rozdziały w testach, nie tylko w książkach
Już jest nieźle, ale kontynuujmy refactoring. Pewnie zauważasz, że mamy tu dwa schematy w nazwach. Pogrupujmy więc teksty zgodnie z tymi schematami:
Uwaga: Celowo psuję tutaj PEP8 i reguły długości linii i mam nadzieję, że za chwilę stanie się jasne dlaczego pomaga to zwiększyć czytelność testów, które rządzą się nieco innymi prawami niż klasy i metody, które odpalamy w kodzie.
Słyszę Cię, pyteście!
Teraz zobaczmy jak by wyglądało wywołanie jednego z testów pytest
em:
$ pytest test_csv_export.py::Test_field::test_id_contains_user_external_uuid
Pomijając obligatoryjny prefiks test_
i kilka zignorowanych reguł gramatycznych, otrzymujemy niemalże poniższe zdanie:
CSV export field ID contains the user’s external UUID
Co jednak ważniejsze, tak samo będzie to wyglądało w “głośnym” wypisie z odpalenia testów, więc od razu dowiesz się co konkretnie nie działa, bez konieczności patrzenia na linie kodu czy wstawiania breakpointów do testu. Czyż to nie cudowne?
Testy jako dokumentacja
Podsumowując, jeśli chcesz mieć opisowe, pomocne i samo-weryfikujące zestawy testów, to upewnij się, że ich nazwy są konkretnymi hipotezami. Unikaj ogólnikowych słów w rodzaju “all”, “complete” czy “correct”. I nie martw się, że nie testujesz wszystkiego w ramach jednej funkcji – determinizm się tym zajmie.