#17 — testy jednostkowe

By 7 February 2015 February 24th, 2015 Kurs Javy

Dzisiejsza lekc­ja jest pier­wszą doty­czą­ca nie tyle samej nau­ki pro­gramowa­nia, co doskonale­nia warsz­tatu pro­gramisty. Zajmiemy się automaty­czny­mi tes­ta­mi – tzw. Tes­ta­mi jednostkowymi.

W tej lekcji powiemy sobie o tym, czym są testy jed­nos­tkowe, jak je tworzyć oraz jak uży­wać ich ze Springiem. W przyszłoś­ci powiemy sobie także o tzw. mock­owa­niu i bib­liotekach wspo­ma­ga­ją­cych testowanie aplikacji.

Lekcja

Czym są testy jednostkowe

Testy jed­nos­tkowe to – moim zdaniem – najważniejszy z aspek­tów zapew­ni­a­nia jakoś­ci opro­gramowa­nia. Testy jed­nos­tkowe to nic innego jak zbiór testów (prób), które wery­fiku­ją, czy jed­nos­t­ka kodu (np. Klasa, ser­wis itp.) dzi­ała zgod­nie z oczeki­wa­ni­a­mi. Jeśli uży­wamy Mave­na, testy jed­nos­tkowe uruchami­ane są automaty­cznie przy każdym budowa­niu pro­jek­tu, jeśli kto­ryś z testów nie zadzi­ała, cała pro­ce­du­ra jest przerywana.

Testy jed­nos­tkowe są ważne z 2 powodów. Po pier­wsze przy tworze­niu opro­gramowa­nia pozwala­ją upewnić się, że nasz pro­gram dzi­ała poprawnie (a przy­na­jm­niej jego poszczególne częś­ci). Aby taka wery­fikac­ja była wiary­god­na, testy jed­nos­tkowe powin­ny testować nie tylko ‘oczeki­wany’ sce­nar­iusz (tzw. Hap­py case / hap­py path), ale także nieoczeki­wane ele­men­ty – np. błędne dane jako atry­bu­ty metod, brak danych itp. Dobrze napisany test to taki, kto­ry zaskoczył pro­gramistę pisza­cego kod, który testujemy :)

Dru­gi powód to unikanie tzw. regresji – z dużym praw­dopodobieńst­wem kod, który pisze­my, będzie w przyszłoś­ci rozwi­jany i mody­fikowany. Może sie zdarzyć, że takie mody­fikac­je spowodu­ją, że coś, co wcześniej dzi­ałało praw­idłowo, przes­tanie dzi­ałać. Zami­ast czekać na reakc­je niezad­owolonych użytkown­ików, może­my takie prob­le­my wych­wycić już przy budowa­niu naszej aplikacji.

Testy jednostkowe w Maven

Maven jak wspom­ni­ałem wspiera automaty­czne testy jed­nos­tkowe. Aby je utworzyć, najpierw doda­je­my zależność do bib­liote­ki Junit http://mvnrepository.com/artifact/junit/junit, po czym może­my przys­tąpić do tworzenia testow.

Pami­etaj jed­nak, aby testy tworzyć w kat­a­logu src/test/java! Inaczej Maven ich nie będzie ‘widzi­ał’, więc nie zostaną one uruchomione.

Przykładowe testy

Do testowa­nia może­my użyć kil­ka bib­liotek, z których najpop­u­larniejsza to Junit. Dal­szy opis będzie doty­czył wyko­rzys­ta­nia właśnie tej biblioteki.

Nasz test skla­da się z zestawu poje­dynczych testów, z których każdy może testować inny przy­padek. Każdy z takich poje­dynczych przy­pad­ków to osob­na meto­da, która oznacza­my adno­tac­ja @Test . Wewnątrz tej metody real­izu­je­my logikę, po czym za pomocą metod staty­cznych klasy Assert tes­tu­je­my, czy otrzy­mane wyni­ki są zgodne z oczekiwaniami:

public class KalkulatorTest {
    @Test
    public void mnozeniePrzezZeroZwracaZero() {
        Kalkulator kalkulator = new Kalkulator();
        Assert.assertEquals("0 * 0 musi sie rownac zero", 0, kalkulator.mnoz(0, 0));
    }
}

Powyższy test najpierw tworzy obiekt, który tes­tu­je­my, a następ­nie sprawdza, czy wynik metody dla konkret­nych argu­men­tów jest równy oczeki­wane­mu (w tym wypad­ku 0). Pier­wszy argu­ment to infor­ma­c­ja o błędzie — jeśli test się nie powiedzie, taki komu­nikat pojawi się w logach.
Jeśli chodzi o inne metody klasy Assert, zna­jdziemy je w doku­men­tacji JUnit. Z najpop­u­larniejszych moż­na wymienić:

  • assertTrue/assertFalse — sprawdza, czy wynik jest prawdą/fałszem
  • assert­Null — sprawdza, czy wynik jest nullem
  • assertE­quals — sprawdza, czy dwa ele­men­ty są równe (wywołu­je metody equals)
  • fail — powodu­je, że test jest prz­ery­wany i oznaczany jako niezdany

Sprzątanie’ i przygotowanie do testów

JUnit pozwala nam także na wyko­nanie jakiegoś kodu przed i po poje­dynczym teś­cie jak i po całym zestaw­ie testów (czyli klasie, która posi­a­da metody z adno­tac­ja­mi @Test). Służą do tego adno­tac­je @Before, @BeforeClass, @After oraz @AfterClass . Przykład­owa klasa może wyglą­dać następująco:

public class KlasaTestowa {
    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        //ta metoda będzie wywołana raz, przed wszystkimi testami
    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception {
        //ta metoda będzie wywołana raz, po wszystkich testach
    }

    @Before
    public void setUp() throws Exception {
        //ta metoda będzie wywołana przed każdym testem
    }

    @After
    public void tearDown() throws Exception {
        //ta metoda będzie wywołana po każdym teście
    }

    @Test
    public void testujCos() {
        //...
    }
}

Zwróć uwagę, że metody, które są wywoły­wane tylko raz przed i po wszys­t­kich tes­tach są staty­czne! Metody te służą np. do przy­go­towa­nia danych do testów, ‘rese­towa­niu’ zmi­an wprowad­zonych przez poprzed­nie testy, ‘sprzą­ta­niu’ po tes­tach (np. zamyka­niu połączeń itp) — żad­na z nich nie jest obow­iązkowa, ale może­my je wyko­rzys­tać, jeśli potrzebujemy.

Pokrycie kodu (code coverage)

Ter­min ten jest dość częs­to spo­tykany w kon­tekś­cie testów jed­nos­tkowych, w szczegól­noś­ci w dużych fir­ma­ch. Oznacza on ile pro­cent lin­i­jek kodu (nie licząc klamer, deklaracji itp.) jest real­nie wykony­wanych pod­czas wszys­t­kich testów. Inny­mi slowy, wyni­ki wyko­na­nia jakiej częś­ci kodu wery­fiku­je­my (w teorii). Zdarza­ją sie patolo­gie, jak np. wymóg 100% pokrycia (patrz niżej) – ogól­nie w sen­sown­ie napisanym sys­temie (tj. takim, w którym nie pisze­my zbed­nego kodu, uży­wamy adno­tacji, korzys­tamy z CoC itp.) dobrą wartoś­cią jest 70–85% pokrycia (w zależnoś­ci od tech­nologii, logi­ki biz­ne­sowej, złożonoś­ci itp.). Pokrycie poniżej 40% jest z kolei prze­ważnie bard­zo złym sygnalem.

Co testujemy, czego nie testujemy

Tes­tu­je­my przede wszys­tkim logikę biz­ne­sową – czyli środ­kowa warst­wę (ser­wisy). Testy jed­nos­tkowe służą do tego, żeby upewnić się ze pewien pro­ces, algo­rytm został praw­idłowo przełożony na jezyk pro­gramowa­nia. Z tego powodu bard­zo częs­to nie tes­tu­je się w ten sposób kon­trol­erów (do tego prze­ważnie służą testy inte­gra­cyjne) – ponieważ one odpowiada­ją za ‘tłu­macze­nie’ wejś­cia od użytkown­i­ka na wewnętrzne struk­tu­ry sys­te­mu – jak i unikamy testowa­nia klas mod­elu (np. encji) – real­nie może­my przetestować get­tery i set­tery,  a więc w zasadzie tylko to, czy język Java nadal dzi­ała ;) Doda to kil­ka pro­cent do pokrycia, ale nie o to w tes­tach chodzi.

Testy jednostkowe i Spring

Spring wspiera tworze­nie testow jed­nos­tkowych, m.in. pozwala­jąc inicjować kon­tekst nie tylko w ramach aplikacji webowej. Poniżej przyk­lad testów, ktore korzys­ta­ją z kon­tek­stu Spring’a (zdefin­iowanego w pliku root-context.xml):

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/root-context.xml")
public class SpringoweTesty {
    @Autowired
    private JakisSerwis serwisDoTestowania;

    @Test
    public void testujSerwis() {
        Assert.assertTrue(serwisDoTestowania.metodaKtoraPowinnaZwrocicTrue());
    }

    // inne testy ...
}

Dzię­ki takiej kon­fig­u­racji może­my korzys­tać np. z adno­tacji @Autowired, co zde­cy­dowanie upraszcza testowanie poszczegól­nych elementów :)

Podsumowanie

Dzisi­aj nauczyłaś się korzys­tać z pier­wszego z narzędzi służą­cych polep­sza­niu jakoś­ci kodu – wg mnie najważniejszego. Testy jed­nos­tkowe nie są tech­nicznie trud­nym zagad­nie­niem, ale trze­ba sporo wyobraźni, żeby przetestować wszys­tkie (tzn. jak najwięcej) sce­nar­iusze negaty­wne. Ważną rzeczą jest, zeby pamię­tać, czemu służą testy – to nie jest narzędzie do pod­wyzsza­nia code cov­er­age, to narzędzie do zapew­ni­a­nia jakoś­ci – jes­li Twoj sys­tem ma pokrycie 50%, ale cała logi­ka jest przetestowana – super, nie twórz na siłę bezsen­sownych testów :)

Licencja Creative Commons

Jeśli uważasz powyższą lekcję za przy­dat­ną, mamy małą prośbę: pol­ub nasz fan­page. Dzię­ki temu będziesz zawsze na bieżą­co z nowy­mi treś­ci­a­mi na blogu ( i oczy­wiś­cie, z nowy­mi częś­ci­a­mi kur­su Javy). Dzięki!