Hlavní navigace

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.

Našli jste v článku chybu?

10. 1. 2013 1:30

Clock (neregistrovaný)

To mi pripomina jak mi BLEK jednou rikal jak jednou vyhral nejakou programovaci olympiadu tim, ze misto aby implementoval zadani, implementoval pouze to aby to proslo testem overujici spravnost.

Vsiml si totiz, ze pro vysoka vstupni n je pravdepodobnost, ze program vrati "ANO" extremne nizka, a tak tam zadratoval, ze to od urciteho n vrati fixne "NE" cimz se rychlost vypoctu extremne zrychlila a diky tomu to vyhral. Pravdepodobnost ze bude vadnost programu odhalena pritom byla velmi nizka.

Pri…

10. 1. 2013 14:43

TDD (neregistrovaný)

Doporucuji Growing object oriented software guided by tests. Jedna z nejlepsich knih, kterou jsem o TDD cetl.
http://www.growing-object-oriented-software.com/


Vitalia.cz: Vláknina: Rozpustná, nebo nerozpustná?

Vláknina: Rozpustná, nebo nerozpustná?

Podnikatel.cz: Dárky v podnikání. Jak je uplatnit v daních?

Dárky v podnikání. Jak je uplatnit v daních?

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Vitalia.cz: Žloutenka v Brně: Nakaženo bylo 400 lidí

Žloutenka v Brně: Nakaženo bylo 400 lidí

Vitalia.cz: Jak koupit Mikuláše a nenaletět

Jak koupit Mikuláše a nenaletět

DigiZone.cz: Další dva kanály nabídnou HbbTV

Další dva kanály nabídnou HbbTV

Měšec.cz: U levneELEKTRO.cz už reklamaci nevyřídíte

U levneELEKTRO.cz už reklamaci nevyřídíte

Lupa.cz: Propustili je z Avastu, už po nich sahá ESET

Propustili je z Avastu, už po nich sahá ESET

Měšec.cz: Kdy vám stát dá na stěhování 50 000 Kč?

Kdy vám stát dá na stěhování 50 000 Kč?

Root.cz: Vypadl Google a rozbilo se toho hodně

Vypadl Google a rozbilo se toho hodně

Lupa.cz: Avast po spojení s AVG propustí 700 lidí

Avast po spojení s AVG propustí 700 lidí

DigiZone.cz: Recenze Westworld: zavraždit a...

Recenze Westworld: zavraždit a...

DigiZone.cz: NG natáčí v Praze seriál o Einsteinovi

NG natáčí v Praze seriál o Einsteinovi

Podnikatel.cz: EET zvládneme, budou horší zákony

EET zvládneme, budou horší zákony

Vitalia.cz: Říká amoleta - a myslí palačinka

Říká amoleta - a myslí palačinka

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

DigiZone.cz: Sony KD-55XD8005 s Android 6.0

Sony KD-55XD8005 s Android 6.0

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Vitalia.cz: Chtějí si léčit kvasinky. Lék je jen v Německu

Chtějí si léčit kvasinky. Lék je jen v Německu