A tesztvezérelt fejlesztés (test-driven development, TDD) egy szoftverfejlesztési folyamat, ami egy nagyon rövid fejlesztési ciklus ismételgetésén alapul, tehát a követelményeket a fejlesztők speciális tesztesetekként fogalmazzák meg, a kódot pedig ehhez mérten írják meg, így az át fog menni a teszten. Ez tökéletes ellentéte a hagyományos szoftverfejlesztésnek, ami megengedi olyan kódrészletek meglétét is, amelyek nem felelnek meg teljesen a követelményeknek.
Kent Beck amerikai szoftvermérnököt tartják a technika kifejlesztőjének, úgymond annak az „újra-felfedezéséért”, 2003-ban úgy fogalmazott, hogy a tesztvezérelt fejlesztés ösztönzi az egyszerű tervezési elemek használatát, valamint hatással van az önbizalom növekedésére.
A TDD összefüggésben áll az extrém programozás koncepciójával, miszerint először teszteljünk, és ha minél többet tesztelünk, annál több hibát tudunk kiküszöbölni a kódban. Az utóbbi 1999-ben indult útjára, a TDD-nek pedig az idő előrehaladtával kialakultak sajátos megoldásai.Newkirk, JW and Vorontsov, AA. Test-Driven Development in Microsoft .NET, Microsoft Press, 2004.
Rengeteg programozó alkalmazza ezt a technikát arra is, hogy egyszerűbbé tegyék az olyan legacy kódok fejlesztését, hibakövetését, amik régebbi eszközök felhasználásával készültek.Feathers, M. Working Effectively with Legacy Code, Prentice Hall, 2004
Tesztvezérelt fejlesztési ciklus bélyegkép|A tesztvezérelt fejlesztési életciklus grafikus ábrázolása A következő lépések a Test-Driven Development by Example könyv elméletén alapulnak:
1. Teszt hozzáadása A tesztvezérelt fejlesztés során minden új funkció teszt írásával kezdődik. Olyan tesztet kell írni, amely tömören és pontosan meghatározza az új funkciót vagy a már meglévő, javításra szánt funkciót. Teszt írásához a fejlesztőnek egyértelműen meg kell értenie a szolgáltatás specifikációját és követelményeit. A fejlesztő felhasználási esetek és a felhasználóval történt beszélgetések alapján pontosan lefedheti a követelményeket és az elvárásokat, így képes lesz a teszteket megírni, függetlenül a tesztelési és fejlesztői környezettől. Mindez alkalmazható már meglévő tesztek módosítására is. Ez a tulajdonság különbözteti meg igazán a tesztvezérelt fejlesztést az olyan unit tesztek írásától, amelyek már a kód megírása után készültek. Apró, de fontos eltérés ez. 2. Minden korábbi teszt futtatása, hogy kiderüljön az új teszt megbukik e Ez megmutatja, hogy a tesztkihasználtság jól működik, az új teszt új kód igénylése nélkül nem teljesít sikeresen, mert még nem létezik a kívánt viselkedés, és ez megfelelően kiküszöböli annak a lehetőségét, hogy az új teszt selejtes és minden esetben átmegy a teszten. Az új tesztnek kudarcot kell vallania a várt eredmények alapján. Ez a lépés növeli a fejlesztő magabiztosságát az új tesztek megírásával kapcsolatban. 3. Kód írás A következő lépés egy olyan kód írása, amely alapján a teszt sikeres lesz. Az ebben a szakaszban írt új kód nem lesz tökéletes, és valószínűleg egy nem túl elegáns módon fogja teljesíteni a tesztet. Ez azért elfogadható, mert az 5. lépésben úgyis csiszolni, szépíteni fogjuk a kódot. Ezen a ponton az írott kód egyetlen célja a teszt teljesítése. A programozó nem írhat olyan kódot, amely meghaladja a teszt által ellenőrzött funkciókat. 4. Minden teszt újbóli futtatása Ha az összes tesztelési eset működik, a programozó biztos lehet benne, hogy az új kód megfelel a tesztelési követelményeknek, és nem ront a meglévő szolgáltatások minőségén. Ha mégis hibát okoz, akkor az új kódot addig kell módosítani, amíg meg nem felel az elvárásoknak. 5. Kódszépítés A növekvő kódot rendszeresen meg kell tisztítani a tesztvezérelt fejlesztés során. Az új kód áthelyezhető onnan, ahol alkalmas volt a tesztelésre, oda, ahova logikusan tartozik. A másolatot el kell távolítani. Az objektum-, osztály-, modul-, változó- és metódusneveknek egyértelműen tükrözniük kell jelenlegi céljaikat és felhasználás mikéntjét, hogy az extra funkciók gond nélkül hozzáadhatóak legyenek. A szolgáltatások hozzáadásával a metódusok testei hosszabbá válhatnak, és más objektumok nagyobbak lehetnek. Előnyünkre válhat az ilyen részek feldarabolása az olvashatóság és a karbantarthatóság javításának érdekében, ami értékes lépésnek fog bizonyulni a szoftver későbbi életciklusában . Az öröklési hierarchiákat át lehet rendezni, hogy logikusabbak és hasznosabbak legyenek, és hogy előnyükre váljanak a különböző tervezési mintáknak. Ismeretesek speciális és általános iránymutatások a refaktoráláshoz és a tiszta kód létrehozásához. A teszt esetek folyamatos újraindításával az egyes refaktorozási szakaszok során a fejlesztő biztos lehet benne, hogy a folyamat nem változtat meg meglévő funkciókat. A sokszorosítás megszüntetése minden szoftvertervezési minta fontos aspektusa. Ebben az esetben azonban ez vonatkozik a duplikációs kód eltávolítására is a teszt kód és a végleges kód között – mint ahogyan azt említettük is a 3. lépésben. Lépések ismétlése Új teszt írásával a ciklus újra fog indulni. A lépések méretének mindig kicsinek kell lennie, mindegyik teszt futtatása között csak körülbelül 1-10 szerkesztéssel. Ha az új kód nem elégíti ki gyorsan az új tesztet, vagy ha más tesztek váratlanul sikertelennek bizonyulnak, akkor a programozónak vissza kell vonnia vagy vissza kell állítania a kódot szemben a túlzott hibakereséssel. A folyamatos integráció visszafordítható ellenőrzőpontok állításával segít ezen a problémán. Külső könyvtárak használatakor fontos, hogy ne dolgozzunk olyan kicsi lépésekben, amikkel effektíven teszteljük csupán csak magát a könyvtárat, kivéve, ha okkal feltételezhető, hogy a könyvtár hibás vagy nem elégíti ki a szolgáltatásokat teljes körűen.
Fejlesztési stílus A tesztvezérelt fejlesztés alkalmazásának különféle aspektusai vannak, ilyen például a „tartsd egyszerűen, ostoba” ( KISS ) és a „Nem lesz rá szükséged” (YAGNI) alapelvek. Azáltal, hogy csak a tesztek lebonyolításához szükséges kódra fókuszálunk, a formaterv gyakran tisztább lehet, mint más módszerekkel. Kent Beck az általa írt könyvében, ami a Test-Driven Development by Example, javasolja még a " hamisítsd, amíg el nem készíted " elvet is.
Ahhoz hogy olyan fejlett tervezési koncepciókat érjenek el, mint például a tervezési minták, a megírt tesztek maguk generálják ezeket a tervezési koncepciókat. Lehet, hogy a kód egyszerűbb marad, mint a célmintázat, de teljesíti az összes szükséges tesztet. Ez elsőre zavaró lehet, de lehetővé teszi a fejlesztőnek, hogy csak arra koncentráljon, ami ténylegesen számít.
Első lépés, a teszt írás: A teszteket a tesztelni kívánt funkció előtt kell megírni. Ennek a későbbiekben számos előnye lesz. Az alkalmazás így a tesztelhetőség érdekében lesz megírva, a fejlesztő így figyelembe veheti, hogy hogyan fogja tesztelni a projektet mielőtt azt ténylegesen megírná. Biztosítja azt is, hogy minden szolgáltatáshoz tartozzon megfelelő teszteset. Ezenkívül a tesztek írása közben a fejlesztő a termékigényeket mélyebben és korában megértheti, ezáltal biztosítja a tesztkód hatékonyságát, és folyamatosan a szoftver minőségére összpontosít. Ha viszont funkciók minél gyorsabb megírására törekszenek, előfordulhat, hogy a többi fejlesztő és a szervezetek hajlamosak a fejlesztőt sürgetni a következő funkció megírásával, ami azt eredményezi hogy a tesztelést akár teljes egészében figyelmen kívül fogja hagyni. Az első TDD teszt először talán még le sem fog fordulni, mert az előírt osztályok és módszerek még a kezdeti szakaszban nem léteznek. Ennek ellenére az első teszt a végrehajtható specifikáció kezdeteként működik. Az egységek legyenek minél kisebbek A TDD esetében az egységet leggyakrabban osztályként vagy kapcsolódó funkciók csoportjaként definiálják, amelyet gyakran modulnak hívnak. Az egységek viszonylag kis méretben tartása nagyszerű előnyökkel járnak, többek között:
Csökkenti a hibakeresésre szánt időt – Amikor a teszthiba észlelhetővé válik, a kisebb egységek elősegítik a hiba vagy hibák felderítését. Öndokumentáló tesztek – A kis tesztesetek gyorsabban olvashatóak és könnyebben megérthetőek.
A tesztvezérelt fejlesztés fejlett gyakorlatai hasonlóak lehetnek az elfogadási tesztvezérelt fejlesztés (ATDD) és a Specifikáció példákon keresztül szemléletéhez. Utóbbi például azt jelenti, hogy az ügyfél által megadott kritériumokat automatizálják, mint elfogadási tesztek, amelyek ezután vezetik a hagyományos egység tesztvezérelt fejlesztési (UTDD) folyamatokat.Koskela, L. "Test Driven: TDD and Acceptance TDD for Java Developers", Manning Publications, 2007 Ez a folyamat biztosítja, hogy az ügyfél automatizált mechanizmussal rendelkezik annak eldöntésére, hogy a szoftver megfelel-e a követelményeinek. Az ATDD-vel a fejlesztői csapatnak konkrét célt kell teljesítenie – az elfogadási teszteket -, amelyek folyamatosan arra összpontosítanak, hogy az ügyfél pontosan mit is akar látni az egyes felhasználói sztorikból.
Színek jelentése Minden teszt kezdetben kudarcot vall: Ez biztosítja, hogy a teszt valóban működik és képes megtalálni a hibát. Miután ez megvalósult, a mögöttes funkciók ezután megírhatóak. Ez vezetett a "tesztvezérelt fejlesztési mantrához", azaz "vörös / zöld / kék / vörös". teszteléshez, ahol a piros a kudarcot, azaz a teszt sikertelenségét jelenti, a zöld pedig azt, hogy sikeres volt a teszt. A refaktorálás nélküli tesztelést Piros-Zöld-Piros tesztelésnek nevezzük. A tesztvezérelt fejlesztés folyamatosan megismétli a az újabb tesztesemények hozzáadását, amik sikertelenül végződnek, később sikeresek lesznek, és ezeket folyamatosan refaktorálja, azaz szépíti. A kódrefaktorálás színe egységesen a kék. Elvileg minden zöld lépés után kék jön, ezért a TDD-t nevezhetjük Piros-Zöld-Kék-Piros tesztelésnek is. A várt teszteredmények minden szakaszban történő lefutása megerősíti a fejlesztő kódjának mentális modelljét, növeli az önbizalmát és növeli a munkatermelékenységét.
Legjobb gyakorlatok
A teszt felépítése A teszt eset hatékony elrendezése biztosítja az összes szükséges művelet elvégzését, javítja a teszt eset olvashatóságát és megkönnyíti a végrehajtás folyamatát. A konzisztens felépítés segít az öndokumentáló tesztesetek létrehozásában. A teszt esetekben általánosan alkalmazott struktúrának van (1) beállítása, (2) végrehajtása, (3) érvényesítése és (4) megtisztítása.
Beállítás: Helyezze az egységet tesztelés alá (UUT) vagy a teljes tesztrendszert a teszt futtatásához szükséges állapotba. Végrehajtás: Indítsa el/vezesse az UUT-t a célviselkedés végrehajtásához és rögzítse az összes kimenetet, mint például a visszatérési értékeket és kimeneti paramétereket. Ez a lépés általában nagyon egyszerű. Érvényesítés: Ellenőrizze, hogy a teszt eredményei helyesek-e. Ezek az eredmények tartalmazhatnak a végrehajtás során rögzített explicit kimeneteket vagy az UUT állapotváltozásait. Tisztítás: Állítsa vissza az UUT-t vagy a teljes tesztrendszert a teszt előtti állapotba. Ez a helyreállítás lehetővé teszi egy újabb teszt végrehajtását közvetlenül a teszt után.
Egyéni legjobb gyakorlatok