Tesztvezérelt fejlesztés

A Wikipédiából, a szabad enciklopédiából
Ugrás a navigációhoz Ugrás a kereséshez

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 speciális teszt esetekként fogalmazzuk meg, a kódot pedig ehhez mérten írjuk 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 tartjuk számon a technika kifejlesztéséért, úgymond az "újra-felfedezéséért",[1] 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.[2]

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[3] indult útjára, a TDD-nek pedig az idő előrehaladtával kialakultak sajátos megoldásai.[4]

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.[5]

Tesztvezérelt fejlesztési ciklus[szerkesztés]

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:[2]

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.[6] [7] 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ülzotthibakeresé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,[4] 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[szerkesztés]

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.[2] 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[8] ö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.[9]

Az egységek legyenek minél kisebbek[szerkesztés]

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önyebben megérthetőek.[8]

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.[10] 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[szerkesztés]

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".[11] 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 termelékenységét.

Legjobb gyakorlatok[szerkesztés]

A teszt felépítése[szerkesztés]

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. [8]

Egyéni legjobb gyakorlatok[szerkesztés]

Néhány bevált gyakorlat, amelyet az egyén követhet. A közös beállítási és lebontási logika szétválasztása teszt támogató szolgáltatásokba amelyeket a megfelelő tesztesetek használnak fel, hogy minden egyes teszt orákulum csak a teszt validálásához szükséges eredményekre összpontosítson, és tervezzen időfüggő teszteket a végrehajtás toleranciájának lehetővé tétele érdekében a nem valósidejű operációs rendszerekben. Az általános gyakorlat az, hogy 5-10 százalékos tartalékot hagynak a késői végrehajtáshoz, csökkentve ezáltal a hamis negatívok számát a teszt végrehajtások során. Azt is gyakran javasolják, hogy a tesztkód ugyanazt a bánásmódot kapja, mint a termelési kód. A tesztkódnak helyesen kell működnie mind a pozitív, mind a negatív esetekben, legyen időtálló, olvasható és karbantartható. A csapatok összegyűlhetnek és felülvizsgálhatják a teszteket és a tesztelési gyakorlatokat, hogy megosszák egymással a hatékony technikákat és kiküszöböljék a rossz szokásokat.[12]

Kerülendő gyakorlatok vagy „antiminták”[szerkesztés]

  • Olyan tesztesetek megléte, melyek a korábban végrehajtott tesztesetekkel manipulált rendszer állapotától függenek (azaz az egység tesztet mindig egy ismert és előre konfigurált állapotból kell elindítani).
  • A teszt esetek közötti függőségek. Az a tesztkészlet, amelyben a teszt esetek egymástól függenek, törékeny és túlságosan összetett. A végrehajtási sorrend ne legyen kikövetkeztethető. A kezdeti teszt esetek vagy az UUT struktúrájának alapvető refaktorálása egyre inkább átfogó hatások spirálját okozza a kapcsolódó tesztekben.
  • Interdependens tesztek, azaz egymástól függő tesztek. Ezek sorban egymáshoz kapcsolódó hamis eredményeket generálnak. A korai teszt hibája egy későbbi teszt esetét akkor is megszakítja, ha nincs tényleges hiba az UUT-ban, ami növeli a hibaelemzésre szánt időt és a hibakeresési erőfeszítések számát.
  • A végrehajtási viselkedés pontos időzítésének vagy teljesítményének tesztelése.
  • "Mindent tudó orákulumok" építése. Egy olyan orákulum, amely a szükségesnél többet vizsgál, drágábbá és törékenyebbé válik az idő múlásával. Ez a nagyon gyakori hiba azért veszélyes, mert kényes, de átfogó időelnyelőt okozhat egy komplex projekt során.[12]
  • A kivitelezés részleteinek tesztelése.
  • Lassú lefutású tesztek.

Előnyök[szerkesztés]

Egy 2005-ös tanulmány megállapította, hogy TDD használatával jóval több teszt íródik, így azok a programozók, akik több tesztet írtak, sokkal termelékenyebbnek bizonyultak.[13] Azonban a kódminőséggel kapcsolatos hipotézisek, valamint a TDD és a termelékenység közvetett összefüggése nem volt meggyőző.[14]

Azok a programozók, akik tiszta TDD-t használtak az új („ zöldmezős ”) projekteknél, csak ritkán érezték szükségét a hibakeresõ használatának. Verziókezelő rendszerrel együtt használva, amikor a tesztek váratlanul kudarcot vallanak, a kód visszaállítása a legutóbbi verzióra, amely minden tesztet sikeresen végrehajtott, gyakran eredményesebb lehet, mint maga a hibakeresés.[15]

A tesztvezérelt fejlesztés nem csupán a helyesség érvényességét jelzi, hanem a program megtervezését is segíti.[16] Ha először a teszt esetekre összpontosítunk, el kell képzelnünk, hogy az ügyfelek hogyan használják majd a funkciókat (az első esetben a teszt eseteket). A programozó tehát a felülettel foglalkozik a megvalósítás előtt. Ez az előny kiegészíti a szerződéses tervezést, mivel a kódot tesztesetekkel, nem pedig matematikai állításokkal közelíti meg.

A tesztvezérelt fejlesztés lehetőséget biztosít arra, hogy szükség esetén csak kis lépésekben haladjon a fejlesztő. Ez lehetővé teszi a programozók számára, hogy a jelenlegi feladatra összpontosítsanak, mivel az első és legfontosabb cél a teszt sikeressége. A kivételes eseteket és a hibakezelést kezdetben nem veszik figyelembe, és ezen idegen körülmények megteremtésére szolgáló teszteket külön-külön hajtják végre. A teszt-vezérelt fejlesztés így biztosítja, hogy az összes írott kódot legalább egy teszt lefedje. Ez nagyobb fokú magabiztosságot ad a kóddal kapcsolatban a programozó csapatnak és az azt követő felhasználóknak.

Noha igaz, hogy a TDD-vel több kódra van szükség, mint TDD nélkül, az egység tesztek miatt, a teljes kód implementálási ideje rövidebbé válhat a Müller és Padberg modell alkalmazásával.[17] A vizsgálatok minél nagyobb száma segíti korlátozni a kód hibáinak számát. A tesztelés korai és gyakori jellege elősegíti a hibák észlelését a fejlesztési ciklus korai szakaszában, megakadályozva őket, hogy endemikus és drága problémává váljanak. A hibák kiküszöbölése a folyamat elején általában segít elkerülni a hosszabb és fárasztóbb hibakeresést a projekt későbbi szakaszában.

A TDD használata modulárisabb, rugalmasabb és bővíthetőbb kódhoz vezethet. Ez a hatás gyakran azért jön létre, mert a módszertan megköveteli, hogy a fejlesztők a szoftvert olyan kis egységekben lássák, amelyeket önállóan meg lehet írni és tesztelni, majd ezeket később össze lehet fűzni. Mindez kisebb, koncentráltabb osztályokhoz, lazább kapcsolódásokhoz és tisztább interfészekhez vezet. A Mock objektum tervezési minta használata szintén hozzájárul a kód általános modulálásához, mivel ez a minta megköveteli a kód meglétét, így a modulok között könnyen át lehet váltani, azokról amelyek az egység tesztelését szolgálták azokra amelyek valódi verziói a projektnek.

Mivel nem írnak annál több kódot, mint amennyire szükség van egy sikertelen teszteset teljesítéséhez, az automatizált tesztek lefedik az összes lehetséges kód kimenetelt. Például ahhoz, hogy egy TDD fejlesztő hozzáadjon egy else ágat egy létező if utasításhoz, a fejlesztőnek először írnia kell egy hibás teszt esetet. Ennek eredményeként a TDD-ből származó automatizált tesztek általában nagyon alaposak: észlelnek minden váratlan változást a kód viselkedésében. Ez olyan problémákat is észlel, amelyek akkor merülhetnek fel, ha a fejlesztési ciklus későbbi változástatásai hatással lesznek a többi funkcióra.

Madeyski[18] empirikus bizonyítékokat szolgáltatott (több mint 200 fejlesztővel végzett laboratóriumi kísérletek sorozatán keresztül) a TDD gyakorlatának fölényével kapcsolatban, ami lenyomta a hagyományos tesztelj-utóbb megközelítést és a helyesség-tesztelési módszert. Az átlagos hatásméret egy közepes (de közel a nagyhoz) hatást képvisel a végrehajtott kísérletek meta-analízise alapján, amely egy igazán lényeges megállapítás. A moduláció javítását (azaz egy modulárisabb kialakítást), a kifejlesztett szoftvertermékek könnyebb újrafelhasználását és tesztelését javasolja, ami a TDD programozási gyakorlatnak köszönhető. Madeyski a TDD gyakorlatnak az egységtesztekre gyakorolt hatását az ág lefedettség (BC) és a mutációs pontszám indikátor (MSI) felhasználásával is megmérte,[19] [20] [21] amelyek az egységtesztek alaposságának és hibadetektálási hatékonyságának mutatói. A TDD hatása az ágak lefedettségére közepes méretű volt, ezért ez már lényeges hatásnak tekinthető.

Korlátozások[szerkesztés]

A tesztvezérelt fejlesztés nem hajt végre elegendő tesztelést olyan helyzetekben, amikor a teljes működési tesztekre szükség van a siker vagy kudarc meghatározásához, ennek oka az egységtesztek széles körű használata. [22] Ezekre jó példa a felhasználói felületek, az adatbázisokkal működő programok, és néhány olyan rendszer, amely az adott hálózati konfigurációtól függ. A TDD arra ösztönzi a fejlesztőket, hogy minimális mennyiségű kódot írjanak az ilyen modulokba, és maximalizálják a logikát, amely a tesztelhető könyvtári kódban van, így a külvilágot hamis és mock objektumok felhasználásával reprezentálják.[23]

A menedzsment támogatása alapvető fontossággal bír. Anélkül, hogy az egész szervezet úgy gondolná, hogy a teszt-alapú fejlesztés javítani fog a terméken, a vezetés úgy érezheti, hogy a tesztek írására fordított idő egyszerű pazarlás.[24]

A teszt-vezérelt fejlesztői környezetben létrehozott egységteszteket általában ugyanaz a fejlesztő hozza létre, aki a tesztelt kódot is írja. Ezért a tesztek vak területeket oszthatnak meg a kóddal: ha például egy fejlesztő nem veszi észre, hogy bizonyos bemeneti paramétereket ellenőrizni kell, akkor valószínű, hogy sem a teszt, sem a kód nem ellenőrzi ezeket a paramétereket. Egy másik példa: ha a fejlesztő félreérti a fejlesztett modulra vonatkozó követelményeket, akkor mind a kód, mind az általa írt egység tesztek ugyanúgy tévedni fognak. Ezért a tesztek ugyan sikeresek lesznek, de téves értelmet adnak a helyességről.

A nagy számú sikeres egységteszt téves biztonságérzetet okozhat, ami kevesebb kiegészítő szoftver tesztelési tevékenységhez vezethet, ilyenek például az integrációs tesztek és megfelelőségi-tesztek.

A tesztek a projekt karbantartási költségeinek részévé válnak. A rosszul megírt tesztek, például azok, amelyek kódolt hibaszövegeket tartalmaznak, maguk is hajlamosak a kudarcra, és költséges a karbantartásuk. Ez különösen igaz a törékeny tesztekre.[25] Fennáll annak a veszélye, hogy a rendszeresen hamis hibákat generáló teszteket figyelmen kívül hagyják, így amikor valódi hiba fordul elő, akkor azt nem veszik észre. Lehetőség van tesztek írására az alacsony és könnyű karbantartás érdekében, például a hibaszövegek újbóli felhasználásával, ez a fentiekben ismertetett kódrefaktorálási szakasz egyik fő célja.

A túl sok teszt írása és fenntartása sok időbe telik. Ezenkívül a rugalmasabb (korlátozott tesztekkel rendelkező) modulok új követelményeket is elfogadhatnak anélkül, hogy a teszteken bármit is változtatni kellene. Ezen okok miatt a csak szélsőséges körülmények között végzett tesztelés, vagy a kisebb adatmintákon végzett tesztelés jobban megkönnyítheti a munkát, mint a túl részletesen felépített tesztesetek.

Az ismételt TDD ciklusok során elért lefedettségi szintet és a tesztelési részletességet a későbbiekben egyre nehezebbé válik újraalkotni, megváltoztatni. Ezért ezek az eredeti, vagy a korai tesztek egyre értékesebbé válnak az idő múlásával. A taktika az, hogy korán fixálják az ezekkel kapcsolatban felmerülő problémákat. Továbbá, ha rossz az architektúra, a tervezés vagy a tesztelési stratégia, az később változásokhoz vezethet, amely több tucat meglévő teszt kudarcba fulladását eredményezheti, így tehát fontos, hogy azokat külön-külön rögzítsék. Ha pusztán csak törlik, letiltják vagy a változtatásokat meggondolatlanul hajtják végre, azok valószínűleg nem észlelhető lyukakhoz vezethetnek a teszt lefedettségében.

Tesztvezérelt munka[szerkesztés]

A teszt-vezérelt fejlesztést mára már a termék-, és a szolgáltatócsoportok is átvették, mint teszt-vezérelt munka.[26] A TDD-hez hasonlóan a nem-szoftver csapatok a munka minden egyes vonatkozásában a munka megkezdése előtt minőség-ellenőrzési (QC) ellenőrzéseket hajtanak végre(általában manuális teszteket, nem automatizált teszteket dolgoznak ki). Ezeket a QC ellenőrzéseket ezután felhasználják a terv tájékoztatására és a kapcsolódó eredmények validálására. A TDD sorozat hat lépését kisebb szemantikai változtatásokkal alkalmazzák:

  1. A "Ellenőrzés hozzáadása" a "Teszt hozzáadása" helyett
  2. Az "Összes ellenőrzés futtatása" a "Az összes teszt futtatása" helyett
  3. A "Csináld a munkát" a "Kód írása" helyett
  4. Az "Összes ellenőrzés újbóli futtatása" a "A tesztek újbóli futtatása" helyett
  5. A "Tisztítsd meg a munkát" a "Kódszépítés" helyett
  6. "Ismétlés"

TDD és ATDD[szerkesztés]

A tesztvezérelt fejlesztés kapcsolódik, de különbözik az elfogadási tesztvezérelt fejlesztéstől (ATDD).[27] A TDD elsősorban egy fejlesztői eszköz, amely elősegíti az egységkódok helyes megírását (funkció, osztály vagy modul), amely helyesen fogja végrehajtani műveletek adott halmazát. Az ATDD egy kommunikációs eszköz az ügyfél, a fejlesztő és a tesztelő között, ami azt a célt szolgálja, hogy a követelményeket pontosabban lehessen meghatározni. A TDD tesztek automatizálást igényelnek. Az ATDD nem, bár az automatizálás segíti a regressziós tesztelést. A TDD-ben használt tesztek gyakran ATDD tesztekből származtathatók, mivel a kód egységek végrehajtják a követelmények egy részét. Az ATDD teszteknek olvashatónak kell lenniük az ügyfél számára. A TDD tesztnek ezzel szemben nem kell annak lennie.

TDD és BDD[szerkesztés]

A BDD (viselkedésvezérelt fejlesztés) egyesíti a TDD és az ATDD gyakorlatait.[28] Ez magában foglalja a tesztek írásának gyakorlatát, de a viselkedést leíró tesztekre összpontosít, nem pedig a végrehajtási egységet tesztelő tesztekre. Az olyan eszközök, mint a JBehave, a Cucumber, az Mspec és a Specflow, olyan szintaxisokat biztosítanak, amelyek lehetővé teszik a terméktulajdonosok, a fejlesztők és a tesztmérnökök számára, hogy együttesen határozzák meg a viselkedést, amelyet azután automatizált tesztekké alakíthatnak.

Kódláthatóság[szerkesztés]

A tesztsorozatnak hozzá kell férnie a tesztelésre szánt kódhoz. Azonban, a normál tervezési követelmények szerint, mint az információrejtés, enkapszuláció és az ügyek szétválasztása, az adatok nem veszélyeztethetőek. így tehát, a TDD egység tesztjei általában ugyanazon projektben vagy modulban találhatóak, mint a tesztelni kívánt kód.

Objektum-orientált tervezésnél ez továbbra sem biztosít hozzáférést a bizalmas adatokhoz és módszerekhez. Ezért további munkára lehet szükség az egységtesztek megírásánál. Java és más nyelveken a fejlesztő reflexiót használhat a privát mezők és metódusok eléréséhez.[29] Alternatív megoldásként egy belső osztály is létrehozható az egység tesztek lebonyolításához, hogy láthatóak legyenek a mellékelt osztály tagjai és attribútumai. A . A NET Framework-ben és néhány más programozási nyelvben, részleges osztályok használhatóak fel, így hozzáférhetővé válnak a szükséges metódusok és adatok a teszt elvégzéséhez.

Fontos, hogy az ilyen tesztelési trükkök ne maradjanak benne a végleges kódban. C-ben és más nyelvekben olyan fordító irányelvek, mint az #if DEBUG ... #endif helyezhetőek el kiegészítő osztályok és valójában az összes többi, a teszthez kapcsolódó kód körül, hogy megakadályozzák őket, hogy a kiadott kódban is leforduljanak. Ez azt jelenti, hogy a kiadott kód nem pontosan egyezik meg azzal, amit korábban teszteltek. A kevesebb, de átfogóbb, végponttól kezdődő integrációs tesztek rendszeres futtatása a végleges kiadás összeállításában biztosíthatja (többek között), hogy nem létezik olyan gyártási kód, amely kicsit is támaszkodik a tesztköteg szempontjaira.

A TDD-t aktívan használók között vita folyik, amelyet a blogjaikban és más írásaiban dokumentálnak, arról, hogy mindenképpen bölcs dolog-e a privát adatok és metódusok tesztelése. Egyesek szerint a privát tagok pusztán végrehajtási részletek, amelyek megváltozhatnak, és ezeknek engedni kell, anélkül, hogy a tesztszámokon csökkentenének. Ily módon elegendő bármilyen osztály tesztelése a nyilvános interfészein vagy alosztályi interfészein keresztül, amelyet néhány nyelv "védett" felületnek hív.[30] Mások szerint a funkcionalitás kritikus szempontjai megvalósíthatók privát metódusokkal, és ezeket közvetlenül lehet tesztelni a kisebb és specifikusabb egységtesztekkel.[31] [32]

Szoftverek tesztvezérelt fejlesztéshez[szerkesztés]

Számos olyan tesztelési keretrendszer és eszköz áll rendelkezésre, ami hasznos a TDD megvalósításához.

xUnit keretrendszerek[szerkesztés]

A fejlesztők használhatnak számítógéppel támogatott tesztelési keretrendszereket, ezek közös megnevezése a xUnit (mely az 1998-ban létrehozott SUnit elnevezésből származik). Ezek azt a célt szolgálják, hogy önmaguk megalkossák és automatikusan futtassák a teszteseteket. Az xUnit keretrendszerek állításalapú érvényességi vizsgálatokat és eredményeket biztosítanak. Ezek a képességek kritikusak az automatizálás szempontjából, mivel a végrehajtás érvényesítésének terheit egy független utófeldolgozási tevékenységből átvitték a teszt végrehajtásába. Az ezen tesztelési keretrendszerek által biztosított végrehajtási keret lehetővé teszi az összes teszteset vagy különféle részhalmaz automatikus végrehajtását más funkciókkal együtt.[33]

TAP-eredmények[szerkesztés]

A tesztelési keretrendszerek elfogadhatják az egységteszt kimenetét az 1987-ben létrehozott nyelv-agnosztikai Test Anything Protokoll alapján.

Hamis, mock objektumok és integrációs tesztek[szerkesztés]

Az egységteszteket azért így nevezték el, mert egy egységnyi kódot tesztelnek. Egy komplex modulnak ezer egységtesztje lehet, egy egyszerű modulnak csak tíz lehet. A TDD-hez használt egységtesztnek soha nem szabad átlépnie a program határait, nem is beszélve a hálózati kapcsolatokról. Ha ezek mégis megtörténnek, a bemutatás csúszhat, amely miatt a tesztek lassan futnak le, és elriasztják a fejlesztőket a teljes tesztcsomag futtatásától. A külső moduloktól vagy adatoktól való függőség az egységteszteket integrációs tesztekké alakítja. Ha az egyik modul hibásan működik egymással összekapcsolt modulok láncában, akkor annyira nem lesz egyértelmű, hogy hol kell keresni a hiba okát.

Ha a fejlesztés alatt álló kód adatbázisra, webszolgáltatásra vagy bármilyen más külső folyamatra vagy szolgáltatásra támaszkodik, az egység-tesztelhetőség szétválasztása szintén lehetőséget biztosít a modulárisabb, tesztelhetőbb és újrafelhasználhatóbb kódok tervezéséhez.[34] Ennek megvalósításához két lépés szükséges:

  1. Amikor a végleges tervhez külső hozzáférésre van szükség, meg kell határozni egy interfészt, amely leírja a rendelkezésre álló hozzáférést. Lásd a függőségi inverzió elvét, ami ennek előnyeit vitatja, függetlenül a TDD-től.
  2. Az interfészt kétféle módon lehet megvalósítani: az egyik valóban hozzáfér a külső folyamathoz, a másik pedig egy hamis vagy mock objektum. A hamis objektumoknak többet kell tennie annál, mint hogy egy „Személyes objektum mentve” üzenetet mentenek a nyomkövetési naplóba, amelyet könnyen helyettesíthet egy állításalapú teszt, mely futtatható a helyes viselkedés ellenőrzésének érdekében. A mock-objektumok abban különböznek ettől, hogy maguk tartalmaznak teszt-állításokat amelyek miatt a teszt sikertelen lehet, például ha a személy neve vagy egyéb adatai nem azok ami az elvárt lenne.

A hamis és álobjektumok módszere ami adatokkal tér vissza, látszólag egy adattárból vagy felhasználótól jönnek, elősegíthetik a teszt folyamatát azáltal, hogy mindig ugyanazokat a reális adatokat szolgáltatják vissza, amikre a tesztek támaszkodhatnak. Beállíthatók előre meghatározott hibamódok is, hogy a hibakezelési rutinok fejleszthetők és megbízhatóan tesztelhetők legyenek. Hiba módban egy metódus visszatérhet érvénytelen, hiányos vagy nulla válasszal, vagy kivételt dobhat. Az adattárolóktól eltérő hamis szolgáltatások szintén hasznosak lehetnek a TDD-ben: A hamis titkosítási szolgáltatás valójában nem titkosítja az átadott adatokat; egy hamis véletlen szám szolgáltatás mindig visszatérhet 1-el. Hamis vagy ál-megvalósítás még a függőségi injekció is.

A Teszt Páros egy tesztspecifikus képesség, amely helyettesíti a rendszer képességét, általában egy osztályt vagy funkciót, amelytől az UUT függ. Két olyan alkalom van, amikor be lehet vezetni a tesztpárost egy rendszerbe: kapcsolódás és végrehajtás. A kapcsolódási idő helyettesítése akkor történik, amikor a tesztpárost összeállítják a betöltési modulba, amelyet a tesztelés validálásának érdekében hajtanak végre. Ezt a megközelítést általában akkor alkalmazzák, ha olyan környezetben futnak, amely nem a célkörnyezet, ahol a párosításhoz a hardver szintű kód megkétszerezése szükséges a lefutáshoz. A kapcsolóhelyettesítés alternatívája a futásidejű helyettesítés, amelyben a valós funkciók kicserélésre kerülnek egy teszt végrehajtása során. Ezt a helyettesítést általában az ismert funkciómutatók újbóli hozzárendelésével vagy az objektum cseréjével hajtják végre.

A tesztpárosok különféle típusúak és különböző bonyolultságúak lehetnek:

  • Dummy - A dummy, azaz próbabábu a kettős teszt legegyszerűbb formája. Megkönnyíti a kapcsolóidő-helyettesítést azáltal, hogy szükség esetén megad egy alapértelmezett visszatérési értéket.
  • Stub - A stub, azaz a csonk egyszerűsített logikát ad a próbabábu számára, különféle kimeneteket biztosítva.
  • Kém - A kém rögzíti és elérhetővé teszi a paraméter- és állapotinformációkat, közzéteszi azokat akik hozzáférnek a teszt kódhoz, így privát információk tesztkódjával teszi lehetővé a fejlettebb állapotérvényesítést.
  • Mock - A modellt egy egyedi teszt eset határoz meg, ami a teszt-specifikus viselkedés validálását, a paraméterértékek ellenőrzését és a hívások sorrendjének eldöntését szolgálja.
  • Szimulátor - A szimulátor egy átfogó elem, amely a cél képességének nagyobb pontosságú megközelítését biztosítja (a dolog megduplázódik). A szimulátor általában jelentős további fejlesztési erőfeszítéseket igényel.[8]

Az ilyen függőség-befecskendezések következménye az, hogy a tényleges adatbázist vagy más külső hozzáférésű kódot soha nem teszteli maga a TDD-folyamat. Az ebből adódó hibák elkerülése érdekében más tesztekre is szükség van, amelyek a teszt-vezérelt kódot a fentiekben tárgyalt interfészek "valódi" megvalósításával kivitelezik. Ezek integrációs tesztek, és teljesen elkülönülnek a TDD egység tesztjeitől. Ezekből lényegesen kevesebb van és ritkábban kell futtatni őket, mint az egységteszteket. Mindazonáltal ugyanazon tesztelési keretrendszerrel is megvalósíthatóak.

A folyamatos tárolót vagy adatbázist megváltoztató integrációs teszteket mindig körültekintően kell megtervezni, figyelembe véve a fájlok vagy az adatbázis kezdeti és végső állapotát, még akkor is, ha bármelyik teszt sikertelen is lesz. Ezt gyakran a következő technikák valamilyen kombinációjával érik el:

  • A TearDown módszer, melynek szerves részét képezi a rengeteg tesztelési keretrendszer.
  • try...catch...finally kivételkezelési szerkezet, ott ahol ezek elérhetőek.
  • Adatbázis-tranzakciók, ahol egy tranzakció atomilag tartalmazhat írási, olvasási és megfelelő törlési műveletet.
  • A „pillanatfelvétel” készítése az adatbázisból bármilyen teszt futtatása előtt, és visszatérés a pillanatképhez minden tesztfutás után. Ez automatizálható egy keret, például Ant vagy NAnt által, vagy egy folyamatos integrációs rendszer, mint például CruiseControl felhasználásával is.
  • Az adatbázis tiszta állapotba állítása a tesztek előtt, ahelyett, hogy utána kellene megtisztítani. Ez akkor lehet releváns, ha a takarítás megnehezítheti a teszteredmények diagnosztizálását azáltal, hogy törli az adatbázis végleges állapotát, mielőtt a részletes diagnózist el lehetne végezni.

TDD komplex rendszerekhez[szerkesztés]

A TDD nagy, kihívást jelentő rendszereken történő gyakorlása moduláris architektúrát, jól definiált komponenseket, publikus interfészeket és fegyelmezetten rétegzett rendszert igényel, a platform függetlenségének maximalizálásával. Ezek a bevált gyakorlatok növelik a tesztelhetőséget és megkönnyítik az építkezés és a tesztelés automatizálását.[8]

Tesztelhetőség szerinti tervezés[szerkesztés]

A komplex rendszerekhez egy sor követelményt kielégítő architektúrára van szükség. E követelmények kulcsfontosságú részhalmaza magában foglalja a rendszer teljes és hatékony tesztelésének támogatását. A hatékony és moduláris kialakítás olyan komponenseket eredményez, amelyek osztoznak a tényleges, TDD szempontjából nélkülözhetetlen tulajdonságokon.

  • A magas kohézió biztosítja, hogy minden egység biztosítsa a kapcsolódó képességeket, valamint megkönnyíti ezen képességek tesztelését.
  • Az alacsony tengelykapcsoló lehetővé teszi az egyes egységek elszigetelten történő hatékony tesztelését.
  • A publikált interfészek korlátozzák a komponensek hozzáférését, így a tesztek kapcsolattartó pontjaiként szolgálnak, megkönnyítve a teszt létrehozását, biztosítva a legnagyobb pontosságot a teszt és a gyártóegység konfigurálása között.

A hatékony moduláris architektúra felépítésének kulcsfontosságú módszere a forgatókönyv-modellezés, ahol sorozatdiagramok készíthetők, amelyek mindegyike egyetlen rendszerszintű végrehajtási forgatókönyvre koncentrál. A forgatókönyv modell kiváló eszköz a komponensek közötti interakció stratégiájának megalkotásához, ami egy specifikus stimulusra adott válasz. Ezen forgatókönyv-modellek mindegyike gazdag szolgáltatásokkal és funkciókkal kapcsolatos követelményeknek felelnek meg, ezek leírják, hogy egy összetevőnek miket kell biztosítania, és ez egyúttal diktálja az összetevők és a szolgáltatások egymás közötti kölcsönhatásának sorrendjét is. A forgatókönyv modellezése nagyban megkönnyítheti a TDD tesztek összeállítását egy komplex rendszer számára.[8]

Tesztek vezetése nagy csapatok számára[szerkesztés]

Egy nagyobb rendszerben a rossz komponensminőség hatását az interakciók bonyolultsága csak növeli. A tesztek teljes populációjának összetettsége azonban önmagában is problémává válhat, csökkentve a lehetséges nyereségeket. Ez egyszerűen hangzik, de a kezdeti kulcsfontosságú lépés annak felismerése, hogy a tesztkód egyben fontos szoftver is, ezért azonosan szigorúan kell eljárni az elkészítés és a fenntartás folyamatában, csakúgy mint a gyártási kód esetében.

A tesztszoftver architektúrájának összetett rendszeren belüli létrehozása és kezelése ugyanolyan fontos, mint az alaptermék architektúrája. A tesztvezetők kölcsönhatásba lépnek az UUT-val, a teszt párosaival és az egység teszt keretrendszerével.[8]

Jegyzetek[szerkesztés]

  1. Kent Beck: Why does Kent Beck refer to the "rediscovery" of test-driven development?, 2012. május 11. (Hozzáférés: 2014. december 1.)
  2. a b c Beck, Kent. Test-Driven Development by Example. Addison Wesley (2002. november 8.). ISBN 978-0-321-14653-3 
  3. Lee Copeland: Extreme Programming. Computerworld, 2001. december 1. [2011. augusztus 27-i dátummal az eredetiből archiválva]. (Hozzáférés: 2011. január 11.)
  4. a b Newkirk, JW and Vorontsov, AA. Test-Driven Development in Microsoft .NET, Microsoft Press, 2004.
  5. Feathers, M. Working Effectively with Legacy Code, Prentice Hall, 2004
  6. Beck, Kent. XP Explained, 1st Edition. Addison-Wesley Professional, 57. o. (1999). ISBN 0201616416 
  7. Ottinger and Langr: Simple Design. (Hozzáférés: 2013. július 5.)
  8. a b c d e f g Effective TDD for Complex Embedded Systems Whitepaper. Pathfinder Solutions
  9. Agile Test Driven Development. Agile Sherpa, 2010. augusztus 3. [2012. július 23-i dátummal az eredetiből archiválva]. (Hozzáférés: 2012. augusztus 14.)
  10. Koskela, L. "Test Driven: TDD and Acceptance TDD for Java Developers", Manning Publications, 2007
  11. Kusper, Gábor. Programozási technológiák - A program kódja állandóan változik (PDF) (2015) 
  12. a b YouTube-videó. a YouTube-on by Pathfinder Solutions
  13. Erdogmus: On the Effectiveness of Test-first Approach to Programming. Proceedings of the IEEE Transactions on Software Engineering, 31(1). January 2005. (NRC 47445). [2014. december 22-i dátummal az eredetiből archiválva]. (Hozzáférés: 2008. január 14.) „We found that test-first students on average wrote more tests and, in turn, students who wrote more tests tended to be more productive.”
  14. Proffitt: TDD Proven Effective! Or is it?. [2008. február 6-i dátummal az eredetiből archiválva]. (Hozzáférés: 2008. február 21.) „So TDD's relationship to quality is problematic at best. Its relationship to productivity is more interesting. I hope there's a follow-up study because the productivity numbers simply don't add up very well to me. There is an undeniable correlation between productivity and the number of tests, but that correlation is actually stronger in the non-TDD group (which had a single outlier compared to roughly half of the TDD group being outside the 95% band).”
  15. Llopis: Stepping Through the Looking Glass: Test-Driven Game Development (Part 1). Games from Within, 2005. február 20. (Hozzáférés: 2007. november 1.) „Comparing [TDD] to the non-test-driven development approach, you're replacing all the mental checking and debugger stepping with code that verifies that your program does exactly what you intended it to do.”
  16. Mayr, Herwig. Projekt Engineering Ingenieurmässige Softwareentwicklung in Projektgruppen, 2., neu bearb. Aufl., München: Fachbuchverl. Leipzig im Carl-Hanser-Verl., 239. o. (2005. november 1.). ISBN 978-3446400702 
  17. Müller: About the Return on Investment of Test-Driven Development (PDF). Universität Karlsruhe, Germany. (Hozzáférés: 2012. június 14.)
  18. Madeyski, L. "Test-Driven Development - An Empirical Evaluation of Agile Practice", Springer, 2010, ISBN 978-3-642-04287-4, pp. 1-245. DOI: 978-3-642-04288-1
  19. The impact of Test-First programming on branch coverage and mutation score indicator of unit tests: An experiment. by L. Madeyski Information & Software Technology 52(2): 169-184 (2010)
  20. On the Effects of Pair Programming on Thoroughness and Fault-Finding Effectiveness of Unit Tests by L. Madeyski PROFES 2007: 207-221
  21. Impact of pair programming on thoroughness and fault detection effectiveness of unit test suites. by L. Madeyski Software Process: Improvement and Practice 13(3): 281-295 (2008)
  22. Problems with TDD. Dalkescientific.com, 2009. december 29. (Hozzáférés: 2014. március 25.)
  23. Hunter: Are Unit Tests Overused?. Simple-talk.com, 2012. október 19. (Hozzáférés: 2014. március 25.)
  24. Loughran: Testing (PDF). HP Laboratories, 2006. november 6. (Hozzáférés: 2009. augusztus 12.)
  25. Fragile Tests
  26. Leybourn, E. (2013) Directing the Agile Organisation: A Lean Approach to Business Management. London: IT Governance Publishing: 176-179.
  27. Lean-Agile Acceptance Test-Driven Development: Better Software Through Collaboration. Boston: Addison Wesley Professional (2011). ISBN 978-0321714084 
  28. BDD. (Hozzáférés: 2015. április 28.)
  29. Burton: Subverting Java Access Protection for Unit Testing. O'Reilly Media, Inc., 2003. november 12. (Hozzáférés: 2009. augusztus 12.)
  30. van Rossum: PEP 8 -- Style Guide for Python Code. Python Software Foundation, 2001. július 5. (Hozzáférés: 2012. május 6.)
  31. Newkirk: Testing Private Methods/Member Variables - Should you or shouldn't you. Microsoft Corporation, 2004. június 7. (Hozzáférés: 2009. augusztus 12.)
  32. Stall: How to Test Private and Protected methods in .NET. CodeProject, 2005. március 1. (Hozzáférés: 2009. augusztus 12.)
  33. Effective TDD for Complex, Embedded Systems Whitepaper. Pathfinder Solutions. [2013. augusztus 20-i dátummal az eredetiből archiválva]. (Hozzáférés: 2012. november 27.)
  34. Fowler, Martin. Refactoring - Improving the design of existing code. Boston: Addison Wesley Longman, Inc. (1999). ISBN 0-201-48567-2 

Fordítás[szerkesztés]

Ez a szócikk részben vagy egészben a Test-driven development című angol Wikipédia-szócikk ezen változatának fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel.

Kapcsolódó szócikkek[szerkesztés]

További információk[szerkesztés]