3. fejezet - Szoftvertesztelés

Tartalom

Belövés
Belövés Eclipse-ben
Egységtesztelés JUnit segítségével
Parametrizált tesztek
Kivételek tesztelése
Tesztkészletek létrehozása
JUnit antiminták

A szoftvertesztelés fő feladata, hogy megbizonyosodjunk arról, hogy a szoftver az elvárásoknak megfelelően működik. Ez a tevékenység (illetve az ettől némileg általánosabb verfikációs és validációs folyamat) gyakorlatilag felöleli a teljes szoftveréletciklust, a követelmények vizsgálatától kezdve a szoftverterv áttekintésén át egészen az elkészült implementáció (maga a termék) tesztelésésig. Ráadásul, mivel az evolúciós fázisban a rendszer tovább változik, annak során is szükség lesz tesztelési tevékenységekre. Mivel jelen jegyzet középpontjában a megvalósítás áll, ezért a továbbiakban csak ebből a szemszögből foglalkozunk e folyamattal. A verifikációs és validációs tevékenységeket többféleképpen is csoporthatjuk. Az egyik csoportosítás alapja a szoftver vizsgálatához szükséges reprezentációján alapul. Ebből a szempontból beszélhetünk statikus illetve dinamikus tesztelésről.

  1. A statikus tesztelés során nincs szükség a program lefuttatására. Ezt statikus programelemző eszközök alkalmazásával, illetve kódátvizsgálások segítségével végezhetjük, melynek során a forráskódot akár többen is áttekintik, hiányosságok illetve hibák után kutatkodva benne.

  2. A dinamikus tesztelés során a program lefuttatásával ellenőrizzük, hogy az megfelelően működik-e. Ez átalában magával vonja annak ellenőrzését, hogy megadott bemeneti értékek esetén a program az elvárt kimenetet produkálja-e.

A fejlesztés folyamatában betöltött szerepe alapján az alábbi csoportosítás végezhető:

  1. Egységtesztelés (más néven modultesztelés vagy komponenstesztelés): az egyes programegységeket (például modulokat, osztályokat) egymástól függetlenül, izolálva teszteljük. Általában az adott egység fejlesztését végző programozó végzi.

  2. Integrációs tesztelés: az egyes komponensek közötti interfészek, valamint az operációs rendszerrel, állományrendszerrel, hardverrel vagy más rendszerrel való interakciók tesztelését végezzük. Az integrációt végző fejlesztő, vagy (szerencsésebb esetben) egy direkt e célra létrehozott integrációs tesztelő csapat végzi. Az integrációs tesztelésnek több szintje is lehet, különféle célokkal. Tesztelhetjük a komponensintegrációt vagy a rendszerintegrációt.

  3. Rendszertesztelés: A teljes rendszer (vagyis a késztermék) viselkedésének vizsgálatával foglalkozik. Általában ez az utolsó, a fejlesztést végző szervezet részéről elvégzett tesztelésfajta.

  4. Elfogadási tesztelés: olyan felhasználó vagy ügyféloldali tesztelés, amelynek célja, hogy megbizonyosodjanak arról, hogy az elkészült rendszer mgfelel-e a célnak, elfogadható-e a felhasználók/ügyfelek számára.

Jelen jegyzetben ezekből a fő hangsúlyt az egységtesztelésre helyezzük, az egyéb tesztelési szintek kívül esnek a tárgyalandó témákon.

A szoftver verifikációja és validációja során a program hiányosságai rendes körülmények között kiderülnek, így a kódot módosítani kell a hiányosságok kiküszöbölése érdekében. Ezt a belövési folyamatot gyakran más verifikációs és validációs tevékenységekkel ötvözik. A tesztelés (vagy általánosabban, a verifikáció és a validáció) és a belövés azonban olyan különálló folyamatok, amelyeket nem kell integrálni:

  1. A verifikáció és validáció az a folyamat, amely megállapítja, hogy egy szoftverrendszerben vannak-e hiányosságok.

  2. A belövés az a folyamat, amely behatárolja és kijavítja ezeket a hiányosságokat.

A programok belövésére nem létezik egyszerű módszer. A jó hibakeresők mintákat keresnek a teszt kimenetében, és a hiányosság típusának, a kimeneti mintának, a programozási nyelvnek és a programozási folyamatnak az ismeretében behatárolják a hiányosságot. A belövés során felhasználhatjuk a szokásos programozói hibákról (például egy számláló megnövelésének elmulasztása) szóló tudásunkat, és azokat illesztjük a megfigyelt mintákra. A programozási nyelvre jellemző hibákat – például rossz mutatóhivatkozás C-ben – is figyelembe kell vennünk.

A hibák behatárolása egy programban nem mindig egyszerű folyamat, mivel a hiba nem szükségszerűen annak a pontnak a közelében van, ahol a sikertelenség bekövetkezett. A programhiba behatárolásához a belövésért felelős programozónak esetleg olyan újabb teszteket kell megterveznie, amelyek megismétlik az eredeti hibát, és segítik a hiba forrásának felderítését. Szükség lehet a program végrehajtását szimuláló kézi nyomkövetésre is. Bizonyos esetekben a program futásáról információt gyűjtő belövő eszközök is igen hasznosak lehetnek.

Az interaktív belövő eszközök általában a fordítórendszerrel integrált nyelvi segédeszközökből álló programcsomagok részét képezik. Ezek speciális végrehajtási környezetet biztosítanak a programnak, amely hozzáférést tesz lehe­tő­vé a fordítóprogram szimbólumtáblájához, ezáltal a program változóinak értékeihez. A felhasználók gyakran utasításról utasításra „lépegetve” vezérelhetik a végrehajtást. Az utasítások végrehajtását követően, a változók értékeit megvizsgálva, a lehetséges hibák felderíthetők. A belövési folyamatról és eszköztámogatásáról részletesebben a „Belövés” szakaszban lesz szó.

Belövés

Hibátlan program nem létezik. Éppen ezért roppant fontos, hogy amikor egy programhibára fény derül, hatékonyan meg tudjuk keresni, hogy a program mely része a hibás, hiszen ez elengedhetetlen a hiba kijavításához..A belövés (debugging) az a folyamat, amely behatárolja és kijavítja a szoftverben talált hiányosságokat. A folyamat során interaktív módon futtatjuk az alkalmazást, láthatjuk a forráskódban, hogy hol tart a végrehajtás, miközben lehetőségünk van egyes változók illetve kifejezések aktuális értékének a vizsgálatára is. A forráskódba töréspontokat (breakpoint) is elhelyezhetünk, amelyek segítségével azt befolyásolhatjuk, hogy hol kerüljön felfüggesztésre a program végrehajtása. Annak érdekében, hogy a végrehajtást egy adattag értékének olvasásához illetve módosításához kötődően függesszük fel, figyelőpontokat (watchpoint) hozhatunk létre. A töréspontokat és figyelőpontokat összefoglaló néven megállási pontoknak is nevezzük. Miután a program végrehajtása felfüggesztésre került, vizsgálhatjuk a változók értékeit, módosíthatjuk azokat, stb.

A különféle integrált fejlesztői környezetek (így természetesen az Eclipse is) általában lehetőséget biztosítanak arra, hogy programjainkat belövési üzemmódban (Debug mode) futtassuk. Az Eclipse rendelkezik egy speciális belövési perspektívával (Debug perspective, amelyet a Window menü Open perspective almenüjének Debug menüpontjával hívhatunk elő), amely előre definiált nézetek segítségével teszi lehetővé, hogy vezéreljük programunk végrehajtását és vizsgáljuk változóink állapotait.

Belövés Eclipse-ben

Töréspont létrehozásához a forráskódszerkesztő bal oldalán elhelyezkedő margóra kattintva kell kiválasztanunk a Toggle Breakpoint (Ctrl+Shift+B) elemet (Töréspont beállítása ábra), vagy egyszerűen csak duplán kell kattintanunk a margóra.

3.1. ábra - Töréspont beállítása

Töréspont beállítása

Ezt követően a margón megjelenő kis kék kör jelzi, hogy a 6. sorra töréspontot állítottunk be (A beállított töréspont ábra).

3.2. ábra - A beállított töréspont

A beállított töréspont

Az alkalmazás belövéséhez a végrehajtandó Java alkalmazás futtatására van szükség, amelyet a megfelelő Java forráson jobb gombbal történő kattintással vagy az eszköztár bogár ikonjának lenyitásával előnyíló menü Debug As almenü Java Application menüpontját kiválasztva tudunk elvégezni, ahogyan azt a Debug perspektíva ábra is mutatja.

3.3. ábra - Belövés indítása

Belövés indítása

A belövő elindításakor az Eclipse rákérdez, hogy szeretnénk-e átváltani a belövést segítő perspektívába, mihelyst egy megállási ponthoz elérünk. Itt válasszuk a Yes lehetőséget, ekkor az Eclipse a Debug perspektíva ábrán látható módon megnyitja a Debug perspektívát.

3.4. ábra - Debug perspektíva

Debug perspektíva

Itt azt látjuk, hogy a program futtatása megkezdődött, de a korábban beállított töréspontunknál a végrehajtás felfüggesztésre került a 6. sorban szereplő utasítás végrehajtását megelőzően. A vizsgált program végrehajtásának vezérlésére az eszköztáron találunk gombokat, amelyeket természetesen gyorsbillentyűkkel is kiválthatunk. Az e célból használható főbb gyorsbillentyűket az alábbi táblázat foglalja össze:

3.1. táblázat - A Debug perspektíva gyorsbillentyűi

IkonGyorsbillentyűLeírás
Resume F8 Arra utasítja a belövőt, hogy mindaddig folytassa a program végrehajtását, amíg a következő megállási pontot el nem érjük (illetve további megállási pontok hiányában a program végéig).
Terminate Ctrl+F2 Leállítja a belövést.
Step Into F5 Végrehajtja az aktuálisan kiválasztott sor utasításait, és a következő sorra lép. Ha a kiválasztott sorban metódushívás szerepel, akkor a belövő belép annak kódjába.
Step Over F6 Átlépi a hívást, vagyis úgy hajtja végre a metódust, hogy a belövő nem lép bele.
Step Return F7 Kilép az aktuálisan végrehajtott metódus hívójához. Ez azt jelenti, hogy az aktuálisan végrhajtott metódus befejeződik, és a végrehajtás a hívás helyén folytatódik.
Use Step Filters Shift+F5 Be/kikapcsolja a lépésszűrők használatát.


3.5. ábra - A Debug nézet gyorsbillentyűi

A Debug nézet gyorsbillentyűi


A bal felső sarokban (Debug nézet) a végrehajtás során mindig az aktuális hívási lánc látható. A Hívási lánc a Debug nézetben ábra példáján az látszik, amikor a főprogram 7. sorában szereplő counter.count() metódushívásba belelépve éppen a Counter.java forrásállomány 11. sorában lévő utasítás végrehajtása következik. Ebben a pillanatban a hívási láncon a Main osztály main(String[]) metódusa (vagyis a főprogram) és a Counter osztály count() metódusa szerepel.

3.6. ábra - Hívási lánc a Debug nézetben

Hívási lánc a Debug nézetben

Breakpoints nézet

A Breakpoints nézet segítségével a megállási pontok (törés- és figyelőpontok) kezelése valósítható meg. Lehetőség van ezen pontok törlésére, passzívvá változtatására, illetve különféle tulajdonságaik módosítására.

3.7. ábra - Breakpoints nézet

Breakpoints nézet

Az összes töréspont egyidejűleg a Skip All Breakpoints (Skip All Breakpoints) gombra kattintva hagyható figyelmen kívül.

Variables nézet

A Variables nézetben (Variables nézet ábra) az aktuális hívási lánc lokális változói, illetve objektumainak adattagjai jeleníthetőek meg. Csak a láthatósági- és élettartamszabályoknak megfelelő változók láthatóak itt. A lenyíló menü ( ) Java almenüjének menüpontjai segítségével beállítható, hogy a listában megjelenjenek-e a nevesített konstansok (static final), az osztály szintű (static) adattagok, a teljesen minősített nevek, a null értékű tömbreferenciák (alapértelmezés szerint ezek nem jelennek meg), illetve általában véve a referenciák (alapértelmezés szerint referencia helyett a hivatkozott objektum maga látszik). A Layout almenü Select Columns... menüpontjának segítségével a változók alapértelmezés szerint megjelenített nevén és értékén túlmenően beállíthatjuk a deklarált illetve tényleges típusának, a példányazonosítónak és a példányszámláló értékének a kijelzését is.

3.8. ábra - Variables nézet

Variables nézet

Lehetőség van a változók értékének futásidejű megváltoztatására, ami megengei, hogy ha úgy találjuk, hogy egy változó értéke nem megfelelően került beállításra, akkor az adott belövési folyamatban ideiglenesen megváltoztatva ezt az értéket újrafuttatás nélkül vizsgáljuk, hogy az újonnan beállított érték megfelelő-e. Mindehhez a Változóérték megváltoztatása ábrán látható módon egyszerűen át kell írni az értéket, és a végrehajtás további részében a módosult érték figyelembe vételével zajlik a futtatás.

3.9. ábra - Változóérték megváltoztatása

Változóérték megváltoztatása

Egy változó értéke alapértelmezetten a toString() metódus által visszaadott érték felhasználásával kerül megjelenítésre, azonban ez az alapértelmezés felülírható. Megadhatunk egy részletes formázót, amelyben Java kódban írhatjuk le, hogyan szeretnénk a változó értékét megjeleníteni. Például a Counter osztály esetén a toString() metódus nem szolgál túlságosan értelmes információkkal (hu.unideb.inf.prt.debugging.Counter@1224b90 a megjelenített érték), ezért ennek olvashatóbbá tétele érdekében (persze a toString() metódus felüldefiniálásának lehetősége mellett) a megfelelő változó jobb egérgombra kattintással előhívható környezeti menüjéből a New Detail Formatter... menüpont kiválasztásával adhatjuk meg, miképpen kellene az objektum megjelenítését elvégezni. A Részletes formázó hozzáadása ábrán látható példában a Counter osztály getResult() metódusát használjuk e célra.

3.10. ábra - Részletes formázó hozzáadása

Részletes formázó hozzáadása

Expressions nézet

Az Expressions nézet (WindowShow viewExpressions) segítségével adatok vizsgálata végezhető el. Gyakori probléma, hogy a futó programnak számos, a Variable nézetben megjelenő változója van, azonban ezek közül csak néhányra vagyunk kíváncsiak, illetve nem pontosan valamely változó értéke érdekes, hanem egy komplex kifejezésé. Ekkor használhatjuk az Expressions nézetet (Expressions nézet ábra), amely automatikusan megjelenítődik, ha hozzáadunk egy új kifejezést. Egy kifejezést legegyszerűbben a nézethez adni a forráskódban a kifejezést kijelölve, annak környezeti menüjéből a Watch menüpont kiválasztásával, illetve a nézet ) funkciójával lehet. Az adott helyen látható változókat és a belőlük képzett tetszőleges kifejezéseket adhatjuk ily módon a nézethez. A létrehozott kifejezések értéke automatikusan változik, ahogyan a kódban lépegetünk.

3.11. ábra - Expressions nézet

Expressions nézet

Töréspontok tulajdonságai

Miután létrehoztunk egy töréspontot, a jobbklikk → Breakpoint Properties menüpont segítségével bármikor beállíthatjuk a tulajdonságait. Megadhatunk a töréspont aktiválására vonatkozó megszorító feltételeket, például hogy a töréspont hatására a végrehajtás csak akkor kerüljön felfüggesztésre, ha legalább bizonyos számú alkalommal áthaladt rajta a vezérlés. Ezt a minimumértéket a Hit Count érték beállításával (amely a Breakpoints nézet ábrán látható helyen is megadható) határozhatjuk meg. Amikor n-edjére halad át a vezérlés a törésponton, a végrehajtás felfüggesztésre kerül, és a töréspont mindaddig letiltott állapotba kerül, amíg újra nem engedélyezzük, illetve meg nem változtatjuk a minimumértéket.

Ugyanitt lehetőség van feltételes kifejezés létrehozására is, ekkor a végrehajtás csak akkor kerül felfüggesztésre az adott töréspontnál, ha a feltétel igaz. Ezt a módszert kiegészítő naplózásra is használhatjuk, hiszen a feltételt megadó kódrészlet minden olyan alkalommal végrehajtásra kerül, amikor a vezérlés eléri ezt a pontot. A Törésponthoz tartozó feltétel megadása ábrán látható kódrészlet tehát minden alkalommal lefut, amikor elérjük a töréspontot, vagyis az első 50 iterációban a töréspont nem aktív, azonban ezt követően minden alkalommal megjelenítésre kerül az iteráció sorszáma, és a végrehajtás is felfüggesztésre kerül.

3.12. ábra - Törésponthoz tartozó feltétel megadása

Törésponthoz tartozó feltétel megadása

A végrehajtás felfüggesztésének további lehetőségei

  • Figyelőpontok: Figyelőpontok beállításával a végrehajtást akkor függeszthetjük fel, ha egy adattag értéke kiolvasásra illetve megváltoztatásra kerül. Figyelőpont létrehozására az adattag deklarációja melletti margóra történő dupla kattintással van lehetőség. A töréspontokhoz hasonlóan a figyelőpontok is rendelkeznek szerkeszthető tulajdonságokkal: lehetnek engedélyezettek illetve letiltottak, beállítható rájuk a Hit Count minimumérték, és szabályozható, hogy az adattag elérésekor, módosításakor, vagy akár (az alapértelmezés szerinti) mindkét esetben kerüljön-e felfüggesztésre a végrehajtás.

  • Kivételtöréspontok: Segítségükkel olyan töréspontok definiálhatóak, amelyek akkor aktivizálódnak, ha a forráskódban bekövetkezik valamilyen kivétel. Megadásához a Breakpoints nézet Add Java Exception Breakpoint ( ) menüpontjára kattintva van mód, és beállítható, hogy elkapott vagy el nem kapott kivételek esetén kerüljön felfüggesztésre a végrehajtás.

  • Metódustöréspontok: Metódustöréspont létrehozására a metódus fejléce melletti margóra történő dupla kattintással van lehetőség. A töréspontokhoz és figyelőpontokhoz hasonlóan a metódustöréspontok is rendelkeznek szerkeszthető tulajdonságokkal: lehetnek engedélyezettek illetve letiltottak, beállítható rájuk a Hit Count minimumérték, és szabályozható, hogy a metódusba történő belépéskor, annak elhagyásakor, avagy mindkét esetben kerüljön-e felfüggesztésre a végrehajtás.

  • Töréspontok osztálybetöltéshez: Egy osztálybetöltő-töréspont a végrehajtást akkor függeszti fel, amikor egy adott osztály betöltése megtörténik. Ennek beállítására a jobb alsó sarokban található Outline nézetben a kiválasztott osztályon nyomott jobb egérgomb, majd a Toggle Class Load Breakpoint menüpont kiválasztásával van mód.

  • Lépésszűrő: Bizonyos csomagok kihagyhatóak a belövés során. Ez olyan esetben lehet hasznos, ha például egy tesztelési keretrendszert használunk, de nem szeretnénk a keretrendszer osztályaiba belelépni a végrehajtás során. Ezt a beállítást a WindowPreferencesJavaDebugStep filtering menüútvonal használatával végezhetjük el.

  • Drop to frame: Az Eclipse lehetővé teszi, hogy a hívási lánc bármely elemét kiválasztva rábírjuk a virtuális gépet arra, hogy ettől a ponttól újrakezdje a végrehajtást. Ennek segítségével a programunk egy részét tudjuk újrafuttatni, de azon változók, amelyek értékét a végrehajtás alatti kód már módosította, módosítottak maradnak. A funkció eléréséhez ki kell választanunk a hívási lánc egy szintjét, és meg kell nyomni a Drop to Frame gombot ( ).