Piramida testów

Gdy młody programista zaczyna swoją przygodę z testowaniem, często napotyka się po raz pierwszy na pojęcie piramidy testów. Stało się ono bardzo popularne kilka lat temu, wraz z tym, jak testy automatyczne „weszły pod strzechy” i stały się częścią codziennej rzeczywistości programistów.

Warto sobie zdawać sprawę, że to podejście nie zawsze musi być optymalne i istnieją odstępstwa od tego modelu. Piramida testów jest przydatnym uproszczeniem i jeśli dopiero zaczynasz rozumieć, o co chodzi w testowaniu, to naprawdę, NAPRAWDĘ, polecam, abyś zrozumiał to podejście i stosował je w praktyce. W wielu przypadkach jest ono dobre, a w najgorszym razie nie jest dramatycznie złe.

Zacznijmy od wyjaśnienia, jak rozumiemy poszczególne poziomy testów.

Testy jednostkowe

Pierwszym rodzajem testów są testy jednostkowe. Dla niektórych to synonim zautomatyzowanych testów, jednak warto wiedzieć, że to jedynie jeden z ich rodzajów. Ich cechą jest to, że testujemy w nich jedynie… cóż, jednostkę. Zawsze tu pojawia się dyskusja – czym jest jednostka (unit)? Co programista, to opinia, ja nie zamierzam Ci narzucać żadnej.

Jedynie zaznaczę, że dla mnie unit nie musi być koniecznie jedną klasą. Jeśli mamy zespół kilku klas, które ściśle ze sobą współpracują i nie mają sensu w oderwaniu od siebie, nie widzę sensu, by testować każdą z nich osobno. Takie testy bardziej zaciemniają sytuację i „betonują” kod – sprawiają, że jakikolwiek refactoring jest bolesny, bo trzeba zrobić zmiany w wielu testach, mimo że sama funkcjonalność się nie zmienia.

Co jednak z pewnością charakteryzuje test jednostkowy to szybkość wykonywania. Aby test wykonywał się szybko, musimy maksymalnie go uprościć. Nie ma mowy o łączeniu się do bazy danych, stawiania serwera HTTP, tworzeniu kontekstu springowego. Testujemy tylko klasę lub kilka klas, w izolacji od całego świata. Dzięki temu, wykonanie takiego testu nie powinno zająć więcej niż kilka milisekund.

Testy integracyjne

Drugim poziomem są testy integracyjne. Tu też znajdą się różne definicje, jednak wyznacznikiem jest wyjście poza sam kod. Jeśli testujemy nie tylko nasz kod, ale też framework, którego używamy (np. Spring), jeśli łączymy się z bazą danych lub wysyłamy coś na topic Kafki, to prawdopodobnie mamy już do czynienia z testem integracyjnym.

Dobrym przykładem mógłby być test repozytorium Spring Data. Mogłoby się wydawać, że przedmiotem testu jest tylko klasa, tak więc test jest jednostkowy, jednak sam kod repozytorium nie ma większego sensu, jeśli nie zostanie odpowiednio zinterpretowany przez Springa. Twój test nie sprawdza tak naprawdę Twojego kodu, tylko czy dobrze zintegrowałeś się z frameworkiem, jakim jest Spring Data. 

Innym przykładem ze świata Springa mógłby być test kontrolera. Znów, klasa kontrolera sama w sobie nie robi wiele ciekawych rzeczy. Cała magia dzieje się, gdy Spring Web analizuje adnotacje, którymi oznaczyłeś klasę i na tej podstawie tworzy endpointy HTTP.

Bez testów integracyjnych trudno wyobrazić sobie dziś dobrą aplikację. Jakie są ich wady? Z pewnością szybkość. W przypadku testów springowych test odpala tak naprawdę cały kontekst, co jest kosztowne. Czas wykonania przeciętnego testu integracyjnego może wynosić nawet kilkaset milisekund.

Testy E2E

Na górze piramidy zobaczymy testy E2E (end-to-end, obejmujące całą aplikację). W czasach, gdy tworzono pojęcie piramidy testów, tutaj zwykle pojawiały się już testy manualne. Oddelegowana osoba ręcznie przechodziła różne ścieżki w aplikacji w celu wyłapania błędów. Jeśli nawet takie testy były zautomatyzowane, to często wciąż zajmowała się nim osoba spoza zespołu programistów. Nie trzeba chyba mówić, że takie podejście było bardzo kosztowne, ponieważ ludzie zawsze będą drożsi w utrzymaniu, niż kawałek kodu.

Jednak technologia poszła do przodu i dziś pisanie niezawodnych testów E2E nie jest aż takim wyzwaniem. Często zajmuje się tym sam programista. Wciąż jednak są one dość drogie w utrzymaniu, ponieważ są bardzo wolne. Wymagają często uruchomienia całego środowiska, a niekiedy łączą się z zewnętrznymi systemami, które zawsze będą miały narzut czasowy związany z przesyłem danych.

Są też mniej stabilne- zmiana każdej niewielkiej części kodu może skutkować w zmianie wyniku testu. Z tego wynika jeszcze jeden problem. Ponieważ test obejmuje dużą ilość kodu, trudniejsze jest dostrzeżenie przyczyny, jeśli wykonanie go się nie powiedzie. Test jednostkowy zwróci negatywny wynik jeśli dana klasa jest źle zaimplementowana. Jeśli nie powiedzie się test E2E – musimy dopiero rozpocząć poszukiwanie źródła błędu.

Mimo to, testy E2E są niezastąpione do upewnienia się, że całość aplikacji działa tak, jak sobie to wyobraziliśmy. Pełnią więc często rolę testów akceptacyjnych.

Piramida testów

Teraz, gdy już wiemy, co kryje się pod poszczególnymi kategoriami testów, możemy wyjaśnić, co kryje pod swoją nazwą piramida testów. Kilkukrotnie wspominałem o szybkości działania i koszcie utrzymania poszczególnych testów. Najszybsze są testy jednostkowe, najwolniejsze są testy E2E. To wymusza już na nas pewną strategię testowania. Jeśli napiszemy mnóstwo testów integracyjnych i E2E, taki zestaw testów będzie się wykonywał godzinami (uwierz mi, widziałem takie systemy). Takim ograniczeniom nie podlegają testy jednostkowe. Setki lub tysiące takich testów to wciąż kwestia kilku sekund. Dzięki temu programista może bez problemu puszczać takie testy co kilka minut bez negatywnego wpływu na swoją efektywność (i stan psychiczny).

Nazwa piramidy bierze się od liczby testów, które chcemy mieć na poszczególnych poziomach. Oto, jak można taką strategię testów zwizualizować.

Staramy się więc, aby zdecydowaną większość naszych testów stanowiły testy jednostkowe. Są one tanie w pisaniu i utrzymaniu, są szybkie, więc możemy puszczać ich setki lub tysiące naraz, utrzymując krótki czas wykonania oraz produkujemy je niejako „przy okazji” developmentu (przynajmniej tak powinno być przy użyciu podejścia TDD).

To były podstawy wiedzy o strategii testowania. Jeśli chcesz dowiedzieć się więcej, przeczytaj też mój kolejny artykuł:

Piramida testów na głowie

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *