Zasady pisania dobrych testów – F.I.R.S.T. i konkretne techniki żeby je zastosować

softwareskill.pl 3 lat temu

Istnieją ogólnie przyjęte zasady, które warto stosować podczas pisania testów. Sprawiają, iż testy są bardziej użyteczne w Twoim projekcie. Do każdej z tych zasad podam Ci konkretne techniki i wskazówki, aby je spełnić.

Zasady mają akronim F.I.R.S.T. i kolejne litery oznaczają:

  • Fast
  • Isolated / Independent
  • Repeatable
  • Self-validating
  • Timely

Nie chcę, żeby zabrzmiało to jak oklepany temat z rozmów rekrutacyjnych, ale chcę Ci pokazać, co KONKRETNIE możesz zrobić, żeby JUŻ TERAZ poprawić użyteczność testów w Twoim projekcie.

(F) Fast

Uruchomienie testów powinno być szybkie. Sprawia to, iż otrzymujemy szybką informację zwrotną, czy wprowadzona zmiana wprowadziła błąd, czy nie. Szybkie testy nie zniechęcają ludzi do ich uruchamiania, tworzenia nowych i utrzymywania.

Wyobraź sobie, iż aby uruchomić jakikolwiek test lub zestaw musisz poczekać 20 sekund. Podczas tych 20 sekund jest wysoce prawdopodobne iż ulegniesz rozproszeniu, albo będzie silna pokusa „zrobienia czegoś w międzyczasie”. Zerkniesz gdzieś, odpiszesz na maila. Ulegniesz rozproszeniu, a Twoja produktywność spadnie przez tzw. context switching, czyli stracenie/zmianę kontekstu wykonywanej czynności. Pamięć krótkotrwała zostanie wyczyszczona i trzeba poświęcić dodatkową energię na zorientowanie się, co adekwatnie przed chwilą robiłem, na czym stanęło. Spada produktywność. Jesteś kognitywnie przeciążony. Twoja praca staje się mniej efektywna.

Przyczyną długo wykonujących się testów są głównie:

  • Interakcje I/O
  • Opóźnienia (timeouty)
  • Tworzenie i odświeżanie kontekstu Spring

Jak to zoptymalizować?

Niektóre testy siłą rzeczy wykonują się dłużej. Są to głównie testy integracyjne, które potrzebują interakcji z I/O. Tam, gdzie tylko jest to możliwe izoluj I/O. Wykorzystuj implementacje in-memory lub całkowicie zaślepiaj takie interakcje.

  • Czytaj więcej – Mockować czy nie? 🤔 Czym jest Unit w unit testach? Dwie szkoły pisania testów
  • Czytaj więcej – Mockito w pigułce! Poznaj dobre praktyki i przeczytaj czego nie mockować

Bądź pragmatyczny. W testach unitowych stosuj całkowitą izolacją I/O, a implementacje sprawdzaj w testach integracyjnych. Zastosuj piramidę testów. Wykonuj zestaw szybkich testów najpierw, później wolniejszych, tak, aby dostać jak najszybszy feedback.

  • Czytaj więcej: Testowanie: Piramida testów

Z implementacji in-memory (które też bywają wolne) korzystaj w testach komponentowych (nie unitowych).

Twój małe konteksty Spring-owe, o ile unitem jest pakiet, a nie unit. Staraj się nie modyfikować klas w kontekście Spring-a, który wymaga potem przeładowania (tzw. dirty context).

Gdy testujesz funkcjonalność z opóźnieniem zegara, zamiast wstawienia sleep() w testach skorzystaj z zamockowanego zegara, który możesz zmodyfikować:

Clock baseClock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); // do action Clock after = Clock.offset(baseClock, Duration.ofSeconds(5)); // check operation

Psst… Interesujący artykuł?

Jeżeli podoba Ci się ten artykuł i chcesz takich więcej – dołącz do newslettera. Nie ominą Cię materiały tego typu.

Dołączam

Dziękujemy!

Wysłaliśmy Ci mail powitalny, w którym znajdziesz link do aktywacji newslettera. Do usłyszenia!

(I) Isolated, Independent

Testy nie powinny zależeć od siebie. Pozwala to na uruchamianie ich osobno i w całym zestawie. Zawsze dają takie same wyniki.

Testy zależą od siebie, kiedy po wykonaniu testu ustawiany jest jakiś efekt uboczny we współdzielonych obiektach, np. mockach, danych, stanów obiektów. Wtedy kolejne ich uruchamianie nie będzie deterministyczne.

Dlatego o ile test oczekuje jakiegoś stanu, powinien sobie go utworzyć w izolacji, tylko na czas działania. o ile to możliwe – twórz obiekty lokalnie w fazie given.

Jeżeli istnieją wspólne implementacje, bo koszt utworzenia nowych obiektów jest zbyt wysoki, wtedy przywróćmy stan zmodyfikowanych obiektów (posprządanie po teście). Przykłady:

  • Współdzielone Mocki w kontekście Spring-a po wykonaniu testu resetują się (dzięki runnerom).
  • Stan w implementacjach in-memory powinien zostać zresetowany.

Gdy zadbasz o tę higienę, testy nie będą zależały od siebie.

(R) Repeatable

Płynnie przechodzimy do kolejnej zasady. Testy powinny być powtarzalne w każdych warunkach.

Powinny zależeć tylko i wyłącznie od kodu, który testują. Nie powinny zależeć od czynników zewnętrznych, na przykład poprzednich testów ale też od dostępności bazy danych, sieci, plików.

Jeżeli zależą od czegoś jeszcze, to nie dają klarownego wyniku PASS lub FAIL. Raz test przechodzi, raz nie. Nazywa się to kruchym testem, (flaky test), łatwo go uszkodzić.

Istnieje klasa testów integracyjnych, która co do zasady ma małą izolację. W porządku, dajmy takie testy do osobnej kategorii, wykonywanych później lub w osobnym zadaniu w Contineous Integration. Możemy wtedy sami ocenić, czy błąd jest spowodowany jakimś tymczasowym błędem i wydajemy oprogramowanie, czy nie. Ale nie blokuje nam to pracy.

Dowiedz się, jak podnieść stabilność tego typu testów: Nie działa środowisko? 🤬 Stabilne testowanie integracyjne z infrastrukturą 🧘‍♂️ Projekt Testcontainers

(S) Self-validating

Tetsy powinny dawać jasną i klarowną informację, czy przeszły, czy nie: PASS lub FAIL. o ile się nie udał, to powinien dać informację:

  • co się stało
  • i dlaczego się stało

Dlatego tak bardzo ważne jest poprawne i jednoznaczne nazywanie testów oraz towarzyszącym ich asercji. Później raport z testów będzie wyglądał w taki sposób, iż możemy bardzo gwałtownie zorientować się co poszło nie tak i dlaczego.

W zasadzie self-validating chodzi też o to, zeby przygotować raport z testów w sposónb automatyczny. Żeby nie musieć manualnie interpretować raportu z przebiegu testów. Wynik powinien być kwantyfikowany (mierzalny). Wtedy przykładając do wyniku jakąś miarę, możemy w sposób automatyczny określić rezultat przebiegu testów, bez manualnego sprawdzania.

Psst… Interesujący artykuł?

Jeżeli podoba Ci się ten artykuł i chcesz takich więcej – dołącz do newslettera. Nie ominą Cię materiały tego typu.

Dołączam

Dziękujemy!

Wysłaliśmy Ci mail powitalny, w którym znajdziesz link do aktywacji newslettera. Do usłyszenia!

(T) Timely

Zasada Timely mówi o tym, żeby testy pisane były zawsze na czas – razem z kodem, a nie dopisywane po tym, jak wdrożymy funkcjonalność.

Wyobraź sobie, iż piszesz kod, a potem po jakimś czasie zabrakło testów i „trzeba dopisać testy”. Testy są współczynnikiem jakości. o ile piszesz kod razem z testem, gwarantujesz pewną jakość wytworzonej pracy, iż implementacja działa poprawnie.

Są techniki, które wspomagają tego typu zachowanie TDD (Test-Driven Developent), gdzie najpierw powstają testy, a później implementacja. To się nazywa testowanie przed kodem.

Moim zdaniem nie ma niczego złego w napisaniu testów po kodzie, po powstaniu implementacji, ale w ramach tego samego zestawu zmian wciąganego do repozytorium. Możemy się wtedy wspomóc raportem Code Coverage i Mutation Coverage.

  • Czytaj więcej – Code Coverage w Java
  • Czytaj więcej – Mutation Coverage – testy mutacyjne w Java

Ważne jest to, żeby testy produktowa w procesie wytwarzania oprogramowania, razem z kodem, żeby wprowadzić dobrą jakość.

Przykład testów z zachowaniem zasad F.I.R.S.T.

Oto raport z przykładowego projektu (który znajdziesz w Programie Java Developera). Wprowadziłem tam zmianę, która popsuła funkcjonalność. Raport mówi mi, iż testy nie przechodzą:

Sprawdźmy czy ten zestaw testów odpowiada regułom F.I.R.S.T.:

  • F – OK
    Zestaw testów wykonuje się relatywnie szybko. Czas wykonania bez inicjalizacji to ok 15ms.
  • I – OK
    Wprowadzana zmiana zapaliła tylko część testów. Testy nie zależą od żadnego stanu.
  • R – OK
    Uwierz na słowo, iż testy można uruchamiać ponownie i każde uruchomienie daje tan sam rezultat.
  • S – OK
    Nie muszę za każdym razem manualnie sprawdzać wyniku działania, np. czytać logów aplikacji, żeby dowiedzieć się co się stało. Z nazw przypadków testowych gwałtownie orientuję się co popsułem. Szczegóły widzę w opisie asercji.
  • T – OK
    Testy zostały napisane „na czas”, razem z kodem.

Podsumowanie

Jakie są zasady pisania dobrych testów?

Zestaw zasad, które sprawią, iż Twój zestaw testów będzie bardziej użyteczny w projekcie zawiera akronim F.I.R.S.T.

Co oznacza F.I.R.S.T.?

Akronim F.I.R.S.T. zbiera ogólnie przyjęte dobre zasady pisania dobrych testów. Są to:
(F)ast – Szybkie, często uruchamiane testy.
(I)ndependent/Isolated – Testują pojedynczy możliwie przypadek. Są wyizolowane i niezależne od siebie, nie zależą od stanu ustawionego w innych testach, ani nie mają efektów ubocznych. Uruchamiane w losowej kolejności dają ten sam wynik.
(R)epeatable – Takie same wyniki za każdym razem. Testy są stabilne i deterministyczne.
(S)elf-validating – Brak manualnej interpretacji. Dają jasny i klarowny raport z testów, co nie działa i dlaczego.
(T)imely – Napisane na czas, wraz z implementacją, jako jedna zmiana w projekcie. Wspomóż się TDD lub napisz testy po kodzie. Praktyka czyni mistrza.

Wpis który czytasz to zaledwie fragment wiedzy zawartej w Programie szkoleniowym Java Developera od SoftwareSkill. Mamy do przekazania sporo usystematyzowanej wiedzy z zakresu kluczowych kompetencji i umiejętności Java Developera. Program składa się z kilku modułów w cotygodniowych dawkach wiedzy w formie video.

Chcę więcej wiedzy

Podoba Ci się ten artykuł? Weź więcej.

Jeżeli uważasz ten materiał za wartościowy i chcesz więcej treści tego typu – nie przegap ich i otrzymuj je prosto na swoją skrzynkę. Nawiążmy kontakt.

Dołączam

Dziękujemy!

Wysłaliśmy Ci mail powitalny, w którym znajdziesz link do aktywacji newslettera. Do usłyszenia!

Gdybyś potrzebował jeszcze więcej:

Jesteś Java Developerem?

Przejdź na wyższy poziom wiedzy
„Droga do Seniora” 🔥💪

Chcę więcej wiedzy

Jesteś Team Leaderem? Masz zespół?

Podnieś efektywność i wiedzę swojego zespołu 👌

Sprawdź

Gdybyś potrzebował jeszcze więcej:

Jesteś Java Developerem?

Przejdź na wyższy poziom wiedzy
„Droga do Seniora” 🔥💪

Chcę więcej wiedzy

Jesteś Team Leaderem? Masz zespół?

Podnieś efektywność i wiedzę swojego zespołu 👌

Sprawdź

Piguła wiedzy o najlepszych praktykach testowania w Java

Pobierz za darmo książkę 100 stron o technikach testowania w Java

Pobierz ebooka
Idź do oryginalnego materiału