Letöltés mappa rendező program – Tesztelés alapjai
Az előző részben elkészült a programunk, így már „csak” tesztelni kell és a felmerülő hibákat javítani. De előtte teszünk egy kis kitérőt.
A tesztelés egy igen nagy témakör, és mielőtt a tényleges tesztekbe belevetnénk magunkat, egy picit beszélnünk kell a tesztelés alapjairól. Szeretném leszögezni, hogy a cikkben szereplő információk nem helyettesítenek egy ISTQB oktatást, illetve az itt leírtak egy fejlesztő szemszögéből mutatják be a tesztelést.
Alapok
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?
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étel azon feltételek halmaza, 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ő teszt szintekről beszélhetünk. A manuális tesztelés szintje általában a rendszer (System vagy UI) teszt. Vagyis 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ég (Unit) teszt. 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 is.
Általában az egység teszteket 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 teszt futtató 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.
Videó forrása: https://www.reddit.com/r/ProgrammerHumor/comments/isidkn/unit_testing_vs_integration_testing/
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.
Ha a programunk egység- és együttműködőképességi tesztekkel jól le van fedve, akkor egy kisebb-nagyobb átalakítás során elkerülhető, hogy hibákat vigyünk a rendszerbe és korábban már működő funkciókat törjünk el.
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.
Végszó
Ennyi felvezetés után remélem látszik, hogy a tesztelés nem egy egyszerű témakör és bőven mélyebb az a nyúl üreg, mint elsőre hittük.
A jó teszteléshez kell egy tesztelői gondolkodásmód, ami nem azonos a fejlesztőivel. Fejlesztés közben hajlamosak vagyunk elfelejtkezni részletekről, mivel a nagyobb probléma megoldása lebeg a szemünk előtt. Azonban tesztelés során érdemes végignéznünk és végiggondolnunk, hogy hol romolhat el a program és vajon sikerül-e elrontanunk.
A következő részben a tényleges tesztek megírásával folytatjuk. Ígérem arra nem kell majd olyan sokat várni, mint erre a részre 🙂 Addig is itt egy cuki nyuszó kárpótlásként 🐰