W sumie to znalazłem ciekawą lekturę na dokładnie ten temat.
Co do umiejscowienia testów - jak dla mnie grunt, żeby nie były upchane razem z kodem źródłowym. Powiedzmy, że kod źródłowy aplikacji jest jakimś podkatalogu projektu src
- zatem na pewno nie chciałbym zobaczyć testów w src
. To gdzie w takim razie? Na przykład w sąsiednim katalogu test
. Fajnie by było również, gdyby struktura testów odzwierciedlała w jakiś sposób strukturę testowanego kodu źródłowego - łatwiej się wtedy odnaleźć w tym wszystkim, wiesz co gdzie powinno być wiedząc, jak wygląda projekt, widzisz już na pierwszy rzut oka, co jest, a czego brakuje.
W kwestii rozdziału testów na integracyjne, jednostkowe etc. wydaje mi się, że sensowniej jest trzymać je osobno, a nie razem np. per-testowana-klasa
. Nie zawsze chcesz odpalać wszystkie testy naraz, możesz np. mieć zarówno jakieś lekkie i szybkie testy nadające się do częstego puszczania (bo nie spowalniają za bardzo developmentu) oraz osobno te najcięższe, które będziesz chciał puszczać rzadziej, na zasadzie "próby generalnej" nim uznasz, że zadanie skończone. Taki rozdział znacznie to ułatwia, a odpalenie wszystkich naraz nadal nie będzie sprawiało problemów.
Co do samego testowania - ot, piszesz np. klasę testową TestFoo
, definiujesz, co ma się dziać:
- przy ładowaniu klasy testowej
- przy ładowaniu pojedynczego testu
- po zakończeniu pojedynczego testu
- po zakończeniu testów w klasie testowej
Czyli najpierw zapewniasz przygotowanie odpowiednich warunków
do testowania, a później sprzątasz po teście - w zależności od potrzeb, oczywiście. Jeśli chcesz np. testować klasę Kalkulator
która dodaje i odejmuje liczby podane jako parametry metod, no to wiele do inicjalizowania i sprzątania nie ma. Ale jeśli chcesz testować już niekoniecznie jednostkowo, a bardziej integracyjnie klasę, w dodatku jakoś symulując "żywy organizm" i jakieś "realne dane" i patrząc na szerszy kontekst tj. to jak ta klasa wchodzi w interakcje z resztą aplikacji, otoczeniem etc, no to takie metody nazwijmy to roboczo beforeTest
/ afterTest
(wybacz, nie pamiętam jak dokładnie się nazywają w pytest
) będą nieocenione.
Co do tego jak "mapować" testy np. na metody klasy itp. to wydaje mi się, że reguła jedna metoda - jeden test
jest kiepska. Dostajesz w sumie dwa możliwe główne przypadki:
- Twoje testy będą pokrywać jedynie jeden przypadek wykorzystania danej metody, prawdopodobnie najbardziej typowy (czyli ten, który prawdopodobnie i tak się nie wysypie). Czyli tak naprawdę niby będą, ale nie będą prawie niczego testować i przydadzą się jedynie po to, by móc powiedzieć, że jakieś tam są.
- Twoje testy będą pokrywać wiele przypadków testowych naraz, co rodzi dodatkowe problemy. Po pierwsze, gdy któryś taki pod-test w teście się wysypie, no to siłą rzeczy pozostałe przypadki już się nie odpalą i nawet nie będziesz wiedział, czy są ok czy nie. Po drugie, pal licho testy jednostkowe, które niby nie powinno niczego tykać, ale w momencie gdy testujesz już jakąś imitację "żywego organizmu", to zmiana stanu spowodowana wykonaniem pierwszego przypadku może wpływać na działanie drugiego. Robi się z tego bajzel i z takiego testu co najwyżej się dowiesz, że
B zadziała, jeśli najpierw wykonasz A
.
Dlatego uważam, że jeden test powinien testować dokładnie jeden przypadek - przy czym dla każdej metody testowanej klasy (choć równie dobrze możesz i powinieneś chcieć testować czyste funkcje, jeśli masz jakieś zdefiniowane) takich przypadków testowych może (a nawet powinno) być wiele. Szczególnie oprócz jakichś przypadków "typowych" dobrze jest przetestować wszelkiej maści znane corner-case'y, zarówno pozytywne (prawidłowy input -> prawidłowy output) jak i negatywne (nieprawidłowy input -> pożądane zachowanie). Dobrze mieć zarówno testy jednostkowe (by mieć pewność, że pojedyncze bloczki spełniają swoją rolę) i integracyjne (by mieć pewność, że większe kawałki spięte z tych mniejszych bloczków też spełniają swoją rolę)
Jest jeszcze kwestia podejścia do testowania, np. Test Driven Development
i tego, czy powinno się pisać testy do kodu czy kod do testów. Wydaje mi się, że dopóki obracasz się w obrębie jakiegoś swojego feature-brancha, odizolowanego od reszty świata, który dopiero w przyszłości będziesz chciał wypchnąć do master brancha czy tam developa, to różnicę widzisz przede wszystkim Ty - koniec końców w pull requeście znajdą się przecież i testy, i kod. Nie jestem żadnym ekspertem od testowania, TDD itd. ale widzę jedną zaletę - zaczynając zadanie od pisania testów wymuszasz na sobie to, by napisany później kod był jakkolwiek testowalny. W drugą stronę może być słabo, jeśli mając prawie gotowy moduł uświadomisz sobie, że strasznie nababrałeś i musisz przepisać wszystko od nowa, by móc w ogóle napisać porządne testy. A to się zdarza :D Niemniej jednak nawet, jeśli zastosujesz TDD, prawdopodobnie rzadko (albo nigdy) będzie tak, że najpierw napiszesz sobie testy które wszystko pokrywają i wszystko przewidują, potem napiszesz cacy kod, który najpierw niby będzie świecił cały na czerwony, ale potem dumnie zabłyśnie zielenią i już, już będzie można pchać prosto do mastera nawet bez review. To tak nie działa. Prawdopodobnie już pisząc kod uświadomisz sobie, że istnieją jeszcze jakieś inne przypadki, których nie przewidziałeś siadając do testów, zatem wrócisz do testów by je uwzględnić, wówczas okaże się, że kod wymaga poprawienia bo faktycznie niektóre testy zaświeciły się na czerwono. I tak dalej - byleby nie trwało to w nieskończoność ;)
Tu masz jakiś wątek o tym, kiedy właściwie pisze się kod w TDD.