Bármilyen szoftvert alapvetően két módon tudunk tesztelni. Az egyik módszer a manuális tesztelés. Ez azt jelenti, hogy egy dedikált ember a program dokumentációja, leírása alapján különböző szituációkban megnézi, hogy a program hogy működik. A különböző szituációk a tesztesetek. Ezek rendelkez(het)nek előfeltételekkel, végrehajtandó lépésekkel, egy elvárt állapottal és egy, a program futása közben produkált állapottal. Ilyen teszteset egy program esetén lehet például az, hogy elindítjuk és a program ablaka megjelenik a képernyőn. Ezen a teszten értelemszerűen gondolhatnánk, hogy akkor megy át a program, ha tényleg megjelenik a program a képernyőn. Viszont a történet ennyire nem egyszerű. Mi van akkor, ha egy nem támogatott operációs rendszeren akarom futtatni? Akkor is meg kell jelennie és nem szabad hibával elszállnia?1
A fenti kis szösszenetből sejthető, hogy minél jobban van egy szoftver dokumentálva, annál részletesebb és jobb teszteket lehet hozzá készíteni, de 100%-ra egy szoftvert sem lehet tesztelni. Egy bizonyos pont után felesleges idő és pénz kidobás a szoftvert tovább tesztelni, meg kell fogalmazni egy kilépési feltételt.
A kilépési feltételek azon feltételek halmazát jelentik, melyeknek melyeknek teljesülésekor egy meghatározott feladat hivatalosan befejezettnek tekinthető. Ez a szoftver bonyolultságától függően lehet akár 1 teszteset teljesítése, vagy akár 3 millió teljesítése is. Ha 3 millió teszt esetében kellene megnézni a program működését, akkor két lehetőségünk van: vagy többen tesztelik a terméket, vagy automatizáljuk a tesztek végrehajtását. Utóbbiak az automata tesztek.
Az automatizált teszteknél különböző tesztszintekről beszélhetünk. A manuális tesztelés szintje általában a rendszer (System vagy UI) teszt. Ebben az esetben a tesztelő az egész rendszert teszteli átfogóan. Költség szempontjából ez a legdrágább, mivel ekkor már a szoftver majdnem kész. Tételezzük fel, hogy egy számológép programot készítünk. Ha itt jön ki az, hogy a szorzás funkció nem megfelelően működik, akkor annak a javítása sokkal drágább lesz, mint ha a fejlesztés közben ez kiderült volna.
A fejlesztés közbeni tesztelés szintje az egységteszt (Unit test). Egységnek a legkisebb önállóan tesztelhető szoftver egységet nevezzük. Programozói nyelven ez egy metódus, vagy C# esetén specifikusan ide tartoznak még a Property-k is2.
Általában az egységteszteket egy egységteszt keretrendszer futtatja. Ez egy olyan környezetet biztosító keretrendszer, amelyben egy komponens egyaránt tesztelhető különállóan, valamint a megfelelő segédprogramokkal. Hibakeresési funkciójával támogatja a szoftverfejlesztő munkáját is. Szerencsére a Visual Studio beépítetten tartalmaz egy ilyen tesztfuttató környezetet.
Attól, hogy a program nagy része egységtesztelve van, még nem biztos, hogy teljes egészében hiba nélkül fog működni. Ezt egy valóélet-beli példával egyszerű szemléltetni. Tételezzük fel, hogy van egy nyílászáró gyár, ami ablakokat gyárt. A gyárban leellenőrzik, hogy az ablak nyitható és csukható, megfelelt az egységteszt szintnek. A helyszínen a kőműves beépíti az ablakot, de véletlenül fordítva, vagyis úgy, hogy az utcáról nyitható az ablak és nem belülről. Az ablak továbbra is megfelel az egységteszt szintnek, mert nyitható és zárható, de már együttműködési szinten nem.
Az együttműködőképességi (Integration) tesztek több egység együttműködését tesztelik. Ezeknek a futtatását is általában az egységteszt keretrendszer szokta végrehajtani. Ezeknek a költsége egy picivel nagyobb, mint az egységteszteknek, mivel ezek bonyolultabbak.
A rendszerszintű tesztelés is automatizálható, de ezek automatizálása a legköltségesebb. Az összes rendszerszintű teszt program lényegében egy olyan program, ami a szoftver működését végig kattintgatja és reagál a program kimenetére, ami alapján eldönti, hogy megfelel-e a tesztkövetelményeknek, vagy nem. Ez a szoftver felépítéséből adódóan lehet bonyolult, vagy lehetetlen feladat.
Éppen ezért egy egészségesen tesztelt program esetén az a legjobb, ha az egységtesztekből van a legtöbb és a rendszer szintű tesztekből a legkevesebb, egyfajta piramisba foglalva középen az együttműködőképességi teszteket. Persze létezik olyan szoftver is, amely esetén ez a piramis fordított. Az ilyen szoftverek esetén a bővítés és későbbi hibajavítás költsége jóval magasabb, mint egy egészséges teszt piramissal rendelkező szoftver esetén.
Tesztelés jósága, metrikák
Kérdés, hogy mikor tekintjük jól lefedettnek a programot? Erre a kérdésre válaszul különböző metrikák születtek meg. Az egyik ilyen a line coverage. Ha a programunk 100 soros és a teszt kód futtatása során a 100 sorból 40 hajtódik végre, akkor line coverage alapján mondhatjuk, hogy a kód lefedettsége 40%. Ez leírva elég rosszul hangzik, de gyakorlatban nem biztos, hogy annyira szörnyű helyzetről beszélünk.
Tételezzük fel, hogy ez a 100 soros program egy JSON fájlt tölt le és a benne található információk alapján számol valamit. A JSON fájl kibontásához és memóriában tartásához készítettünk osztályokat. Ezeket kód írja le, de logikát nem tartalmaznak, illetve a JSON fájl osztályokra átalakítását a használt keretrendszerünk készítői megtették, ezért nem teszteljük a programnak ezt a részét. Viszont amit tesztelünk, az a számítás elvégzése, ami belefért 40 sorba.
Ez alapján az állatorvosi ló példa alapján könnyen belátható, hogy önmagában a sor lefedettség metrika semmit sem jelent. A probléma az, hogy ha felülről, menedzsment szinten erőltetik ennek a metrikának a növelését. Tapasztalatom szerint 60%-os lefedettség elérése nem jelent különösebben nagy erőfeszítést. 60% feletti lefedettség elérése könnyen lehet, hogy ugyanannyi időt jelent (rosszabb esetben még többet), mint megírni a programot.
De tételezzük fel, hogy 100%-os a teszt lefedettségünk. Ilyenkor naív módon gondolhatjuk azt, hogy a programban nincs hiba. Azonban a történet nem ennyire egyszerű. Ha az alapoknál félrecsúszott a specifikáció, akkor a 100%-os lefedettség csak azt jelenti, hogy a programunk 100%-ig biztosan rosszul működik.
Jobb metrika, ha branch coverage-et nézünk. Ez a metrika azt nézi, hogy ha van egy döntésünk (if utasítás a programban), akkor mely ágai kerülnek végrehajtásra a tesztben. Egy egyszerű if-else esetén 100% a branch coverage, ha mindkét ága tesztelve van.
Ennek a metrikának az előnye, hogy csak a tényleges logikát számolja bele. Ez egy picivel jobb képet fest a program állapotáról, mivel hiba csak a logikát tartalmazó részekben fordul elő.
Itt már lehet értelme a 100%-os lefedettség megcélzásának, de ez szintén idő és nem is biztos, hogy van értelme. Éppen ezért a gyakorlatban a teszt stratégia dokumentum rögzíti a projekt kezdete elején, hogy mit és hogyan kell tesztelni és milyen mélyrehatóan.
-
Ebben a példában a támogatott operációs rendszer egy nem funkcionális (NFR) követelmény. Ezen követelmények a szoftver azon aspektusaival foglalkoznak, amelyek nincsenek közvetlen hatással a rendszer funkcionalitására, mégis döntően meghatározzák a fogadtatását a felhasználók és a támogatók körében.↩
-
Abban az esetben, ha a Property nem Auto property, vagyis specifikusan valami logikával van felvértezve.↩