4it101»J Unit 2

J Unit 2

Úvod do testování v BlueJ

V této části si ukážeme vytvoření testů v BlueJ na jednoduchém příkladu třídy Ctverec s metodami pro výpočet obvodu a obsahu.

Třída Ctverec

Vzhledem k jednoduchosti třídy se trochu zpronevěřím postupu TDD a rovnou napíšu třídu Ctverec s metodami obvod(), obsah(), getStranaA() a konstruktorem:

public class Ctverec {
    private double stranaA;

    public Ctverec(double stranaA) {
        this.stranaA = stranaA;
    }

    public double getStranaA() {
        return stranaA;
    }

    public double obvod() {
        return 4 * stranaA;
    }

    public double obsah() {
        return stranaA * stranaA;
    }
}

Zapnutí nástrojů pro testování v BlueJ

Na začátku se nástroje pro testování v BlueJ nezobrazují, je potřeba v menu Nástroje => Nastavení => Různé zapnout jejich zobrazování:

Bluej nastavení - zobrazit nástroje pro testování
Zapnutí zobrazování nástrojů na testování v nastavení BlueJ

Po zapnutí se zpřístupní nástroje pro testování. Nejviditelnější jsou nová tlačítka v levém panelu, přibude volba "Vytvořit testovací třídu" v kontextovém menu u třídy, přibudou i volby v některých menu.

Nástroje pro testování v BlueJ
Nástroje pro testování v BlueJ

Navrh testů

Prvním krokem by mělo být navržení testů, tj. co testovat a jaké by měly být správné výsledky.

délka stranygetStranaA()obvod()obsah()
552025
0.10.10.40.01

Testy je potřeba vytvořit i pro chybné vstupní parametry, např. pro situaci, kdy někdo zadá zápornou délku strany. To si ukážeme na konci článku


Vytvoření testovací třídy, nahrání prvního testu

Jednotkové testy se píší do samostatné třídy. Z toho též vyplývá, že pomocí jednotkových testů lze testovat pouze veřejné rozhraní testované třídy, nelze volat privátní metody či se "podívat" do privátních datových atributů.

Testovací třída se vytvoří v kontextovém menu k testované třídě - pomocí volby Vytvořit testovací třídu. Vznikne třída s názvem CtverecTest - ke jménu testované třídy se přidá slovo Test.

BlueJ umožňuje vytvořit "klikáním" jednoduché testy/testovací metody. Ukážeme si to na testu, kterým ověříme, že metoda getStranaA() vrací správnou délku strany. Tj. vytvoříme instanci třídy Ctverec s délkou strany např. 5, zavoláme metodu getStranaA() a zkontrolujeme, že vrátí hodnotu 5.

  1. V kontextovém menu pro třídu CtverecTest zvolte volbu Vytvořit testovací metodu
  2. Testovací metodu je potřeba pojmenovat - použíjeme název testStranaA - je dobrým zvykem začít jméno testovací metody slovem test,
  3. V levém panelu se rozsvítí záznam testu - vše co nyní uděláme, se zaznamená do testovací metody. Záznam skončí stisknutím tlačítka Ukončit.
  4. Vytvoříme instanci třídy Ctverec - v kontextovém menu ke třídě Ctverec zvolíme vytvoření instance (new). V následujícím okně zadáme parametr konstruktoru - délku strany 5. Vytvoření instance se doplní do testovací metody.
  5. V kontextovém menu u instance v dolním panelu zvolíme zavolání metody getStrana(). Objeví se okno "Návratová hodnota metody", ve kterém je zobrazena vrácená hodnota, vstupní pole pro očekávanou hodnotu a vstupní pole pro odchylku.
  6. Je potřeba doplnit očekávanou hodnotu (číslo 5) a odchylku (např. 0.00001). Když stiknete tlačítko Zavřít, doplní se do testovací metody test na ověření, že vrácená hodnota se rovná 5 s přípustnou odchylkou 0.00001.
  7. Ukončete nahrávání testu pomocí tlačítka Ukončit v levém menu.

Odchylka se zadává pouze u reálných čísel, neboť reálná čísla nelze v mnoha případech přesně uložit do omezené velikosti počítačové paměti.


Spuštění testů

Spustit vytvořený test lze několika způsoby:

  • přes tlačítko "Spustit testy" - spustí se všechny testy v projektu/balíčku,
  • přes volby v kontextovém menu k testovací třídě - lze spustit buď jedno konkrétní testovací metodu nebo všechny testovací metody ze třídy,

Výsledky testů se zobrazí v samostatném okně:


Bluej, okno s výsledky testu

Pokud všechny testy projdou, je uprostřed zelený pruh. Test může ale i selhat (návratová hodnota metody neodpovídá očekávané hodnotě) či test může skončit chybou (v průběhu testu vznikla neodchycená výjimka). Když si vyberete neúspěšný test, můžete pomocí tlačítka "Zobrazit zdrojový kód" přejít na odpovídající řádek v testovací třídě.


Zdrojový kód testovací třídy

Následuje zdrojový kód testovací třídy CtverecTest (jsou smazány komentáře):

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

public class CtverecTest
{

    @Before
    public void setUp() {
    }

    @After
    public void tearDown() {
    }

    @Test
    public void testStranaA() {
        Ctverec ctverec1 = new Ctverec(5);
        assertEquals(5, ctverec1.getStranaA(), 0.00001);
    }
}
 

Anotace (z anglického annotation - poznámka, vysvětlivka) je dodatečná informace (metadata), která ovlivňuje překlad či provádění kódu. Jsou tři oblasti použití:

  1. dodatečná informace pro překladač - sem patří anotace @Override, kterou se označuje metoda, která překrývá metodu z předka či z rozhraní,
  2. vygenerování části kódu či XML souborů,
  3. ovlivnění průběhu provádění kódu - např. dle přítomnosti anotace @Test se vybírají metody, které se mají spouštět jako testy.

Anotace začínají znakem @ (zavináč), formálně patří mezi modifikátory - podobně jako public či static se píší se před metodu/třídu/deklaraci.

Testovací třída má na začátku uvedeny čtyři řádky import, které zjednodušují psaní názvů tříd a metod z jiných balíčků. Např. není nutné psát celý název org.junit.Test, ale lze nyní napsat jen krátký název Test. Dále jsou ve třídě uvedeny tři metody setUp(), tearDown() a stranaA(). Před každou z nich jsou uvedeny anotace.

  • anotace @Before - označuje metodu, která se provádí před zavoláním jednotlivé testovací metody, tato metoda se obvykle jmenuje setUp(). Nyní je prázdná.
  • anotace @After - označuje metodu, která se provádí po každé testovací metodě, tato metoda se obvykle jmenuje tearDown(). Nyní je prázdná.
  • anotace @Test - označuje jednu testovací metodu.

V metodě assertEquals se porovnává očekávaná hodnota (první parametr) se skutečnou hodnotou, kterou vrátí volání metody ctverec1.getStranaA(). Test projde, pokud rozdíl mezi očekávanou a skutečnou hodnotou je menší, než třetí parametr (rozdíl/delta). Knihovna JUnit poskytuje více metod pro porovnání, jejich přehled je v kapitole Přehled metod třídy Assert.


Doplnění dalších testů

Doplníme testy pro metody obvod() a obsah():

    @Test
    public void testObsah() {
        Ctverec ctverec1 = new Ctverec(5);
        assertEquals(20, ctverec1.obvod(), 0.00001);
    }

    @Test
    public void testObvod() {
        Ctverec ctverec1 = new Ctverec(5);
        assertEquals(25, ctverec1.obsah(), 0.00001);
    }

V jedné testovací metodě nemusí být pouze jedno volání metody assertEquals. Více porovnání má dvě nevýhody:

  • porovnání se provádějí do prvního selhání, ostatní se již nekontrolují,
  • pokud nějaký test selže, tak se vypíše jméno metody - obtížnější se zjišťuje, které porovnání selhalo.

Jednu metodu s více porovnáními si ukážeme na testu pro čtverec s délkou strany 0.1 (viz Navrh testů).

    @Test
    public void testNulaJedna() {
        Ctverec ctverec1 = new Ctverec(0.1);
        assertEquals(0.1, ctverec1.getStranaA(), 0.00001);
        assertEquals(0.4, ctverec1.obvod(), 0.00001);
        assertEquals(0.01, ctverec1.obsah(), 0.00001);
    }

Po napsání každého testu, po každé ucelené úpravě/opravě v kódu třídy je vhodné projekt přeložit a spustit testy:


Bluej, okno s výsledky testů 2

Všimněte si, že se testy spouští v náhodném pořadí.


Záporná délka strany, selhání testu

V následující tabulce je přehled variant ošetření záporné délky strany:

vardélka stranygetStranaA()obvod()obsah()komentář
1.-2výjimka v konstruktoruvznikne výjimka v konstruktoru, nevytvoří se instance
2.-2-2Double.NaNDouble.NaNmetody obvod, obsah vrací Not-a-Number
3.-2Double.NaNDouble.NaNDouble.NaNmetody getStranaA, obvod, obsah vrací Not-a-Number
4.-2284použije se absolutní hodnota strany

Nelze říct, že některá varianta je správnější než druhá. Závisí na způsobu využívání třídy Ctverec a na volbě autora třídy. Vytvořením testů navrhujete chování třídy.

Já dále budu předpokládat variantu 2, tj. metoda getStrana() vrátí chybnou délku, metody obvod() a obsah() vrátí konstantu Double.NaN - Not-a-Number. Nejdříve vytvořím testovací metodu:

    @Test
    public void testZaporna() {
        Ctverec ctverec1 = new Ctverec(-2);
        assertEquals(-2, ctverec1.getStranaA(), 0.00001);
        assertEquals(Double.NaN, ctverec1.obvod(), 0.00001);
        assertEquals(Double.NaN, ctverec1.obsah(), 0.00001);
    }

Spustím testy, nová testovací metoda by neměla projít - je to minimální ověření toho, že jsme test napsali dobře.


BlueJ selhání testu při záporné délce strany

Pokud nějaký test selže, zobrazí se uprostřed okna s výsledky testů červený pruh. Když se zvolí neúspěšný test, zobrazí v dolní části okna důvod selhání - v našem případě byla očekávána hodnota NaN, ale skutečná hodnota byla -8. V testovací metodě testZaporna() je více porovnání (více assertEquals), z výpisu není jasné, které selhalo. Na konkrétní řádek se lze dostat pomocí tlačítka "Zobrazit zdrojový kód".

Nyní upravím metody obvod() a obsah() ve třídě Ctverec:

    public double obvod() {
        if (stranaA >= 0) {
            return 4 * stranaA;
        }
        else {
            return Double.NaN;
        }
    }

    public double obsah() {
        if (stranaA >= 0) {
            return stranaA * stranaA;
        }
        else {
            return Double.NaN;
        }
    }

A nyní mi testy projdou:


Bluej, okno s výsledky testů 3