Testy jednostkowe, czy może integracyjne?

Co warto wiedzieć na początku i nie tylko?

Featured image

Początki programowania nie są łatwe. Mnogość technik, metod, języków mogą przytłoczyć nie tylko początkującego adepta. Przechodzisz przez podstawy. Znasz już język. Robisz kolejny projekt. Potrafisz zrobić kilka endpointów. W końcu jesteś na finiszu swojego REST API. A przynajmniej tak ci się wydaje. Nie wiesz, że to dopiero początek. […]

Nieuchronnie nadchodzi moment na testowanie. Oczywiście MANUALNE, bo nie potrafisz pisać testów! Zgrozo, czemu ja… To są właśnie te momenty gdy zastanawiasz się nad sensem swojego życia. Cóż, nic nie poradzisz. Napisałeś? To trzeba sprawdzić, czy działa. No to zaczynamy testowanie…

Odpalasz aplikację przeklikujesz się przez interfejs. Odpalasz Postman’a. Testujesz strzelając po endpointach - GET, POST, POST, GET, PUT, GET. W sumie działa. Dobra to teraz można iść po kawę. Wracasz z dużym kubkiem kawy […]

Siorbiesz odrobinkę, oddychasz ze spokojem, zakasujesz rękawy i bierzesz się za pisanie kolejnych ficzerów. Zmieniasz kod, uczysz się, poznajesz, ponownie refaktorujesz. Odpalasz… czekasz… czekasz… czemu to nie działa!? Przecież to była prosta zmiana.

Być może na początku skupiałeś swoją uwagę bardziej na języku, czy frameworku (a przynajmniej ja tak to robiłem). Testy jednostkowe, integracyjne, akceptacyjne, regresyjne. Słowem jest tego sporo. Wszystko to zostawiłem na później - kiedyś to ogarnę. Pisanie kodu jest ważniejsze od testów, no nie? Cóż, nie do końca. Testy często są ważniejsze niż kod, ale to tylko moje zdanie. Niemniej jest chociażby takie coś jak TDD/BDD, które pomaga Ci pisać lepszy kod (o tym za chwilkę).

Dobry kod bez testów nie istnieje

Patrz ja mogę bez testów! Po co mi testy? Przecież działa!

Może piszesz taką mikro-mikro aplikację do jakiejś małej-małej rzeczy. Do tego masz spore doświadczenie, a sama aplikacja zajmuje Ci jakiś tydzień pracy. Na domiar tego jesteś pewien, że wyląduje ona w koszu za miesiąc/dwa. W takim przypadku jest pokusa niepisania testów. Pewnie się uda i aplikacja będzie działać. Niemniej pamiętaj i tak będziesz musiał przeklikać się ręcznie przez jakieś flow aplikacji - wielokrotnie. A tego nikt nie lubi, nawet testerzy. Testy jednostkowe są w tym miejscu mega przydatne. Możesz dosłownie w kilka sekund sprawdzić co spartoliłeś. Aplikacje zazwyczaj są pisane na dłuższy czas. Często wiele rzeczy się zmienia. Dochodzą nowe i tak dalej. Jeśli nie masz testów to na pewno przyda się trochę aspiryny.

Trochę o samych testach

Jak wiele nowych rzeczy tak i testowanie na początku wydaje się przytłaczające. Faktem jest, że jest to o wiele łatwiejsze, aniżeli wygląda na pierwszy rzut oka. Jak wygląda typowa testowa ścieżka? Załóżmy, że robisz REST API. Jak każde API masz endpointy, czyli miejsce gdzie wrzucasz dane wejściowe (given). Następnie robisz coś z tymi danymi (when) . Na sam koniec sprawdzasz, czy to co zrobiłeś jest zgodne z oczekiwaniami (then). Po stronie testów pracujemy ze Spockiem (Groovy). A kod naszej aplikacji będzie, chociażby w Kotlinie, ew. Javie.

spock

Jest to bardzo przyjemy framework do testowania, o czym przekonamy się za chwilkę. Warto jeszcze dodać, że (given, when, then) jest to konwencja przyjęta właśnie przez niego, ale istnieje wiele innych wzorców. Chociażby równie popularne AAA (Arrange, Act, Assert), które w zamyśle jest takie same jak w Spocku. Co odróżnia naszego przyjaciela od innych? Tutaj GivenWhenThen to słowa kluczowe. Test wygląda czyściej, a także jest w stylu BDD. Dobra koniec bezsensownej gadki.

Czas na przykład - nasz pierwszy test integracyjny!

Korzystanie ze Spocka jest mega łatwe. Wystarczy rozszerzyć klasę o Specification. Tylko tyle.

abstract class PostPublisher extends Specification { ... }

A sam test wygląda następująco:

abstract class PostPublisher extends Specification {

    def "Should create new blog post"() {
    given: "new blog post data is prepared"
    NewPostDto newPostDto = sampleNewPost ()

    when: "new blog post is submited"
    def response = post ('/posts', newPostDto)

    then: "post is created"
    response.statusCode == CREATED
    response.body.get(0).id != null
}
}

Dodatek do nazwy DTO (data transfer object)- znaczy tyle, że jest to obiekt, który uderza do endpointu.

@PostMapping("posts")
fun save(@RequestBody newPostDto: NewPostDto): PostQueryDto {
    ...
}

Składnia Kotlinowa. Jak widzisz bardzo podobne do tego, jak to robimy w Javie. Zwrotka PostQueryDto zawiera chociażby ID stworzonego posta.

Testy to żywa dokumentacja kodu!

spock

Zapewne jako Developer miałeś do czynienia z czystym, pięknym chaosem. Brak dokumentacji, bo o tym mowa. Nikt o niczym nie wie, a wiele rzeczy w firmie to wiedza plemienna. Niemniej może być gorzej. Otóż jest coś takiego jak stara dokumentacja (dawno nieaktualna). Pamięta jeszcze czasy dinozaurów. Dezinformacja często jest gorsza, aniżeli jej brak. Rozwiązanie jest całkiem proste testy (jako dokumentacja). Jest to jedyna dokumentacja, która nadąża za zmianami aplikacji. Do tego BDD to testy, które są bardzo blisko biznesu.

Proste, łatwe w zrozumieniu testy mogą być dobrą dokumentacją. Ale! Warto tu użyć najpopularniejszego stwierdzenia w całym IT “To zależy”. Od czego? A no… od jakości twoich testów. A błędów da się tu zrobić wiele. Począwszy od przesadnego testowania wszystkiego, aż do testowania nie tego co jest napisane w zakładanym przez ciebie scenariuszu testowym. Takie błędy wbrew pozorom nie są trudne do zrobienia. Wystarczy chociażby nieznajomość swojej domeny. Czym jest domena?

Domena to logika aplikacji. TDD najlepiej czuje się w Domenie. Tam gdzie jest soczyste mięcho naszej aplikacji.

A co to, to TDD/BDD?

W skrócie jest to pisanie testów przed kodem. Budujesz kod na podstawie testów. W teorii BDD to zestaw praktyk, a TDD jest to po prostu proces. W praktyce BDD is TDD done right. Chodzi o to, żeby nie testować implementacji, a zachowanie (Behavior-Driven Development). Spockowe GivenWhenThen wywodzi się własnie z BDD.

Ha! Aaa Ja mam lepszy design aplikacji od Ciebie!

Chodzi oczywiście o poprawienie architektury aplikacji przez pisanie testów. Zmartwię Cię w całym tym TDD nie chodzi o testy per se. Chodzi o to, że jest to technika pisania kodu. Same testy są efektem ubocznym. Powiem Ci szczerze, że nie zawsze potrafię podejść do pisania od strony TDD. Jest to po prostu trudne w niektórych przypadkach, ale to nie znaczy, że nie powinniśmy próbować. Im częściej będziemy starać się tworzyć w ten sposób tym szybciej wejdzie to w nawyk. A jest to niezwykle potężny nawyk o czym już się przekonałem. Zdecydowanie będę się starać polepszać swój warsztat w tej dziedzinie ciebie również zachęcam do spróbowania.

Tak czy inaczej to nie jest silver-bullet na wszystko. TDD jest to po prostu kolejne narzędzie. Można, a nie trzeba wykorzystywać. Wszystko zależy od ciebie. Oczywiście jak do wszystkiego tak i tutaj można znaleźć dobrą wymówkę na nierobienie tego. A to nam się nie chce. Czasami nie wiemy jak. Albo zwyczajnie nie mamy wiedzy na temat domeny. Niemniej największą zaletą TDD jest wymuszenie na nas chwili refleksji nad kodem. Z całym tym TDD jest jak z zębami jeśli nie będziemy myć ich codziennie to w końcu za kilka lat wszystkie nam wypadną. Dlatego trzeba szlifować nawyk, a jest już trudne.

Ale TDD wcale nie musi poprawić architektury kodu

Samo TDD to nie jedyna rzecz jaka poprawia jakość naszego kodu. Chodzi oczywiście o mindset oraz skille. Jeśli masz umiejętności, znasz wzorce, paradygmaty, zasady, metodologie. Wtedy napiszesz fajny kod. Albo i nie. Jest tutaj wiele zależności. Klepanie testów przed kodem nie daje pewności, że kod będzie lepszy. Niemniej jest spore prawdopodobieństwo, że z takim podejściem dowiesz się czegoś czego byś się nie dowiedział bez napisania testu przed kodem. Jak zawsze najłatwiej jest na przykładzie. Załóżmy, że masz bardzo rzadkie zadanie do zrobienia. Naprawienie buga. Piszesz test przed naprawą. Po pierwsze masz pewność, że coś jest popsute. Po drugie masz dowód na to, że naprawiłeś błąd. Proste, nie?

Gdzie nie robimy TDD?

To nie jest twoje!

Kod nie jest dla Ciebie. Kod nie jest dla developerów (no dobra trochę jest). Niemniej kod to przede wszystkim rozwiązanie problemu biznesowego. Dlatego im bliżej testom do biznesu tym łatwiej zrozumieć nie-programiście o czym to jest. Dlatego fajnym pomysłem jest robienie BDD.