Po części z nadmiaru wolnego czasu, po części przez różne dyskusje techniczne w zespole, a po części pod wpływem tego wątku: Jak w Twojej firmie podchodzicie do tematu testowania? próbuję dojść do jakiegoś (choćby pozornie) jednoznacznego wniosku, jak tak naprawdę należy testować... by nie oberwać potem rykoszetem ;)
Generalnie to taki trochę temat-rzeka, co rusz to inne podejście, każdy robi to trochę inaczej (o ile w ogóle). Choćby z samych rzeczy, które widziałem i/lub popełniłem i/lub o których słyszałem/czytałem, a które mógłbym wymienić jako delikatnie mówiąc niekoniecznie godne pochwały:
- Brak testów bo po co / bo nie ma czasu / bo lenistwo / bo działa i tyle.
- Testowanie "tylko tego co jest sens testować" - gdzie okazuje się, że różnorakich potworków też nie było sensu testować.
- Testowanie, ale poza rzeczami, które zbyt trudno jest przetestować.
- Testowanie żeby coverage w JaCoCo czy innym toolu się zgadzał.
- Testy, po których właściwie nie wiadomo, czy system robi wszystko co powinien, no ale niby przechodzą.
- Pisanie "unit testów" pojedynczych klas z mockami zależności, pisanie "integration testów" bez mocków i testowanie niemalże tych samych przypadków na obie modły.
- Pisanie "unit testów" do rzeczy takich, jak gettery wygenerowane dla
KasztanDTO
przez Lomboka. - Są sobie testy, są sobie "high level", testują wymagania, a pod spodem jest czarna skrzynka w której zmieścisz słonia razem z sawanną i klasy po 40k SLoC linii.
- Gruba na metr wylewka z betonu w testach, niczego nie da się ruszyć ani przerobić.
- Testy przechodzą, więc nie ma co sprawdzać, czy faktycznie wszystko działa.
- Testy tak odporne na zmiany w kodzie, że właściwie nie wiadomo, jak je zepsuć, więc nie wiadomo, czy coś tak naprawdę testują.
- Testy nafaszerowane
mock .... mock ... spy .... verify .... times .... mock .... verify .... inorder ....
. - Testy które nie dość, że muszą stawiać kontekst aplikacji, to jeszcze odpalają milion upgrade'ów / migracji DB i to przed każdym testem, w efekcie wszystko trwa wieki.
- Niemal identyczne testy powtarzające się dla wielu różnych komponentów, które re-używają jakiegoś (zwykle springowego) komponentu / walidatora / konfiguracji / aspektu i upewnianie się, że w każdym miejscu to coś zadziałało jak powinno - plus odrębne testy tego komponentu, oczywiście.
Do myślenia dało mi też nagranie z talka Iana Coopera o "upadku" TDD, gdzie mówił o tym, by nie testować szczegółów implementacyjnych, a jeśli już koniecznie chcesz to zrobić, by rozpoznać ogniem jak działa system - to masz je później usunąć. Bo inaczej wpadniesz w masę różnych pułapek testowania, bolączek TDD, będziesz nieefektywny i na domiar złego ogólnie się zniechęcisz.
Ok, spoko. Ma sens.
- Nie chcemy, by testy zabetonowały kod.
- Nie chcemy, by byle refaktoring psuł nam testy, bo w końcu wszyscy będą się bać refaktorować.
- A już tym bardziej nie chcielibyśmy sytuacji, w której nie wiemy, które testy zepsuły się, bo refaktoring rozbił beton, a które zepsuły się, bo niechcący popsuliśmy wymagania.
- Testujemy wymagania, czyli to co robi system, a nie to w jaki sposób to robi. Testy pojawiają się, gdy pojawi się wymaganie.
Ale z drugiej strony - skoro piszesz testy implementacji, by ją "rozpoznać ogniem" i zrozumieć, co się dzieje pod spodem, to dlaczego masz je usuwać, gdy już zrozumiesz co się dzieje? Po Tobie przyjdzie następna osoba, która też będzie chciała zrozumieć i będzie znów pisać te testy. W tej sytuacji takie testy można by wręcz potraktować jak "żywą" dokumentację kodu. Jeśli implementacja się zmieni, należy odświeżyć dokumentację i tyle.
Zresztą, idąc dalej tym tokiem rozumowania:
- Jeśli biznes nie napisze wprost w wymaganiach, jaki ma być performance / throughput systemu, to nie robimy performance / stress testów - nawet żeby wiedzieć, na czym stoimy i czy performance jest nazwijmy to "w granicach rozsądku"
- Jeśli nie ma wymagania, by system był odporny np. na SQL injection, to możemy śmiało sklejać zapytania z user inputu i pchać do DB bez jakichś
prepared statements
i innych wymysłów które są szczegółem implementacyjnym - Jeśli nie ma wymagania, by system był rozszerzalny, utrzymywalny i testowalny oraz był tworzony zgodnie z dobrymi praktykami, to szkoda tracić czasu na code review, stosowanie statycznej analizy kodu (tym bardziej, że to testowanie implementacji do kwadratu) ani tym bardziej refaktoringiem - więc właściwie nieważne, czy kod jest zabetonowany, czy też nie
Nie mówiąc o tym, że... jak tu jednocześnie pisać testy, które można odpalić i błyskawicznie dostać feedback czy przechodzą i które klęknęły (tu bodajże Uncle Bob bardzo uroczo zilustrował onomatopejami takie testy, ale nie chce mi się szukać), a jednocześnie takie, które nie dotykają szczegółów implementacyjnych i sprawdzają całościowo czy system/moduł działa jak na leży.... i tak dalej i tak dalej? ;)