Test-driven development v jazyce Java

Vlastimil Pospíchal 10. 1. 2013

Test-driven development (dále jen TDD) neboli programování řízené testy se používá k ladění jednotlivých modulů aplikací. Pokud si vyhledáme články o TDD, zpravidla nalezneme popis nějaké knihovny JUnit pro Javu nebo PHPunit pro PHP. Přitom je možné začít programovat stylem TDD i bez použití těchto nástrojů.

K čemu je TDD vlastně dobré?

Žádný programátor nepíše bez chyb. Před ostrým nasazením programu si ho minimálně jednou musí spustit, aby zjistil, zda program dělá vše, co je od něj požadováno. Jak praví folklór, v každém programu je minimálně jedna chyba. Některé chyby odhalí kompilátor, ale mnoho jich kompilací projde. Projevují se neočekávaným chováním programu. Programátor tedy vykonává periodickou činnost stylem oprava – kompilace – testování. Pokud bychom tento cyklus chtěli automatizovat, tak na opravy programu kromě lepšího editoru nic nepomůže a kompilátor už asi také nevylepšíme. Zbývají testy.

Ano, testy. Můžeme je dělat ručně, ale pokud je budeme dělat stále dokola, bude nás to zdržovat. Proto si na ně napíšeme program. Nemusí být nijak estetický a ani nemusí být ve stejném jazyku, ve kterém bude výsledná aplikace. Často se pro testování používá skriptovací jazyk, i když výsledná aplikace je kompilovaná.

Chceme testovat v Javě. Jak na to?

Programovací jazyk Java od verze 1.4 nabízí přímo prostředky pro ladění programů. Stačí použít klíčové slovo assert s jednoduchou syntaxí:

assert podmínka : "chybové hlášení";

které znamená: „Pokud není splněna podmínka, vypiš chybové hlášení a přeruš vykonávání programu.“ Chování je tedy obráceně, než jak je to u podmínky if. Chybové hlášení (včetně dvojtečky) je nepovinné, příkaz přesto v případě nepravdivosti podmínky zahlásí místo v programu, kde k chybě došlo. Výpis je tedy pro ladění mnohem užitečnější, než System.err.writeln(). Ve zkrácené verzi tedy stačí jen:

assert podmínka;

Ve skutečnosti program nemusí být ukončen. Je vyvolána výjimka AssertionError, která je potomkem třídy Error. Není nutné ji ošetřovat a zpravidla to ani není vhodné. Je jen dobré vědět, že je to možné.

Jednoduchý příklad

Potřebujeme třídu, která nám bude uchovávat souřadnice [x,y] v objektu a dokáže spočítat vzdálenost mezi dvěma objekty na ploše.

Nejprve napíšeme základ testu

Zní to podivně, ale ještě před prvním písmenkem programu začneme psát test. Co do něj napíšeme? Zadání vyjádřené ve výrazech. Je zvyklostí testy pojmenovávat s předponou „test“. Nejprve si napíšeme základ testu. Osobně jsem si tento základ vložil do šablony, takže se mi objeví pokaždé, když založím nový soubor.

class testPoint {
    public static void main(String[] args) {
        boolean assertsEnabled = false;
        assert assertsEnabled = true;
        if (!assertsEnabled)
            throw new RuntimeException("Asserty se zapínají parametrem -ea");
    }
}

Třída a v ní jedna metoda main. Uvnitř jsou čtyři trochu podivné řádky. Jsou tam proto, abych nezapomněl při spuštění testu povolit asserty. Pokud bych na to zapomněl, mohl bych si myslet, že testy proběhly v pořádku.

Za tento základ nyní napíšeme první test

class testPoint {
    public static void main(String[] args) {
        boolean assertsEnabled = false;
        assert assertsEnabled = true;
        if (!assertsEnabled)
            throw new RuntimeException("Assert se zapíná parametrem -ea");
        Point a = new Point(1.0, 2.0);
        Point b = new Point(4.0, 6.0);
    }
}

Vytvoříme dvě instance třídy Point. Konstruktoru budeme chtít předat parametry [x,y]. Soubor uložíme a necháme zkompilovat. Dle očekávání kompilátor ohlásí chybu:

testPoint.java:12: cannot find symbol
symbol  : class Point
location: class testPoint

Mohlo by se stát, že by chybu nezahlásil?

Ano, mohlo. Mohl by najít nějakou existující třídu s názvem Point a pokusil by se ji použít. Možná by nám i vyhovovala a mohli bychom se rozhodnout, že je pro naši úlohu vhodná, ale to teď pomiňme a předpokládejme, že třída Point zatím neexistuje. Je tedy nutné ji napsat.

class Point {
    private double x, y;

        public Point(double x, double y) {
            this.x = x;
            this.y = y;
        }
}

Nyní spustíme test

Skvělé, test proběhl OK. Jenže třída zatím skoro nic nedělá. Proč nic nedělá? Protože jsme od ní v testu skoro nic nepožadovali. Rozšíříme tedy požadavky:

class testPoint {
    public static void main(String[] args) {
        boolean assertsEnabled = false;
        assert assertsEnabled = true;
        if (!assertsEnabled)
            throw new RuntimeException("Assert se zapíná parametrem -ea");

            Point a = new Point(1.0, 2.0);
            Point b = new Point(4.0, 6.0);
        assert 5.0 == a.diff(b) : "Očekávám výsledek 5.0, ale vyšlo " + a.diff(b);
    }
}

Opět spustíme test

Už při kompilaci testu opět dostaneme chybové hlášení, že nám tato metoda chybí.

javac testPoint.java
testPoint.java:14: cannot find symbol
symbol  : method diff(Point)

Tak ji dopíšeme

class Point {
    private double x, y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double diff(Point druhy) {
        return 5.0;
    }
}

Znovu spustíme test

Nyní proběhne OK. Jenže všichni vidíme, že jsem toho dosáhl podfukem. Proto platí zásada: „Nikdy nevěřte testům, které neselžou.“ Upravíme tedy testy:

class testPoint {
    public static void main(String[] args) {
        boolean assertsEnabled = false;
        assert assertsEnabled = true;
        if (!assertsEnabled)
            throw new RuntimeException("Assert se zapíná parametrem -ea");

        Point a = new Point(1.0, 2.0);
        Point b = new Point(4.0, 6.0);
        assert 5.0 == a.diff(b) : "Očekávám výsledek 5.0, ale vyšlo "
+ a.diff(b);

        Point c = new Point(6.0, 14.0);
        assert 13.0 == a.diff(c) : "Očekávám výsledek 13.0, ale vyšlo
" + a.diff(c);
    }
}

Je možné přidat i víc, ale tohle nám v tuto chvíli bude stačit. Překladač žádnou chybu nehlásí, ale je asi jasné, že takový test neprojde:

Exception in thread "main" java.lang.AssertionError: Očekávám výsledek 13.0, ale vyšlo 5.0

Bylo by dobré si přestat hrát s konstantou a napsat správně funkci na výpočet vzdálenosti:

class Point {
    private double x, y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double diff(Point druhy) {
        double x = this.x - druhy.x;
        double y = this.y - druhy.y;
        return Math.sqrt(x*x + y*y);
    }
}

Hotovo

Výborně, vše pracuje přesně jak má. Dle vlastního uvážení opět dopíšeme do testu několik assertů a znovu spustíme test. Pokud je vše v pořádku, jsme hotovi. Testy nemažeme, uchováme si je pro případy upgrade částí systému, běhového prostředí a jiných změn, o kterých předem nevíme. Pokud obdržíme od klienta hlášení, že program má neobvyklé chování pro určité kombinace vstupů, přidáme je do testu. Pokud bude chtít další metody, opět nejprve přidáme do testu jejich volání, spustíme test a podle výsledku testu dopíšeme a otestujeme chybějící část kódu.

Ohodnoťte jako ve škole:

Průměrná známka 3,15

Našli jste v článku chybu?
Zasílat nově přidané názory e-mailem
Vitalia.cz: 7 nemocí očí, které musíte léčit včas

7 nemocí očí, které musíte léčit včas

Podnikatel.cz: Přechod z OSVČ na firmu? Totální šok!

Přechod z OSVČ na firmu? Totální šok!

Lupa.cz: Jak EET vidí ajťák aneb Drahá vražda UX

Jak EET vidí ajťák aneb Drahá vražda UX

120na80.cz: Kontaktní čočky jako stvořené na dovolenou

Kontaktní čočky jako stvořené na dovolenou

120na80.cz: Tady se vaří padělané léky

Tady se vaří padělané léky

DigiZone.cz: Podzim přinese sport Viasat Ultra HD

Podzim přinese sport Viasat Ultra HD

120na80.cz: 5 triků, jak zastavit krvácení po holení

5 triků, jak zastavit krvácení po holení

DigiZone.cz: O2TV zve na souboj Ledecké s Myslivcovou

O2TV zve na souboj Ledecké s Myslivcovou

DigiZone.cz: Konec geoblokace? Ani náhodou…

Konec geoblokace? Ani náhodou…

Vitalia.cz: Vydával se za český, prozradila ho DNA

Vydával se za český, prozradila ho DNA

Podnikatel.cz: Když už je sexy, tak ať taky funguje

Když už je sexy, tak ať taky funguje

Vitalia.cz: 10 rad šéfkuchařů pro perfektní grilování

10 rad šéfkuchařů pro perfektní grilování

DigiZone.cz: Šlágr TV dostala pokutu 100 000 Kč

Šlágr TV dostala pokutu 100 000 Kč

120na80.cz: Zjistěte, zda je vaše klíště infikované

Zjistěte, zda je vaše klíště infikované

120na80.cz: Jak si udržet zdravou vaginu

Jak si udržet zdravou vaginu

DigiZone.cz: V RS7 ukončila vysílání Retro Music Television

V RS7 ukončila vysílání Retro Music Television

Vitalia.cz: Před, nebo po snídani? Kdy je lepší čistit si zuby

Před, nebo po snídani? Kdy je lepší čistit si zuby

Podnikatel.cz: Různé podoby lahve Coca–Coly. Úchvatné

Různé podoby lahve Coca–Coly. Úchvatné

Podnikatel.cz: Šizený guláš na pultě. Jako Lidl to nedělejte

Šizený guláš na pultě. Jako Lidl to nedělejte

Vitalia.cz: Grilujte v parku i na loďce

Grilujte v parku i na loďce