Struktura testów jednostkowych

Struktura testów jednostkowych

Pierwszy z serii artykułów o testach jednostkowych przeznaczonych dla początkujących programistów.

sobota, 29 grudnia 2018

Informatyka

Ciężko wyobrazić sobie, jak wyglądałby współczesny świat bez testów jednostkowych. Te niepozorne kawałki kodu stanowią pierwszą linię obrony programistów przed wprowadzaniem błędów do kodu. Jednocześnie tworzenie dobrych testów to niemalże sztuka, i to niełatwa. Kiedy rozmawiam z młodszymi programistami, którzy zaczęli swoją drogę zawodową, często słyszę skargi na małą wagę, jaką przykłada się do wyrobienia umiejętności pisania testów w procesie edukacji. I rzeczywiście, sam pamiętam z własnych studiów, że testom jednostkowym były poświęcone jedne, półtoragodzinne zajęcia. To mało, o wiele za mało i później trzeba poświęcać czas oraz energię na nadrobienie zaległości już w pracy.

W mojej drodze zawodowej przez lata miałem dużą styczność z tematyką testowania oprogramowania - byłem m.in. test leadem zespołu przez kilka lat, gdzie zbudowałem i wdrożyłem środowisko testów end-to-end. Te doświadczenia dały mi wiele ciekawych spostrzeżeń, co i jak testować, aby nie tylko szybko wychwytywać błędy, ale by i koszt testowania nas nie zabił. Jednak nie o takich ciężkich testach pragnę napisać. Chcę się skupić wyłącznie na testach jednostkowych i przedstawić kilka dobrych konwencji i praktyk, które mogą być przydatne zwłaszcza dla początkujących programistów. W pierwszej części przybliżę strukturę takich testów, stosowane konwencje oraz zasady dobrego nazewnictwa. Praktyczne zagadnienia będę pokazywał na przykładzie Javy oraz frameworka JUnit 5 z bibliotekami AssertJ oraz Mockito. Zacznijmy zatem!

Po co piszemy testy jednostkowe?

Testy jednostkowe, jak każdy rodzaj testów automatycznych ma za zadanie zabezpieczać projekt przed wydostaniem się nieuchronnie wprowadzanych błędów do końcowego produktu. Nie chodzi o ich całkowitą eliminację, gdyż jest to technicznie niemożliwe, ale o zredukowanie liczby takich przypadków do akceptowalnego poziomu. Środowisko testowe musi być w stanie szybko dać programiście informację zwrotną odnośnie tego czy jego zmiana nie niesie ze sobą żadnych nieoczekiwanych skutków ubocznych. Im szybciej jesteśmy w stanie taką informację mu dostarczyć, tym tańsze jest wprowadzenie poprawki. W informatyce mamy wiele rodzajów testów automatycznych. Relacje między nimi przedstawiane są często na diagramie zwanym piramidą testów:

Ilustracja
Piramida testów

Kształt piramidy reprezentuje środowisko testowe, na które składa się kilka rodzajów testów przedstawionych w postaci warstw. Tych w zależności od intencji autora może być od 3 do 6. Dół piramidy ilustruje testy, które są najłatwiejsze w utrzymaniu i znajdują się także najbliżej kodu. Są to właśnie testy jednostkowe. Warstwa ta jest najszersza, co oznacza, że takich testów powinniśmy mieć w projekcie najwięcej. Drugą linią obrony są szeroko rozumiane testy integracyjne, które są już trudniejsze w stworzeniu i utrzymaniu, dlatego powinno być ich mniej. Na samej górze mamy testy GUI, testy akceptacyjne lub przekrojowe testy E2E. Są one bardzo kosztowne w bieżącym utrzymaniu i często z założenia są niestabilne, dlatego powinno ich być mało. Diagram ten można też rozumieć w taki sposób, że większość wprowadzanych do kodu błędów powinna być wyłapywana przez testy jednostkowe już na etapie jego pisania.

Na wielu wersjach diagramu poszczególne warstwy mają identyczną grubość. Przeważnie wynika to po prostu z tego, że narzędzie używane do stworzenia ilustracji rysuje piramidki właśnie w taki sposób, jednak może dawać mylne wyobrażenie na temat tego, ile takich testów właściwie powinno być. Na mojej piramidce celowo każda warstwa ma inną grubość, aby podkreślić, że testów jednostkowych nie ma być po prostu więcej niż pozostałych. Ma ich być dużo więcej. Jeden z projektów, którym się opiekowałem, miał kilka tysięcy testów jednostkowych i 27 testów integracyjnych. Więcej po prostu nie było potrzebne, a było to możliwe dzięki przemyślanej strukturze kodu i dobrze zaprojektowanym testom.

Testów jednostkowych powinno być dużo więcej niż pozostałych testów. Im więcej rzeczy jesteś w stanie nimi przetestować, tym lepiej.

Oczywiście istnieją rzeczy, których nie da się lub nie powinno się testować jednostkowo, ale o tym powiemy sobie w kolejnych artykułach.

Kiedy uruchamiać testy jednostkowe?

Automatyzacja testów to rodzaj inwestycji - inwestujemy swój czas z nadzieją, że nam się on w przyszłości z nawiązką zwróci. Jednak aby było to możliwe, testy muszą być uruchamiane regularnie, a ich wyniki analizowane i brane pod uwagę. Pisanie testu automatycznego, który uruchomimy raz czy dwa po prostu nie ma absolutnie żadnego uzasadnienia. O wiele prościej i skuteczniej byłoby wykonać go ręcznie. Dotyczy to każdego rodzaju testów. Testy jednostkowe są zawsze najbliżej testowanego kodu i tutaj realizowane jest to dwojako:

  • programista może szybko uruchomić cały zestaw testów lub jego wybraną część u siebie na lokalnym komputerze jako część swojej pracy,
  • aby jakakolwiek zmiana weszła na produkcyjną gałąź, wszystkie testy jednostkowe muszą przejść (weryfikowane automatycznie przez system ciągłej integracji),
  • aby wypuścić nową wersję, testy jednostkowe muszą przejść.

Aby to osiągnąć, środowisko testów jednostkowych musi dać się uruchomić jednym poleceniem i nie wymagać żadnej dodatkowej konfiguracji. Jeśli tak nie jest, to najprawdopodobniej nasze testy wcale jednostkowymi nie są i popełniliśmy poważny błąd w sztuce.

Testy jednostkowe muszą być szybkie i proste w uruchomieniu, by programista jak najszybciej otrzymał informację zwrotną. Jeśli jakiś test jednostkowy nie przechodzi, zmiany nie mogą wejść do głównej linii kodu.

Struktura testów jednostkowych

Zadajmy sobie pytanie, co oznacza, że testy jednostkowe mają być blisko kodu. W przypadku Javy kod zorganizowany jest w klasach, dlatego będzie to też naturalna jednostka podziału naszych testów. Po prostu każda klasa będzie miała swój własny zestaw testów, który w JUnit 5 domyślnie też będzie klasą. Obowiązuje tu prosta konwencja nazewnictwa - jeśli klasa nazywa się Foo, to klasa z testami będzie się nazywać FooTest. Umieszczamy ją w tym samym pakiecie, ale nie w katalogu /src/main/java, gdzie siedzi kod produktu, ale w /src/test/java. Takie nazewnictwo katalogów rozpoznają wszystkie najważniejsze narzędzia w ekosystemie Javy, począwszy od systemów buildowych Maven i Gradle, na środowiskach IDE skończywszy. Nigdy nie łam tej konwencji, bowiem może to prowadzić to problemów. Pamiętam sytuację, kiedy musiałem naprawić coś w projekcie, którego do tej pory nie znałem. Wprowadziłem poprawkę, puściłem z IntelliJ testy jednostkowe i dostałem komunikat, że wszystkie przeszły. Zadowolony zrobiłem commit i wypchnąłem zmiany na serwer. Mój pull request został jednak odrzucony z informacją, że testy nie przechodzą. Zacząłem się zastanawiać, co się stało - czy testy są zależne od jakichś ustawień środowiska czy co? Przyczyna okazała się prozaiczna. W tym projekcie ktoś ponazywał część klas z testami w formie TestFoo (na odwrót) i IntelliJ w ogóle mi ich nie rozpoznał jako testy, a Maven już tak. Kiedy zacząłem puszczać poszczególne klasy osobno ręcznie, okazało się, że faktycznie w moim kodzie był błąd.

Dla każdej klasy Foo twórz odpowiadający jej zestaw testów w klasie FooTest w folderze /src/test/java.

Klasa testowana:

package com.example.xyz;

public class Foo {
   public Foo(int a, Bar b) {
      // ...
   }

   public int doSomething() {
      // ...
   }

   public List<Joe> doAnotherThing(int x) {
      // ...
   }
}

Klasa testowa:

package com.example.xyz;

class FooTest {
   @Test
   void test() {

   }
}

W JUnit 5 testem jest każda metoda oznaczona adnotacją @Test i umieszczona w klasie. Począwszy od tej wersji frameworka, ani przy klasie, ani przy metodach nie musimy umieszczać żadnych modyfikatorów dostępu w stylu public czy protected. Istnieją też inne sposoby na tworzenie testów niż dodawanie nowych metod, ale nie będziemy tego zagadnienia teraz poruszać.

Given-when-then

Podstawową rzeczą, którą należy na początku opanować, jest umiejętność zdecydowania, jakie testy w ogóle napisać i co w nich zawrzeć. Bardzo pomocna jest tu konwencja zwana given-when-then. Chodzi o to, że test dzielimy zwykłymi komentarzami na trzy sekcje:

  • given - opis przykładowej sytuacji i warunków początkowych,
  • when - akcja, którą wykonujemy na obiekcie testowanej klasy (jedno wywołanie metody)
  • then - weryfikacja wyników.

Ta konwencja promuje, aby każdy test sprawdzał jedno konkretne zachowanie. Innymi słowy, nie piszmy testów, które sprawdzają 3837 rzeczy jednocześnie, albo testują całą sekwencję wywołań. Dlaczego? Ponieważ jest to też forma dokumentacji. Ktokolwiek siadłby do takiego przeładowanego testu za miesiąc czy za pół roku, nie połapie się co mieliśmy na myśli i jakie jest oczekiwane zachowanie klasy. Oto prosty test napisany w tej konwencji:

// given
var testedObject = new Foo(5, new Bar());

// when
var result = testedObject.doAnotherThing(4);

// then
assertThat(result)
   .hasSize(4)
   .contains(new Joe());

Co w sytuacji, kiedy chcielibyśmy przetestować dwa lub więcej wywołań? Przede wszystkim powinniśmy napisać więcej testów. Jednak nie ma żadnych przeciwwskazań, aby metody testowanego obiektu wywoływać też w sekcji given:

// given
testedObject.doSomething(4);

// when
var result = testedObject.doAnotherThing(4);

Metoda doSomething() powinna mieć zawsze swoje własne testy. Tutaj dajemy do zrozumienia, że wywołujemy ją tylko po to, by przygotować scenariusz dla testowania innej metody. Pozwala nam to też łatwiej znaleźć przyczynę problemu, jeśli ten wystąpi. Załóżmy, że nie przechodzi nasz test dla doAnotherThing() i jednocześnie nie przechodzą testy dla doSomething(), to wiemy, że musimy skupić się na tych drugich, bo jeśli ta metoda nie działa, to doAnotherThing() nie ma żadnego sensu. Ale jeśli te testy są ekologiczne (zielone :)), to wiemy że doAnotherThing() jest zepsuty.

Dziel testy na sekcje given-when-then i testuj jedno wywołanie metody naraz. Jeśli czujesz, że musisz przetestować więcej, to źle czujesz - zrób po prostu więcej testów.

Jak nazywać testy?

Dla mnie prawidłowe nazewnictwo to jedna z najważniejszych "miękkich" umiejętności programisty. Ma ono duży wpływ na to czy nasz kod będzie zrozumiały dla osób postronnych i czy w obrębie zespołu będziemy się mogli łatwo ze sobą dogadać. Ma to też zastosowanie przy pisaniu testów jednostkowych. Wiemy już, że nasze testy w większości przypadków będą metodami, zatem musimy nadać im jakąś nazwę i możemy to wykorzystać na naszą korzyść. Przede wszystkim zacznę od tego, że strasznie nie cierpię metod nazwanych w stylu testEmpty() - jedno słowo, które nic nie mówi zwłaszcza, gdy w środku metody jest 20 linijek niepodzielonych na sekcje given-when-then. Nazwa powinna być zatem bardziej opisowa i "naturalna". Najlepiej, aby odwoływała się do przeznaczenia testowanej klasy - niestety dotychczas używany przykład z nazwą Foo nie będzie tu pomocny. Wyobraźmy sobie zatem następującą sytuację: mamy system, który monitoruje serwisy informacyjne w różnych kanałach telewizyjnych i zbiera informacje o podawanych tam aktualnościach. Nie wszystkie kanały monitorujemy jednak cały czas - z każdym kanałem skojarzony jest zestaw reguł informujący o tym, kiedy można go nasłuchiwać, a kiedy nie. Kanały mają też swój priorytet i informacje z kanałów o najwyższym muszą być podawane jako pierwsze. Jednym z elementów systemu jest klasa TVChannelMonitor, która agreguje dane z kilku kanałów, uwzględniając dostępność oraz priorytety:

public class TVChannelMonitor {
   public TVChannelCollector(List<TVChannel> allChannels) {
      // ...
   }

   public List<TVChannel> findCurrentlyActiveChannels() {
      // ...
   }

   public List<News> fetchNews(int limit) {
      // ...
   }
}

Spróbujmy teraz napisać do niej jakiś test jednostkowy i odpowiednio go nazwać. Najbardziej pierwotna konwencja nazewnictwa testów to wspomniane już testDoSomething. Wywodzi się ona z czasów JUnit 3, kiedy nie było jeszcze adnotacji i framework rozpoznawał testy właśnie po obecności słowa test na początku nazwy. Obecnie technologia poszła naprzód i nie ma to już żadnego uzasadnienia. O wiele lepsza jest konwencja wykorzystująca słówko should, po którym następuje słowny opis scenariusza w formie całego zdania:

@Test
void shouldReturnAllChannelsIfAllAreActive() {
   // given
   var fooChannel = mock(TVChannel.class);
   var barChannel = mock(TVChannel.class);
   var joeChannel = mock(TVChannel.class);
   when(fooChannel.isActive()).thenReturn(true);
   when(barChannel.isActive()).thenReturn(true);
   when(joeChannel.isActive()).thenReturn(true);
   var testedObject = new TVChannelMonitor(List.of(fooChannel, barChannel, joeChannel));

   // when
   var result = testedObject.findCurrentlyActiveChannels();

   // then
   assertThat(result)
       .hasSize(3)
       .contains(fooChannel, barChannel, joeChannel);
}

@Test
void shouldReturnOnlyActiveChannelsFooBarIfJoeIsInactive() {
   // given
   var fooChannel = mock(TVChannel.class);
   var barChannel = mock(TVChannel.class);
   var joeChannel = mock(TVChannel.class);
   when(fooChannel.isActive()).thenReturn(true);
   when(barChannel.isActive()).thenReturn(true);
   when(joeChannel.isActive()).thenReturn(false);
   var testedObject = new TVChannelMonitor(List.of(fooChannel, barChannel, joeChannel));

   // when
   var result = testedObject.findCurrentlyActiveChannels();

   // then
   assertThat(result)
       .hasSize(2)
       .contains(fooChannel, barChannel)
       .doesNotContain(joeChannel);
}

Dobra nazwa powinna informować o:

  • rodzaju wykonywanej operacji,
  • spodziewanym wyniku
  • okolicznościach, w jakich powinna ona wystąpić.

Ważną rzeczą jest to, że pisząc nazwę testu, staramy się unikać zbyt technicznego języka. Szczegóły możemy sobie bowiem sprawdzić w kodzie, tymczasem nazwa ma nam pomóc przełożyć to na język ludzki. Innymi słowy nie piszmy IfIsActiveMethodReturnsFalse, lecz na przykład IfChannelJoeIsActive.

Konwencja should jest całkiem popularna - jej zaletą jest to, że nazwy testów nie są przesadnie długie, lecz jednocześnie nie oferuje ona ustalonego sposobu, jak dokładnie powyższe trzy punkty powinny zostać w niej zapisane. Różni programiści mogą zrobić to na różne sposoby i nie zawsze rezultat jest łatwy i zrozumiały dla innych. Istnieje jednak alternatywna konwencja:

@Test
void whenFindCurrentlyActiveChannel_givenAllChannelsAreActive_thenReturnAllOfThemInAnyOrder() {
   // given
   var fooChannel = mock(TVChannel.class);
   var barChannel = mock(TVChannel.class);
   var joeChannel = mock(TVChannel.class);
   when(fooChannel.isActive()).thenReturn(true);
   when(barChannel.isActive()).thenReturn(true);
   when(joeChannel.isActive()).thenReturn(true);
   var testedObject = new TVChannelMonitor(List.of(fooChannel, barChannel, joeChannel));

   // when
   var result = testedObject.findCurrentlyActiveChannels();

   // then
   assertThat(result)
       .hasSize(3)
       .contains(fooChannel, barChannel, joeChannel);
}

@Test
void whenFindCurrentlyActiveChannel_givenOneOfChannelsInactive_thenReturnAllChannelsExceptInactive() {
   // given
   var fooChannel = mock(TVChannel.class);
   var barChannel = mock(TVChannel.class);
   var joeChannel = mock(TVChannel.class);
   when(fooChannel.isActive()).thenReturn(true);
   when(barChannel.isActive()).thenReturn(true);
   when(joeChannel.isActive()).thenReturn(false);
   var testedObject = new TVChannelMonitor(List.of(fooChannel, barChannel, joeChannel));

   // when
   var result = testedObject.findCurrentlyActiveChannels();

   // then
   assertThat(result)
       .hasSize(2)
       .contains(fooChannel, barChannel)
       .doesNotContain(joeChannel);
}

W konwencji tej przenosimy given-when-then do nazwy metody, gdzie każdą z sekcji staramy się zwięźle opisać. Poszczególne elementy dla czytelności oddzielone są podkreśleniem i zaczynamy (inaczej niż w kodzie) od when, w której podajemy po prostu nazwę testowanej metody. Tak skonstruowana nazwa będzie oczywiście dłuższa, ale dokładniejsza i co więcej, nauczy nas ona też tego, by nie pisać zbyt długich testów: jeśli tak skonstruowana nazwa testu nie będzie Ci się mieścić w poziomie na ekranie, może to oznaczać, że test robi zbyt dużo i należy go podzielić na mniejsze.

Stosuj jednolitą konwencję nazewnictwa, która pomoże Ci zrozumieć, co właściwie testujesz i jak. Unikaj nazewnictwa w stylu testSomething, lecz stosuj bardziej rozbudowane opisy zgodnie z konwencją shouldDoSomethingInSomeSituation lub whenFoo_givenBar_thenJoe.

Zwięzłe testy

Dzięki zastosowaniu kilku konwencji udało nam się już nadać naszym testom jakąś określoną strukturę. Zauważmy jednak, że sekcja given jest dość rozbudowana. W codziennej praktyce często spotkamy się z sytuacją, że aby skonfigurować określony scenariusz, będziemy musieli napisać wiele linijek kodu. Tu jest kolejne pole do optymalizacji i zwiększenia czytelności testów, co więcej jest ono tak trywialnie proste, że często dziwię się, że nawet doświadczeni programiści potrafią na to nie wpaść. Po prostu opakujmy sobie poszczególne fragmenty kodu w metody i nadajmy im różne czytelne nazwy. Oto przykład:

@Test
void whenFindCurrentlyActiveChannel_givenAllChannelsAreActive_thenReturnAllOfThemInAnyOrder() {
   // given
   var fooChannel = activeChannelOfPriority(1);
   var barChannel = activeChannelOfPriority(2);
   var joeChannel = activeChannelOfPriority(3);
   var testedObject = new TVChannelMonitor(List.of(fooChannel, barChannel, joeChannel));

   // when
   var result = testedObject.findCurrentlyActiveChannels();

   // then
   assertThat(result)
       .hasSize(3)
       .contains(fooChannel, barChannel, joeChannel);
}

@Test
void whenFindCurrentlyActiveChannel_givenOneOfChannelsInactive_thenReturnAllChannelsExceptInactive() {
   // given
   var fooChannel = activeChannelOfPriority(1);
   var barChannel = activeChannelOfPriority(2);
   var joeChannel = inactiveChannelOfPriority(3);
   var testedObject = new TVChannelMonitor(List.of(fooChannel, barChannel, joeChannel));

   // when
   var result = testedObject.findCurrentlyActiveChannels();

   // then
   assertThat(result)
       .hasSize(2)
       .contains(fooChannel, barChannel)
       .doesNotContain(joeChannel);
}

private TVChannel activeChannelOfPriority(int priority) {
   var channel = mock(TVChannel.class);
   lenient().when(channel.isActive()).thenReturn(true);
   lenient().when(channel.getPriority()).thenReturn(priority);
   return channel;
}

private TVChannel inactiveChannelOfPriority(int priority) {
   var channel = mock(TVChannel.class);
   lenient().when(channel.isActive()).thenReturn(false);
   lenient().when(channel.getPriority()).thenReturn(priority);
   return channel;
}

Złota zasada jest taka, żeby w sekcji given unikać rozbudowanej konfiguracji mocków - to one przeważnie odpowiadają za rozwlekłość i nieczytelność takiego kodu. Jeśli mam jedno wywołanie when(), to mogę je umieścić bezpośrednio w teście, ale przy większej liczbie zawsze utworzę zestaw metod, które to ukryją. Ponownie chodzi o dobre nazewnictwo. Metodzie daję nazwę, która używa bardziej ludzkiego słownictwa. Staram się nazywać dane metody słowami podobnymi do tych, które umieszczam w nazwie metody. Chodzi o to, by programista patrzący na dany test mógł sobie łatwo powiązać to, co widzi w nazwie metody z tym, co widzi w jej wnętrzu. Jest też kilka innych korzyści:

  • jeśli programista będzie chciał dowiedzieć się, co znaczy, że np. "kanał jest aktywny", może zajrzeć do metody i ponownie ma jasne powiązanie: "kanał jest aktywny - oznacza to, że metoda X zwraca to, a metoda Y to",
  • niższy koszt utrzymania i poprawienia testu,
  • lepsza kontrola nad tym, kiedy należy dodać nowe testy.

Zatrzymajmy się na tych dwóch ostatnich punktach. Co one oznaczają? Wyobraźmy sobie sytuację, że zmienione zostało API klasy TVChannel i o aktywność kanału odpytujemy już inaczej, np. zamiast wywoływać isActive() musimy najpierw odwołać się przez łańcuszek channel.getChannelRuleset().isActive(). W takim wypadku wystarczy, że poprawimy tylko jedno miejsce bez dotykania testów. Gdybyśmy poprawiali te mocki bezpośrednio w długich testach, nie tylko zajęłoby nam to znacznie więcej czasu, ale jeszcze dodatkowo istniałoby ryzyko, że przypadkiem zmienimy istotę testu. Teraz wyobraźmy sobie, że ponownie API się zmieniło, ale tym razem metoda isActive() potrzebuje jakiegoś dodatkowego argumentu od testowanej klasy TVChannelMonitor. Wymusza to na nas już zmianę zarówno metod activeChannelOfPriority() i inactiveChannelOfPriority(), jak i korzystających z nich testów. Teraz już nie unikniemy zmiany, ale jednocześnie możemy zadać sobie pytanie: czy przypadkiem w związku z tym nie potrzebujemy dopisać kilku dodatkowych testów?

Zwróćmy uwagę na jeszcze jedną rzecz: czy w oryginalnym teście nasze mocki uwzględniały w jakikolwiek sposób priorytet? Nie, a przecież mówiliśmy o nim przedstawiając opis sytuacji. Z opisu wynika, że ten priorytet jednak powinien mieć jakiś wpływ na wynik i nie powinniśmy go ignorować lub zostawiać przypadkowi. To, ile rzeczy uwzględniać w mockach, zależy już od konkretnego przypadku. Wg mnie powinien to być złoty środek i nie należy bać się tego, że w jakimś mocku gdzieś tam jedna metoda nie będzie jednak używana. Złe są dwie skrajności: gdy mockujemy za dużo i zbyt wiele kompletnie niepotrzebnych rzeczy i gdy mockujemy absolutne minimum. Rodzi to bowiem ryzyko, że zapomnieliśmy czegoś skonfigurować i testy nam przechodzą zupełnym przypadkiem. Ponadto taki test staje się wtedy za bardzo przywiązany do aktualnej implementacji, co też nie jest dobre, bo wtedy każda najmniejsza zmiana wymusza też poprawienie testu, a nie o to chodzi. O mockowaniu zresztą będziemy jeszcze mówić.

Ukrywaj skomplikowaną konfigurację scenariusza w sekcji given za ładnie nazwanymi metodami pomocniczymi.

Podsumowanie

Podane tutaj konwencje nazewnictwa i struktury testów pomagają nam w lepszym zrozumieniu testowanego kodu, zwłaszcza w sytuacji, kiedy wprowadziliśmy jakiś błąd. Stosując je, bardzo łatwo jest znaleźć przyczynę problemu i zlokalizować linijkę, która go powoduje. To sprawia, że testy jednostkowe mają tak dużą wartość. W następnym artykule z serii, który ukaże się już niebawem, powiemy sobie więcej o mockowaniu i asercjach, czyli co i kiedy sprawdzać w teście.

Tomasz Jędrzejewski

Programista Javy, lider techniczny. W wolnych chwilach podróżuje, realizując od kilku lat projekty długodystansowych wypraw pieszych.

zobacz inne wpisy w temacie

Informatyka

poprzedni wpis Ratpack, MongoDB i RxJava następny wpis Ratpack, JPA i RxJava

Komentarze (0)

Skomentuj

Od 3 do 40 znaków.

Wymagany, anonimizowany po zatwierdzeniu komentarza.

Odpowiedz na pytanie.

Edycja Podgląd

Od 10 do 8000 znaków.

Wszystkie komentarze są moderowane i muszą być zatwierdzone przed publikacją.

Klikając "Wyślij komentarz" wyrażasz zgodę na przetwarzanie podanych w nim danych osobowych do celów moderacji i publikacji komentarza, zgodnie z polityką prywatności: polityka prywatności