Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

2. diel - Testovanie v Jave - Prvý unit test v JUnit

V minulej lekcii online kurzu o testovanie aplikácií v Jave sme si urobili pomerne solídny úvod do problematiky. Tiež sme si uviedli v-model, ktorý znázorňuje vzťah medzi jednotlivými výstupmi fázou návrhu a príslušnými testami.

Testy teda píšeme vždy na základe návrhu, nie implementácie. Inými slovami, robíme je na základe očakávanej funkčnosti. Tá môže byť buď priamo od zákazníka (a to v prípade akceptačných testov) alebo už od programátora (architekta), kde špecifikuje ako sa má ktorá metóda správať. Dnes sa budeme venovať práve týmto testom, ktorým hovoríme jednotkové (unit testy) a ktoré testujú detailnú špecifikáciu aplikácie, teda jej triedy.

Pamätajte, že nikdy nepíšeme testy podľa toho, ako je niečo vnútri naprogramované! Veľmi jednoducho by to mohlo naše myslenie zviesť len tým daným spôsobom a zabudli by sme na to, že metóde môžu prísť napríklad aj iné vstupy, na ktoré nie je vôbec pripravená. Testovanie s implementáciou v skutočnosti vôbec nesúvisí, vždy testujeme či je splnené zadanie.

Aké triedy testujeme

Unit testy testujú jednotlivé metódy v triedach. Pre istotu zopakujem, že nemá veľký zmysel testovať jednoúčelové metódy napr. V bean alebo JavaFX aplikáciách, ktoré napr. Len niečo vyberajú z databázy. Aby sme boli konkrétnejší, nemá zmysel testovať metódu ako je táto:

public void vlozPolozku(string nazev, double cena) {
    try (Connection spojeni = DriverManager.getConnection("jdbc:mysql://localhost/aplikace_db?user=root&password=");
            PreparedStatement dotaz = spojeni.prepareStatement("INSERT INTO polozka (nazev, cena) VALUES (?, ?)");) {
            dotaz.setString(1, nazev);
            dotaz.setDouble(2, cena);
    } catch (SQLException ex) {
            System.err.println("Chyba při komunikaci s databází");
    }
}

Metóda pridáva položku do databázy. Typicky je použitá len v nejakom formulári a ak by nefungovala, zistí to akceptačné testy, pretože by sa nová položka neobjavila v zozname. Podobných metód je v aplikácii veľa a zbytočne by sme strácali čas pokrývaním niečoho, čo ľahko pokryjeme v iných testoch.

Unit testy nájdeme najčastejšie u knižníc, teda nástrojov, ktoré programátor používa na viacerých miestach alebo dokonca vo viacerých projektoch a mali by byť 100% funkčný. Možno si spomeniete, kedy ste použili nejakú knižnicu, stiahnutú napr. Z GitHub. Veľmi pravdepodobne u nej boli tiež testy, ktoré sa najčastejšie vkladajú do zložky "test", ktorá je vedľa zložky "src" v adresárovej štruktúre projektu. Ak napr. Píšeme aplikáciu, v ktorej často potrebujeme nejaké matematické výpočty, napr. Faktoriál a ďalšie pravdepodobnostné funkcie, je samozrejmosťou vytvoriť si na tieto výpočty knižnicu a je veľmi dobrý nápad pokryť takú knižnicu testy.

Príklad

Ako asi tušíte, my si podobnú triedu vytvoríme a skúsime si ju otestovať. Aby sme sa nezdržovali, vytvorme si iba jednoduchú kalkulačku, ktorá bude vedieť:

  • sčítať
  • odčítať
  • násobiť
  • deliť

Vytvorenie projektu

V praxi by v triede boli nejaké zložitejšie výpočty, ale tým sa tu zaoberať nebudeme. Vytvorte si nový projekt s názvom UnitTesty a do neho si pridajte triedu Kalkulacka a nasledujúce implementácií:

package unittesty;

/**
 * Reprezentuje jednoduchou kalkulačku
 */
public class Kalkulacka {

    /**
     * Sečte 2 čísla
     * @param a První číslo
     * @param b Druhé číslo
     * @return Součet 2 čísel
     */
    public double secti(double a, double b) {
        return a + b;
    }

    /**
     * Odečte 2 čísla
     * @param a První číslo
     * @param b Druhé číslo
     * @return Rozdíl 2 čísel
     */
    public double odecti(double a, double b) {
        return a - b;
    }

    /**
     * Vynásobí 2 čísla
     * @param a První číslo
     * @param b Druhé číslo
     * @return Součin 2 čísel
     */
    public double vynasob(double a, double b) {
        return a * b;
    }

    /**
     * Vydělí 2 čísla
     * @param a První číslo
     * @param b Druhé číslo
     * @return Podíl 2 čísel
     */
    public double vydel(double a, double b) {
        if (b == 0)
            throw new IllegalArgumentException("Nelze dělit nulou!");
        return a / b;
    }

}

Na kódu je zaujímavá iba metóda vydel(), ktorá vyvolá výnimku v prípade, že delíme nulou. Predvolené správanie Javy u desatinných čísel je vrátenie hodnoty "Infinity" (nekonečno), čo v aplikácii nie je vždy to, čo používateľ očakáva.

Generovanie testov

V Jave pre testy používame framework JUnit. Ten by mal byť súčasťou NetBeans, ak by ste ho náhodou nemali nainštalovaný, možno ho jednoducho pridať v menu Tools -> Plugins. V ľavom paneli Projects klikneme na projekt pravým tlačidlom a zvolíme New -> Other.

Pridanie unit testu v NetBeans v Jave - Testovanie v Jave

V nasledujúcom dialógovom okne vyberieme kategórii Unit Tests a typ súboru JUnit Test. Tým hovoríme, že pridávame nový unit test pre nejakú triedu.

Pridanie JUnit testu v Jave - Testovanie v Jave

V poslednom okne zadáme názov testu, ktorý sa spravidla zostavuje ako názov testované triedy + slovo "Test", v našom prípade teda "KalkulackaTest". Testy zvyčajne pridávame do balíčka "Test Packages".

Dokončenie pridanie unit testu v Jave - Testovanie v Jave

Potvrdíme a vygeneruje sa nám nový súbor s nasledujúcim kódom:

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;

public class KalkulackaTest {

    public KalkulackaTest() {
    }

    @BeforeClass
    public static void setUpClass() {
    }

    @AfterClass
    public static void tearDownClass() {
    }

    @Before
    public void setUp() {
    }

    @After
    public void tearDown() {
    }

    // TODO add test methods here.
    // The methods must be annotated with annotation @Test. For example:
    //
    // @Test
    // public void hello() {}
}

Asi vás v objektovej Jave neprekvapí, že je test triedy (scenár) reprezentovaný tiež triedou a jednotlivé testy metódami :) Čo je už zaujímavejšie je fakt, že na ňu nájdeme niekoľko predpripravených metód, ktoré sú označené anotáciami. setUpClass() sa zavolá raz na začiatku, pred všetkými testami. tearDownClass() funguje podobne, zavolá sa raz na konci, až všetky testy prebehnú.

Pokrytie triedy testy

Metódy setUp() a tearDown(), presnejšie metódy s anotáciami @Before a @After, sa zavolajú pred, resp. po každom teste v tejto triede. To je pre nás veľmi dôležité, pretože podľa best practices chceme, aby boli testy nezávislé. Obvykle teda pred každým testom pripravujeme znovu to isté prostredie, aby sa vzájomne vôbec neovplyvňovali. O dobrých praktikách sa zmímíne detailnejšie v nasledujúcej lekcii. Do triedy si pridajme atribút kalkulacka a v metóde setUp() v ňom vždy vytvorme čerstvo novú kalkulačku pre každý test. Pokiaľ by ju bolo ešte potrebné ďalej nastavovať alebo bolo treba vytvoriť ďalšie závislosti, boli by tiež v tejto metóde:

public class KalkulackaTest {

    private Kalkulacka kalkulacka;

    public KalkulackaTest() {
    }

    @BeforeClass
    public static void setUpClass() {
    }

    @AfterClass
    public static void tearDownClass() {
    }

    @Before
    public void setUp() {
        // Nová kalkulačka je vytvořena před každým testem pro garantování jejich nezávislosti
        kalkulacka = new Kalkulacka();
    }

    ...

Pozn .: Keďže sú testy v inom balíčku než triedy aplikácie, importujte si triedu Kalkulacka kliknutím na ikonu vľavo pri príslušnom riadku.

Máme všetko pripravené na pridávanie samotných testov. Jednotlivé metódy budú vždy označené anotácií @Test a budú testovať jednu konkrétnu metódu z triedy Kalkulacka, typicky pre niekoľko rôznych vstupov. Ak vás napadá prečo metódy označujeme anotáciami, umožňuje nám to vytvoriť si aj pomocné metódy, ktoré môžeme v danom teste využívať a ktoré nebudú považované za testy. NetBeans nám totiž testy (metódy s anotáciou @Test) automaticky spustí a vypíše ich výsledky. V starších verziách JUnit museli miesto anotácií metódy začínať na "test" a trieda dedila zo scenára (triedy TestCase).

Pridajme nasledujúcich 5 metód:

@Test
public void scitani() {
    assertEquals(2, kalkulacka.secti(1, 1), 0);
    assertEquals(1.42, kalkulacka.secti(3.14, -1.72), 0.001);
    assertEquals(2.0/3, kalkulacka.secti(1.0/3, 1.0/3), 0.001);
}

@Test
public void odcitani() {
    assertEquals(0, kalkulacka.odecti(1, 1), 0);
    assertEquals(4.86, kalkulacka.odecti(3.14, -1.72), 0.001);
    assertEquals(2.0/3, kalkulacka.odecti(1.0/3, -1.0/3), 0.001);
}

@Test
public void nasobeni() {
    assertEquals(2, kalkulacka.vynasob(1, 2), 0);
    assertEquals(-5.4008, kalkulacka.vynasob(3.14, -1.72), 0.001);
    assertEquals(0.111, kalkulacka.vynasob(1.0/3, 1.0/3), 0.001);
}

@Test
public void deleni() {
    assertEquals(2, kalkulacka.vydel(4, 2), 0);
    assertEquals(-1.826, kalkulacka.vydel(3.14, -1.72), 0.001);
    assertEquals(1, kalkulacka.vydel(1.0/3, 1.0/3), 0);
}

@Test(expected=IllegalArgumentException.class)
public void deleniVyjimka() {
    kalkulacka.vydel(2, 0);
}

K porovnávanie výstupu metódy s očakávanou hodnotou používame metódy Assert *, staticky importované z balíčka org.junit.Assert. Najčastejšie asi použijete assertEquals(), ktorá prijíma ako prvý parameter očakávanú hodnotu a ako druhý parameter hodnotu aktuálny. Toto poradie je dobré dodržiavať, inak budete mať hodnoty vo výsledkoch testov opačne. Ako asi viete, desatinné čísla sú v pamäti počítača reprezentovaná binárne (ako inak :) ) A to spôsobí určitú stratu ich presnosti a tiež určité ťažkosti pri ich porovnávaní. Preto musíme v tomto prípade zadať aj tretí parameter a to je delta, teda kladná tolerancia, o koľko sa môže očakávaná a aktuálna hodnota líšiť, aby test stále prešiel. Všimnite si, že skúšame rôzne vstupy. Sčítanie netestujeme len ako 1 + 1 = 2, ale skúsime celočíselné, desatinné aj negatívne vstupy, oddelene, a overíme výsledky. V niektorých prípadoch by nás mohla zaujímať tiež maximálna hodnota dátových typov a podobne.

Posledný test overuje, či metóda vydel() naozaj vyvolá výnimku pri nulovom deliteľmi. Ako vidíte, nemusíme sa zaťažovať s try-catch bloky, do anotácie stačí iba pridať parameter expected a uviesť tu triedu výnimky, ktorá sa očakáva. Ak výnimka nenastane, test zlyhá. Pre testovanie viac prípadov vyvolanie výnimky týmto spôsobom by bolo treba pridať viac metód. K testovaniu výnimiek sa ešte vrátime nabudúce.

Dostupné Assert metódy

Okrem metódy assertEquals() môžeme použiť ešte niekoľko ďalších, určite sa snažte použiť tú najviac vyhovujúce metódu, sprehľadňuje to hlášky pri zlyhaní testov a samozrejme aj následnú opravu.

  • assertArrayEquals () - Skontroluje, či 2 polia obsahujú tie isté prvky.
  • assertEquals () - Skontroluje, či sú 2 hodnoty rovnaké (porovnáva pomocou equals()).
  • assertFalse () - Skontroluje, či je hodnota false.
  • assertNotEquals () - Skontroluje, či 2 hodnoty nie sú rovnaké.
  • assertNotNull () - Skontroluje, či hodnota nie je null.
  • assertNotSame () - Skontroluje, či 2 referencie neukazujú na rovnaký objekt.
  • assertSame () - Skontroluje, či 2 referencie ukazujú na rovnaký objekt (porovnáva pomocou ==).
  • assertTrue () - Skontroluje, či je hodnota true.

Metóda assertThat() umožňuje novšie prístup k písanie assercí, ale tú si vysvetlíme až nabudúce.

Spustenie testov

Testy spustíme kliknutím pravým tlačidlom na projekt a vybraním položky "Test" z kontextového menu.

Spustenie unit testov v Jave - Testovanie v Jave

NetBeans nám pekne vizuálne ukáže priebeh testov (tie naše budú v okamihu hotové) a tiež výsledky. Zobrazenie aj úspešne prebehnutých testov zapnete pomocou ikony zelenej fajky.

Výsledok unit testov v NetBeans - Testovanie v Jave

Skúsme si teraz urobiť v kalkulačke chybu, napr. Zakomentujte vyvolávanie výnimky pri delení nulou:

public double vydel(double a, double b) {
    //if (b == 0)
    //    throw new IllegalArgumentException("Nelze dělit nulou!");
    return a / b;
}

A spustite znovu naše testy:

Neúspešný výsledok testov v NetBeans - Testovanie v Jave

Vidíme, že chyba je zachytená a sme na ňu upozornení. Môžeme kód vrátiť späť do pôvodného stavu. Nabudúce sa pozrieme na zmeny v JUnit, uvedieme si knižnicu Hamcrest, naučíme sa používať pravidlá a zmienime tie najdôležitejšie best practices pre písanie testov.


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 107x (26.89 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Java

 

Predchádzajúci článok
Úvod do testovania softvéru v Jave
Všetky články v sekcii
Testovanie v Jave
Preskočiť článok
(neodporúčame)
Testovanie v Jave - Hamcrest, JUnit TestRule a best practices
Článok pre vás napísal David Hartinger
Avatar
Užívateľské hodnotenie:
1 hlasov
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David sa informačné technológie naučil na Unicorn University - prestížnej súkromnej vysokej škole IT a ekonómie.
Aktivity