4.4. Struktúra alapú technikák

Ezeknek a módszereknek az alapja az a tapasztalat, hogy a programozási hibák gyakran a vezérlési szerkezeteket érintik. A tesztelés célja tehát a kód struktúrájának a felderítése és helyességének ellenőrzése, ezért a tesztesetek generálása a forráskód elemzése alapján történik.

Ebben a szemléletben a tesztesetek megtervezése során az a cél, hogy a vizsgált kód minden ágát végrehajtva vizsgáljuk annak működését. Mivel egy bonyolult kód végrehajtási útjainak száma nagyon magas lehet, általában nem túl nagy egységek képezik a tesztelés tárgyát.

A kódbejárás alapját a kód matematikai modellje, a vezérlési folyamat gráf (control-flow graph, CFG) képezi.

A vezérlési folyamat gráf egy olyan irányított gráf, amelyben a csomópontok a program utasításainak felelnek meg, az irányított élek pedig a végrehajtásuk sorrendjét jelölik ki. Egy döntési utasításnak megfelelő csomópontból több él indul ki, a vezérlési ágak összekapcsolódási pontjában elhelyezkedő utasításhoz pedig több él fut be. A ciklust visszafelé irányuló él reprezentálja.

A vezérlési folyamat gráf a forráskódból automatikusan előállítható, erre megfelelő segédeszközök állnak rendelkezésre. A folyamat gráf elemzésére, különböző jellemzőinek meghatározására pedig a matematika számos kidolgozott gráfelméleti algoritmust biztosít.

A tesztelés hatékonyságának mérésére mérőszámokat használhatunk. A mérőszámok meghatározására szintén rendelkezésre állnak a megfelelő algoritmusok és az azokat végrehajtani képes eszközök.

4.4.1. A struktúra alapú technikák alkalmazási területei

Vezérlés intenzív alkalmazások

  1. Ebben a kategóriában valószínűleg igaz, hogy a hibák a sok esetben a vezérlési szerkezeteket érintik. Algoritmus hibákat is kimutathat.

Tervezési hibák felderítése

  1. Elsősorban logikai hibák (pl. elérhetetlen kódrészek)

Szabványok előírásai

  1. Mivel mérőszámokkal minősíthető, sok szabvány előírja valamilyen strukturális technika használatát.

4.4.2. A vezérlési folyamat gráf

A vezérlési szerkezetet a vezérlési folyamat gráf modellezi, egy program végrehajtási eset pedig egy út bejárása ebben a gráfban. Ezért felületesen mondhatnánk, hogy a teljes tesztelés valamennyi út bejárását jelenti.

Mivel azonban a feltételek nem mindig függetlenek egymástól, a bejárható utak száma általában kevesebb, mint az összes út.

A ciklomatikus komplexitás (CK) a vezérlési gráfban megtalálható független utak maximális száma. Két út független, ha mindkettőben létezik olyan pont vagy él, amelyik nem eleme a másik útnak.

A ciklomatikus komplexitás értéke arra jellemző, hogy a program vezérlési szempontból mennyire bonyolult.

Általános tesztelési cél, hogy a teszthalmaz fedje le a független utak egy maximális (további független utakkal már nem bővíthető) halmazát. Ennek a célnak a megvalósítását az alábbi problémák nehezíthetik:

  1. Az ilyen utak halmaza nem egyedi, tehát ugyanahhoz a kódhoz akár több ilyen halmazt is lehet rendelni, ami több teszteset halmazt is jelenthet.

  2. Mivel a ciklomatikus komplexitás a független utak számának felső korlátja, egyes halmazok számossága lehet kisebb, mint a ciklometrikus komplexitás.

4.4.3. A strukturális tesztgenerálás lépései

A teszt generálás folyamata lényegében leírható az alábbi lépésekkel.

Vezérlési gráf generálása

Ez automatikusan végrehajtható a kód elemzésével.

CK (ciklomatikus komplexitás) számítása

Létezik rá algoritmus, és a kód elemző eszközök képesek ezt az értéket meghatározni.

Független utak maximális (CK db utat tartalmazó) halmazának generálása

Ebben a lépésben már adódnak problémák. Ha vezérlési gráf kört tartalmaz (márpedig tartalmaz, mert elég nehéz értelmes kódot elképzelni ciklus nélkül), az elvben végtelen számú út generálását tenné lehetővé. Ne felejtsük el, hogy a vezérlési gráf nem tartalmaz futás közbeni értékeket, így egy ciklus menetszáma (ami a valóságban természetesen véges) a gráfból nem állapítható meg. Ez a probléma kezelhető, de a generálandó utak számának növekedését jelenti. Különösen igaz ez az egymásba ágyazott ciklusok esetén. A struktúra alapú tesztelési technikák legnagyobb kihívását éppen a ciklusok kezelése jelenti.

Bemenetek generálása a független utak bejárásához

Ebben a lépésben az okozhat problémát, hogy egy adott úthoz nem feltétlenül generálható olyan bemeneti kombináció, amely annak a bejárását eredményezné. Ez persze nem jelenti feltétlenül azt, hogy az adott út elemeit képező utasítások elérhetetlenek, csak azt, hogy egy másik út részeként hajtódhatnak végre.

A tesztelés alaposságának ellenőrzése kód lefedettségi mérőszámokkal

Az idők során számos ilyen mérőszámot dolgoztak ki, és megoldott ezen mérőszámok automatikus számítása is. A mérőszámok általában 0 és 1 közé eső értékek. Azt mondhatnánk tehát, hogy a tesztelés akkor teljes, ha egy ilyen mérőszám értéke 1 (teljes lefedettség), azonban:

  1. A teljes lefedettség sokszor csak irreálisan nagy teszteset halmazzal érhető el, ezért inkább annak csak minél jobb megközelítésére törekedhetünk.

  2. A 100%-os lefedettség sem jelenti azt, hogy minden hibát megtaláltunk. (Erre példákat az egyes mérőszámok ismertetésénél mutatunk.)

  3. Mivel a különböző lefedettségi mérőszámok más és más szempontból értékelik a tesztelés alaposságát, célszerű többet is használni.

A lefedettség elemzés (coverage analysis) a mértékszámok tesztelés során történő használatának elmélete. A gyakorlat ugyanis azt mutatja, hogy a tesztesetek futtatási sorendjének „ügyes” megválasztásával eleinte a felderített hibák számának gyors növekedését lehet elérni, még viszonylag alacsony lefedettség esetén is. A teljes lefedettséghez való közelítés során a későbbiekben feltárt hibát száma fokozatosan csökken. Különböző stratégiákkal tehát jelentős költségeket lehet megtakarítani.

Itt hívnám fel arra a figyelmet, hogy a kód lefedettségi mutató nem azonos a hiba lefedettséggel (amit nem is tudunk számítani, hiszen ahhoz ismerni kellene a programban levő hibák számát). A kód lefedettség a tesztelés alaposságát méri, a hiba lefedettség az eredményességét. A tapasztalatok alapján azonban abban bízhatunk, hogy az alapos tesztelés az eredményességet is növeli.

4.4.4. Tesztminőségi mérőszámok

Ebben az alpontban az alábbi, gyakrabban használt kód lefedettségi mérőszámok számítási módjait és jelentését tekintjük át

  1. utasítás lefedettség,

  2. ág lefedettség (vagy döntés lefedettség),

  3. út lefedettség.

4.4.4.1. Utasítás lefedettség

Számítási módja:

S(c) = s/S

ahol s a tesztelés során legalább egyszer végrehajtott, S pedig a program összes utasításainak a száma

A 100% még nem biztosíték arra, hogy a teljes tesztelés minden hibát megtalál.

Egyszerű példa a fenti problémára (hibás kódrészlet):

   int a=5 ;
   x= …;
   if (x>0) a = 10;
   a = 20;
         
  1. Az utasítás lefedettség teszt 100%, mert minden sorra rákerül a vezérlés.

  2. Ha nem volt olyan teszt, amely során a feltétel igaz értéket vesz fel, nem derül ki a hiba.

4.4.4.2. Ág lefedettség (döntés lefedettség)

Számítási módja:

D(c) = d/D

ahol d az elágazási utasításokban szereplő feltételek kimeneteinek tesztelés során bekövetkezett értékeinek száma, D pedig a program összes elágazás utasításaiban szereplő feltételeinek lehetséges száma.

A döntés lefedettség tehát akkor teljes, ha a programban szereplő összes döntés minden lehetséges kimenete előfordult a tesztelés során.

A 100%-os lefedettség ugyan alaposabb tesztelést eredményez, mint az utasítás lefedettsége, de itt is van ellenpélda:

  if (felt1 && (felt2 || fuggveny() ) ) 
          u1;
   else
           u2;

Ahol felt1 és felt2 logikai kifejezések

A teljes lefedettséghez két teszteset szükséges, hiszen egy feltételnek két lehetséges kimente van. Ez a két teszteset lehet például:

  1. felt1 és felt2 igaz – ekkor az elágazás feltétele igaz,

  2. felt1 hamis, - ekkor az elágazás feltétele hamis.

Ebben a két tesztesetben egyszer sem volt szükség a harmadik operandus kiértékelésére, tehát a függvény nem hívódik meg. Ha abban van hiba, az felderítetlen marad.

4.4.4.3. Út lefedettség

Számítási módja:

P(c) = p/P

ahol p a tesztelés során bejárt utak száma, P pedig a vezérlési gráf összes útjainak a száma

Teljes út lefedettség teljes utasítás és ág lefedettséget biztosít.

Nagyon szigorú mérőszám, mert

  1. Az összes utak száma nagyon nagy lehet, ezért a tesztesetek generálása és lefuttatása erőforrás igényes.

  2. A vezérlési gráfban lehetnek nem bejárható utak az egymást kizáró feltételek miatt, tehát a teljes lefedettség nem is mindig elérhető.

4.4.5. A struktúra alapú tesztek szerepe

A fenti rövid bevezetőből is látszik, hogy a struktúra alapú tesztelés bonyolult és erőforrás igényes feladat. Végrehajtásához speciálisan erre a célra fejlesztett eszközök kellenek, mert manuális végrehajtása a bonyolult algoritmusok és a szükséges tesztesetek nagy száma miatt legfeljebb mintapéldákon lehetséges.

Bonyolultsága ellenére sem mellőzhetők ezek a tesztek a biztonság-kritikus rendszerek esetén. Az ilyen rendszereknél a program váratlan viselkedése egy adott helyzetben akár emberéleteket is veszélyeztethet, vagy jelentők károkat okozhat. Ha a teljes lefedettséget minnél jobban megközelítő, alapos tesztelésnek vetjük alá ezeket a rendszereket, a váratlan viselkedés valószínűsége az elfogadható kockázati szint alá csökkenthető.

A tesztek során alkalmazott lefedettségi mutatók alkalmazhatók az utasításoknál nagyobb absztrakciós szintű struktúrákra is. Ilyenkor a vezérlési folyamat gráf elemei lehetnek például alrendszerek, modulok, interfészek, vagy akár a menüstrukrúra elemei. Az integrációs tesztek esetén ilyen módon módon mérhetjük, hogy a végrehajtott teszt készletek a rendszer elemeit mennyire alaposan fedték le.