Tartalom
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.
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.
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ő:
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.
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.
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.
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:
A verifikáció és validáció az a folyamat, amely megállapítja, hogy egy szoftverrendszerben vannak-e hiányosságok.
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 lehető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ó.
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 menü almenüjének 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.
Töréspont létrehozásához a forráskódszerkesztő bal oldalán elhelyezkedő margóra kattintva kell kiválasztanunk a Ctrl+Shift+B) elemet (Töréspont beállítása ábra), vagy egyszerűen csak duplán kell kattintanunk a margóra.
(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).
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
ikonjának lenyitásával előnyíló menü almenü
menüpontját kiválasztva tudunk elvégezni, ahogyan azt a Debug
perspektíva ábra is
mutatja.
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 Debug perspektíva ábrán látható módon megnyitja a Debug perspektívát.
lehetőséget, ekkor az Eclipse aItt 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
Ikon | Gyorsbillentyű | Leírás |
---|---|---|
![]() | 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). |
![]() | Ctrl+F2 | Leállítja a belövést. |
![]() | 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. |
![]() | F6 | Átlépi a hívást, vagyis úgy hajtja végre a metódust, hogy a belövő nem lép bele. |
![]() | 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. |
![]() | Shift+F5 | Be/kikapcsolja a lépésszűrők használatát. |
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.
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.
Az összes töréspont egyidejűleg a
(Skip All Breakpoints) gombra
kattintva hagyható figyelmen kívül.
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ü (
) 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 almenü
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.
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.
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
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.
Az Expressions nézet
( → →
) 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.
Miután létrehoztunk egy töréspontot, a jobbklikk → 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.
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 aUgyanitt 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.
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
(
) 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 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 → → → → 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 gombot (
).