Egységtesztelés

a szoftverfejlesztési minőségbiztosítás egy módszere, ami a szoftver legelemibb egységeinek helyes működésére fókuszál
Ez a közzétett változat, ellenőrizve: 2023. május 9.

A számítógép-programozásban az egységtesztelés a szoftvertesztelésnek egy olyan módszere, amelynek során a forráskód egységeit (egy vagy több számítógépes program modul készletet) a kapcsolódó vezérlő adatokkal, a felhasználási-és a működtető eljárásokkal együtt tesztelik annak meghatározására, hogy azok elérik-e kitűzött céljukat.

Az egységtesztek általában automatizált tesztek, amelyeket a szoftverfejlesztők írnak és futtatnak annak biztosítása érdekében, hogy egy adott alkalmazás adott szakasza (úgynevezett "egység") megfeleljen a kialakításának és az elvárt, kívánt módon viselkedjen. A procedurális programozásban az egység lehet egy teljes modul, de túlnyomórészt egyéni függvény vagy eljárás. Objektumorientált programozás során az egység gyakran egy teljes felület/interfész, például egy osztály, de lehet egyedi metódus is. Azáltal, hogyha a tesztek készítése először a legkisebb tesztelhető egységekre történik, majd az azok közötti összetett, egész viselkedésre, átfogó tesztek hozhatók létre komplex alkalmazásokhoz.

Az esetlegesen felmerülő problémák elkülönítése érdekében minden tesztesetet egymástól függetlenül, külön kell tesztelni. Egy modul izolált tesztelésének elősegítésére többek között az összetett, bonyolult viselkedéssel bíró objektumokat helyettesítő, ún. álcaobjektumok (mock objektumok) is használatosak.

A fejlesztés során a szoftverfejlesztő kritériumokat vagy ismert eredményeket is kódolhat a tesztbe azért, hogy ellenőrizze az adott egység helyességét. Az egyes tesztesetek végrehajtása során a keretrendszerek naplózzák azokat a teszteket, amelyek bármely kritériumot nem teljesítik és jelentést készítenek róluk egy összefoglalóban.

Az egységtesztek írása és fenntartása gyorsabban kivitelezhető, mint a paraméterezett tesztek esetén. Ezek lehetővé teszik egy teszt többszöri végrehajtását különböző bemeneti halmazokkal, csökkentve ezzel a tesztkód reprodukálását. A hagyományos egységtesztektől eltérően, amik általában zárt metódusok és invariáns körülményeket tesztelnek, a paraméterezett tesztek bármilyen paraméterkészletet felvehetnek. A paraméterezett teszteket támogatja a TestNG, JUnit és ennek .NET-beli párja, az xUnit is. Az egységteszthez tartozó megfelelő paraméterek manuálisan is megadhatók, vagy egyes esetekben a teszt keretrendszere automatikusan generálja őket. Az utóbbi években támogatták az erőteljesebb (egység) tesztek írását, az elméletek koncepciójának kiaknázását, az olyan teszteseteket, amelyek ugyanazokat a lépéseket hajtják végre, de futási idő alatt generált tesztadatokat használnak, ellentétben a szokásos paraméterezett teszttel, amely ugyanazokat a végrehajtási lépéseket használja olyan bemeneti halmazokkal, amelyek előre definiáltak.

Az egységteszt célja a program minden részének elkülönítése és annak igazolása, hogy az egyes részek helyesek. Az egységteszt szigorú, írásbeli „szerződést” biztosít, amelyet az adott kóddarabnak ki kell elégítenie. Ennek eredményeként az egységteszt számos előnyt kínál.

Az egységtesztelés a szoftverfejlesztési folyamat korai szakaszában találja meg a problémákat. Ez egyaránt magában foglalja mind a programozó végrehajtásának bugjait, mind az egység specifikációjának hibáit vagy annak hiányzó részeit. Az alapos, részletes tesztkészlet megírásának folyamata arra készteti a fejlesztőt, hogy gondolja át a bemeneteket, a kimeneteket és a hibafeltételeket, ezáltal így pontosabban meghatározza az egység kívánt viselkedését. A hibakeresés költsége a kódolás megkezdése vagy a kód első írása előtt jóval alacsonyabb, mint a hiba későbbi felismerésének, azonosításának és kijavításának költsége. A kiadott kódban szereplő hibák szintén költséges problémákat okozhatnak a szoftver végfelhasználói számára. A kódra lehetetlen vagy bonyolult egységtesztet írni, ha az rosszul van megírva, így az egységtesztelés arra készteti a fejlesztőket, hogy jobban strukturálják a funkciókat és az objektumokat.

A tesztvezérelt fejlesztés során (TDD), amelyet gyakran használnak mind az extrém programozásban, mind a scrumban, az egységteszteket a kód írása előtt hozzák létre. Amikor a tesztek sikeresek, az adott kódot teljesnek tekintik. Ha az egységteszt sikertelen, akkor bugnak tekintik vagy a megváltozott kódot, vagy magukat a teszteket is. Ezután az egységtesztek lehetővé teszik a hiba vagy a balsiker helyének könnyű nyomon követését. Mivel az egységtesztek azelőtt figyelmeztetik fejlesztői csoportot a problémára, mielőtt a kódot azt kiosztanák a tesztelőknek vagy az ügyfeleknek, a lehetséges problémák a fejlesztési folyamat korai szakaszában fedezhetők fel.

Az egységteszt lehetővé teszi a programozó számára a kódrefaktorálást vagy a rendszerkönyvtárak egy későbbi időpontban történő frissítését és a modul továbbra is helyes működéséről való megbizonyosodást(például regressziós teszteléskor). A művelet során tesztesetek írására kerül sor minden függvényre és metódusra azért, hogyha bármilyen változás hibát okozna, az gyorsan azonosítható legyen. Az egységtesztek olyan változásokat észlelnek és derítenek ki, amelyek megsérthetik a szerződésalapú tervezést.

Az egységtesztelés csökkentheti a bizonytalanságot magukban az egységekben és a lentről felfelé irányuló tesztelési stílusú (bottom-up testing style) megközelítésben használható. Ha először teszteljük a program részeit, majd teszteljük a részek összegét, az integrációs tesztelés sokkal könnyebbé válik.

Az egységtesztelés a rendszer egyfajta élő dokumentációját nyújtja. Azok a fejlesztők, akik meg akarják tanulni, hogy egy egység milyen funkcionalitást kínál és hogy hogyan kell ezt használni, megnézhetik az egységtesztelést, hogy alapvetően megismerjék az egység felületét (API).

Az egységteszt olyan jellemzőket testesít meg, amelyek kritikusak az egység sikeréhez. Ezek a jellemzők jelezhetik az egység megfelelő / nem megfelelő használatát, továbbá a negatív viselkedéseket, amelyek az egység által „csapdába” (trapped by the unit) estek. Egy egységteszt önmagában dokumentálja ezeket a kritikus jellemzőket, habár sok szoftverfejlesztő környezet nem kizárólag arra szolgál, hogy a fejlesztés alatt álló terméket dokumentálja a kód alapján.

Ha a szoftvert a tesztvezérelt megközelítés alkalmazásával fejlesztik, interfész meghatározására történő egységtesztek, valamint a teszt letelte után elvégzett kódrefaktorálási tevékenységek kombinációja léphet a hivatalos tervezés helyébe. Minden egységteszt tervezési elemnek tekinthető, amely meghatározza az osztályokat, metódusokat és a megfigyelhető viselkedést.

Korlátok és hátrányok

szerkesztés

A tesztelés nem fog minden hibát elfogni a programban, mert nem tud minden végrehajtási útvonalat kiértékelni. Ez a döntési probléma (a megállási probléma (halting problem) szülő-halmaza, azaz, hogy az adott program befejezi-e a futását vagy örökre fut,) eldönthetetlen, mivel lehetnek olyan kódrészek, amikre nem írható olyan ellenőrző algoritmus, amely igaz-hamis válasszal szolgálna a kimenetében. Ugyanez igaz az egységtesztelésre is. Ezenkívül, a definíció szerint az egységteszt csak maguknak az egységeknek a funkcionalitását teszteli. Ebből kifolyólag nem fog elkapni integrációs hibákat vagy tágabb, rendszerszintű hibákat (például több egységen végrehajtott függvények vagy nem funkcionális tesztterületek, például a teljesítmény) Az egységtesztelést más szoftvertesztelési tevékenységekkel összefüggésben kell elvégezni, mivel ezek csak bizonyos hibák jelenlétét vagy hiányát mutatják; nem tudják bizonyítani az összes hiba hiányát. Ahhoz, hogy garantálva legyen a végrehajtás minden útjának és minden lehetséges bemenetnek a helyes viselkedése és biztosítva legyen a hibák hiánya, más technikákra is szükség van, nevezetesen formális módszerek alkalmazására annak igazolására, hogy a szoftverkomponensnek nincs váratlan viselkedése.

Egy egységtesztek alkotta bonyolult hierarchia nem egyezik meg az integrációs teszteléssel. A periferikus egységekkel való integráció bele kell, hogy tartozzon az integrációs tesztekbe, de az egységtesztekbe nem. Az integrációs tesztelés még mindig erősen támaszkodik az emberek általi manuális tesztelésre; ugyanis a magasszintű vagy globális körű tesztelést nehéz automatizálni, a manuális tesztelés gyakran könnyebben és olcsóbban kivitelezhetőbbnek tűnik.

A szoftvertesztelés egy kombinatorikus probléma. Például, minden Boolean döntési nyilatkozatnak legalább két tesztre van szüksége: egy „igaz” és egy „hamis” kimenetelűre. Ennek eredményeként a programozóknak minden megírt kódsorra 3–5 sornyi tesztkódra van szükségük. Ez nyilvánvalóan időt vesz igénybe és esetleg nem éri meg a belefektetett erőfeszítést. Vannak olyan problémák, amelyeket egyáltalán nem könnyű tesztelni - például nemdeterminisztikus algoritmusok, vagy több szálat tartalmaznak. Továbbá, az egységteszt kódja valószínűleg legalább olyannyira hibás (buggy), mint az a kód, amit tesztelni hivatott. Fred Brooks a The Mythical Man Month című könyvében eképpen szól „Soha ne menj a tengerre két kronométerrel; vigyél egyet vagy hármat.” Ugyanis, ha két kronométer ellentmond, akkor honnan tudod, hogy melyik a helyes?

Az egységteszt írásával kapcsolatos másik kihívás a reális és hasznos tesztek felállításának nehézsége. Szükséges megteremteni a megfelelő kezdeti, kiinduló feltételeket azért, hogy az alkalmazás tesztelt része a teljes rendszer részeként viselkedjen. Ha ezek a kezdeti feltételek nincsenek helyesen beállítva, akkor a teszt nem gyakorolja a kódot reális kontextusban, ami csökkenti az egységteszt értékét és pontosságát.

Az egységtesztelés előnyeinek eléréséhez szigorú fegyelemre van szükség az egész szoftverfejlesztési folyamat során. Alapvető fontosságú, hogy ne csak az elvégzett teszteket, hanem a szoftver ezen vagy bármely más egységének forráskódjában elvégzett változtatásokat is gondosan rögzítsük. Lényeges egy verziókezelő rendszer használata is. Ha az egység egy későbbi verziója elbukik egy olyan sajátos teszten, amin korábban átment, akkor a verziókezelő szoftver a forráskód-változtatások (ha vannak) listáját képes nyújtani, amelyek az egységre azóta vonatkoznak.

Szintén elengedhetetlen egy fenntartható folyamat implementálása annak biztosítása érdekében, hogy a kudarcba fulladt tesztesetek rendszeresen felülvizsgálva és azonnal kezelve legyenek. Ha egy ilyen folyamatot nem valósítanak meg és berögzülnek a csapat munkafolyamatába, akkor az alkalmazás nem fog szinkronban fejlődni az egységtesztkészletével, ezzel növelve a hamis pozitív eredményeket és csökkentve a tesztkészlet hatékonyságát.

A beágyazott rendszerek szoftverének egységtesztelése egy egyedülálló kihívást jelent: mivel a szoftvert egy, a futási felületétől eltérő platformon fejlesztették ki, a tesztprogramot az aktuális telepítési környezetben nem lehet könnyedén futtatni, mint ahogy ez az asztali programok esetében lehetséges.

Az egységtesztek általában akkor a legkönnyebbek, ha a metódusnak néhány paramétere és valamilyen kimenete van. Nem ilyen könnyű egységteszteket készíteni, amikor a metódus fő függvénye kölcsönhatásban van valamilyen alkalmazáson kívüli dologgal. Például egy adatbázissal dolgozó metódusnál szükség lehet egy replikára (mock up) az adatbázis-interakciók modelljének létrehozására, amely valószínűleg nem lesz olyan átfogó és széleskörű, mint a valódi adatbázis-interakciók.

Az alábbiakban Java nyelven megvalósított teszteset sorozat látható, amely meghatározza egy elemszámát az implementációnak. Először is, rendelkezni kell egy Hozzáadó (Adder) nevű interfésszel, ami egy hozzáad(add) nevű függvényt tartalmaz, valamint egy HozzáadóImpl nevű, paraméter nélküli konstruktort tartalmazó implementáló osztállyal, ami a Hozzáadó interfészt implementálja. Ezt követően azt kell ellenőrizni, hogy a 2 egész szám típusú bemeneti paraméterrel felruházott hozzáad(add) metódus az egész szám kimenetében ezen paramétereinek helyesen összeadott összegével tér-e vissza. Ez az ellenőrzés az állítás (assertion) segítségével valósítható meg, mely tulajdonképpen egy predikátum, egy olyan logikai függvény, mely hozzájárul a program hibáinak felismeréséhez. Az assert kimenete igaz vagy hamis lehet, ami arra szolgál, hogy jelezze, hogy jelen esetben a paramétereként megadott metódus (hozzáad) a kívánt módon működik-e. A metódus viselkedése meghatározásra kerül egy kisebb értékhalmazzal és különböző számú tesztekkel. Jelen esetben ez azt jelenti, hogy össze tud-e adni helyes eredménnyel 2 számot, legyen az akár pozitív, akár negatív, akár nagyobb értékkel bíró vagy nulla; itt, ha az assert igazzal tér vissza, akkor a példafüggvény helyesen, különben helytelenül működik.

import static org.junit.Assert.*;

import org.junit.Test;

public class TesztHozzáadó {

    @Test
    public void tesztPozitívSzámokEgyÉsEgyÖsszege() {
        Hozzáadó hozzáadó = new HozzáadóImpl();
        assert(hozzáadó.hozzáad(1, 1) == 2);
    }

    // össze tudja-e adni az 1 és 2 pozitív számokat?
    @Test
    public void tesztÖsszegPozitívSzámokEgyÉsKettő() {
       Hozzáadó hozzáadó = new HozzáadóImpl();
        assert(hozzáadó.hozzáad(1, 2) == 3);
    }

    // össze tudja-e adni a 2 és 2 pozitív számokat?
    @Test
    public void tesztÖsszegPozitívSzámokKettőÉsKettő() {
        Hozzáadó hozzáadó = new HozzáadóImpl();
        assert(hozzáadó.hozzáad(2, 2) == 4);
    }

    // a nulla semleges-e?
    @Test
    public void tesztÖsszegNullaSemleges() {
        Hozzáadó hozzáadó = new HozzáadóImpl();
        assert(hozzáadó.hozzáad(0, 0) == 0);
    }

    // össze tudja-e adni a -1 és -2 negatív számokat?
    @Test
    public void tesztNegatívSzámokÖsszege() {
        Hozzáadó hozzáadó = new HozzáadóImpl();
        assert(hozzáadó.hozzáad(-1, -2) == -3);
    }

    // össze tud-e adni pozitív és negatív számot?
    @Test
    public void tesztÖsszegPozitívNegatív() {
        Hozzáadó hozzáadó = new HozzáadóImpl();
        assert(hozzáadó.hozzáad(-1, 1) == 0);
    }

    // nagyobb számokkal is boldogul?
    @Test
    public void tesztÖsszegNagySzámok() {
          Hozzáadó hozzáadó = new HozzáadóImpl();
        assert(hozzáadó.hozzáad(1234, 988) == 2222);
    }

}

Ez esetben az elsőként megírt egységtesztek egyfajta tervezési dokumentumként viselkednek, amelyek meghatározzák a megoldás formáját és viselkedését, viszont az implementáció részleteit nem, az a programozó dolga marad. A „csináld a lehető legegyszerűbb dolgot, ami esetleg működik” gyakorlatot követvén, minden egységteszt az eddig nem tesztelt legegyszerűbb esetet írja le, ami a teszt sikeres lebonyolítását lehetővé teszi, az alábbiakban látható.

interface Hozzáadó {
    int hozzáad(int a, int b);
}
class HozzáadóImpl implements Hozzáadó {
    public int hozzáad(int a, int b) {
        return a + b;
    }
}

Végrehajtható specifikációként

szerkesztés

Az egységtesztnek, mint tervezési specifikációnak, jelentős előnye van a többi tervezési módszerrel szemben: a tervezési dokumentum (maguk az egységtesztek) maga is felhasználható az implementáció ellenőrzésére. A tesztek soha nem lesznek sikeresek, amíg a fejlesztő a terv szerint megoldást nem implementálja.

Az egységtesztek hiányt szenvednek a diagramos specifikációk hozzáférhetőségéből, ilyen az UML-diagram is, ám ezek generálhatók az egységtesztet használó automatizált eszközökből. A legtöbb modern nyelv rendelkezik ingyenes eszközökkel (általában IDE-k kiterjesztéseként elérhető). Az ingyenes eszközök, mint például az xUnit keretrendszerén alapuló eszközök, egy másik rendszerre bocsátják az emberi fogyasztásra szánt nézet grafikus megjelenítését.

Alkalmazások

szerkesztés

Extrém programozás

szerkesztés

Az egységtesztelés az extrém programozás sarokköve, amely egy automatizált egységteszt-keretrendszerre támaszkodik. Ez az automatizált egységtesztelési keretrendszer lehet harmadik fél, például az xUnit, vagy létrehozható a fejlesztői csoporton belül.

Az extrém programozás egységtesztek létrehozását használja a tesztvezérelt fejlesztéshez. A fejlesztő egy egységtesztet ír, amely vagy egy szoftverkövetelményt, vagy egy hiányosságot fejt ki. Ez a teszt sikertelen lesz, mert vagy a követelményt még nem implementálták a kódban, vagy azért, mert szándékosan feltárja a már meglévő, megírt kód hibáját. Ezután a fejlesztő a legegyszerűbb kódot írja, hogy a teszt, más tesztekkel együtt, sikeres legyen.

Egy rendszerben a legtöbb kód egységteszt alá van vetve, de nem feltétlen minden útja a kódnak. Az extrém programozás megköveteli a "Tesztelj mindent, ami esetleg megszakad!" stratégiát, túl a hagyományos "Tesztelj minden végrehajtási utat!" módszeren. Ez arra készteti a fejlesztőket, hogy kevesebb tesztet dolgozzanak ki, mint a klasszikus módszereknél, de ez nem igazán probléma, inkább a tény újra megfogalmazása, mivel a klasszikus módszerek ritkán voltak elég módszeresen követve ahhoz, hogy minden végrehajtási útvonalat alaposan megvizsgáljanak.

A tesztkód egy első osztályú projekt műalkotásnak/készítménynek (artifact) tekinthető, abban a tekintetben, hogy ugyanolyan minőségben tartják fent, mint az implementációs kódot. A fejlesztők a tesztelt kóddal együtt az egységteszt kódot is kiadják a tárolóba (repository). Az extrém programozás alapos egységtesztelése lehetővé teszi a fent említett előnyöket, például az egyszerűbb és magabiztosabb kódfejlesztést és -refaktorálást, egyszerűsített kódintegrációt, pontos dokumentációt és modulárisabb felépítést. Ezek az egységtesztek állandóan futtatva vannak regressziós teszt formájában is.

Egységteszt-keretrendszerek

szerkesztés

Fő cikk: Egységteszt-keretrendszerek listája

Az egységteszt-keretrendszerek leggyakrabban harmadik féltől származó termékek, amelyeket nem terjesztenek a fordítóprogram részeként. Segítik az egységtesztelés folyamatának egyszerűsítését, a programnyelvek széles skálájára fejlesztették ki.

Általában lehetséges egységtesztelést végrehajtani egy adott keretrendszer támogatása nélkül, olyan kliens kód írásával, amely tesztelés alatt futtatja az egységeket és állítást, kivételkezelést vagy más forgalomszabályozó (flow-control) mechanizmusokat használ a hiba jelzésére. A keret nélküli egységteszt akkor bír értékkel, ha valamilyen korlátja van a belépésnek az egységteszt elfogadásához; a kódban gyéren jelenlévő egységtesztek aligha jobbak annál az esetnél, mintha egyáltalán ne lenne egy sem, míg ha egyszer egy keretrendszer alkalmazása megtörténik, utána az egységteszt hozzáadása relatíve egyszerűvé válik. Néhány keretrendszerben sok fejlett egységteszt funkció hiányzik vagy kézzel kell azt kódolni.

Nyelv-szintű egységtesztelés támogatás

szerkesztés

Néhány programozási nyelv közvetlenül támogatja az egységtesztelést. Nyelvtanuk lehetővé teszi az egységtesztek közvetlen deklarálását könyvtár (akár harmadik fél, akár szabvány) importálása nélkül. Emellett az egységteszt logikai feltételei ugyanabban a szintaxisban fejezhetők ki, mint a nem egységteszt-kódban használt logikai kifejezések, például amikre az elágazások és ciklusok (elöl- vagy hátultesztelő) használata esetén kerül sor.

Beépített egységteszt-támogatással bíró programnyelvek:

Beépített egységteszt-támogatással nem, de nagyon jó egységtesztelő könyvtárakkal/keretrendszerekkel rendelkező programnyelvek:

Fordítás

szerkesztés

Ez a szócikk részben vagy egészben az Unit testing 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. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.

Kapcsolódó szócikkek

szerkesztés