A modellalapú tesztelésről

Bevezető

Kedves Olvasó! Ennek a cikknek írásával egy célom van: egy magyar nyelvű, kedvcsináló bevezetőt adni a kezedbe erről a tesztelési megközelítésről, hogy ha munkád során úgy látod, hogy alkalmazni fogod, akkor ez egy jó kiindulópont legyen! Vágjunk is bele!

Mi is az az MBT?

Amikor ezeket a sorokat írtam, kíváncsi voltam, vajon hogyan definiálják mások a modellalapú tesztelést. „A modell alapú tesztelés egy black-box megközelítés, amellyel általános tesztelési feladatokat, mint a teszteset generálást és tesztek kiértékelését teszi lehetővé, amely a tesztelendő alkalmazás modelljén alapul.” Így definiálta Ibrahim K. El-Far a megközelítést 2001-ben[1].

Valójában ez a definíció ma már nem állja meg a helyét, léteznek olyan modellalapú tesztelési technikák, amelyek kifejezetten white-box technikának számítanak, sőt, a téma egyik úttörő kutatójának Mark Utting előadásának a címe is erre utal: Model-Based Testing: Black or White?[2] Bármilyen definíciót is találunk, a lényeg, hogy a tesztelendő rendszer viselkedéséről a követelmények alapján egy modellt kreálunk, majd ebből automatikusan generáljuk ki a teszteseteket. A megközelítés főbb lépéseit jól szemlélteti a következő ábra:



Hogyan készül a modell?

Rendszerünktől függően többféle modellalkotási formát választhatunk. Létrehozhatunk pl. döntési táblákat, nyelvtant (Grammars), Markov láncokat, kiindulhatunk UML diagramokból vagy végesállapot automatákból. Konkrét példánkban az utóbbival fogunk majd foglalkozni. Azt, hogy milyen típusú modellre van szükségünk, a terméktől, a követelményektől és a tesztelési céljainktól függ. A tesztelési célokat azért kell megemlíteni, mert, mint ahogy említettem, fehér és fekete dobozos tesztelésnél is lehet alkalmazni a megközelítést. Ha egy adott kódrészlet jobb lefedettsége a célunk (pl. modell alapján JUnit teszteket akarunk generálni), nem biztos, hogy a követelményekből indulunk ki. A lényeg: reprezentáljuk a tesztelendő rendszer viselkedését. Nem kell az egész rendszerünket lemodellezni, tipikusan csak egy részét kell reprezentálni, a feni modelltípusok valamelyikével. Először gondoljuk át a tesztelési követelményeket, tesztelési céljainkat és a felhasználói eseteket.

És ha már van egy modellem?

Ha megalkottunk egy modellt, pl. egy véges állapot automatát, egy MBT eszközzel generálunk belőle teszteseteket. Természetesen nincs olyan eszköz, amely bármely modellből azonnal futtatható teszteket gyárt, ezért először egy köztes állapotú kimenetet hoz létre, ezek az absztrakt tesztesetek. Ebből már meg tudjuk állapítani, hogy hány darab tesztesetünk van. Ezek után nekünk kell implementálni egy eszközt, ami az absztrakt tesztesetekből futtatható teszteseteket gyárt. Ettől nem kell megijedni, ezeknek az eszközöknek az outputjai úgy vannak kialakítva, hogy könnyen feldolgozhatóak legyenek, bátran kijelenthetjük, nem ez lesz a tesztelési munka oroszlánrésze. Ezek után nincs más dolgunk, mint ráereszteni a futtatható tesztjeinket a rendszerre, és kiértékelni az eredményeket. Természetesen nehéz egy modellt egyből tökéletesen kialakítani, nem könnyű ilyen formába önteni a rendszert úgy, hogy minden viselkedési aspektusát lefedje tesztelési szempontból. A modellünket és azt az eszközünket, ami a transzformációért felelős, folyamatosan csinosítgatnunk kell, cserébe a későbbi karbantartás már jóval könnyebb.

Nézzünk egy konkrét példát!

 Hallom, és elfelejtem. Látom, és elhiszem. Csinálom, és megértem. (Konfucius)

 Első éves hallgatóként az egyetemen, a fenti idézetet hallottam egy előadáson. Azóta sokszor eszembe jut, mint ennek a cikknek az írásakor. Tartsuk most ezt szem előtt, nézzünk egy konkrét példát!
SIM kártyák és modellek

Képzeljük el a következő programot: programunk azért felelős, hogy ha egy mobiltelefonba SIM-kártyát helyezünk, kérje be a PIN-kódot. A felhasználónak 3 lehetősége van, hogy korrekt PIN-kódot vigyen be. Ellenkező esetben PUK-kódot kér tőle, erre már 10 lehetőség van. Ha a fenti kísérletek bármelyikénél megfelelő kódot viszünk be, a SIM kártya hitelesítése sikeres lesz. A tizedik sikertelen PUK-kód bevitele után viszont letiltjuk a SIM kártyát. Vajon hány tesztesetet rejt magában a probléma? A rendszert az alábbi gráf szemlélteti:



Tekintsünk most el a valódi PIN-kódok formai követelményeitől (pl. hogy 4 jegyű vagy, hogy csak számokat tartalmazhat). Ha a sikeres tesztesetekre fókuszálunk, a két végállapotba összesen 14 féle úton tudunk eljutni. 13 út visz a sikeres hitelesítéshez és csak egyféleképpen tudjuk letiltani SIM-kártyánkat. A negatív tesztesetek a meg nem engedett állapotátmenetekből keletkezhetnek. Például ha a programozónk úgy kódolja le a programot, hogy az a 10. alkalommal hibás PUK-kód beírása után nem letiltja a kártyát, hanem újra bekéri a PIN kódot! Ez egy meg nem engedett állapotátmenet. Ezt a hibát egyébként a 14. tesztesetünk el is csípné.

Lássunk neki!

A cikk írásakor mindenképpen egy open source MBT programot szerettem volna választani példaként, így esett a választásom a graphwalkerre[3]. A graphwalker vagy korábbi nevén mbt[4] egy XML alapú gráf leíró nyelvből, a GraphML-ből[5] tud teszteket generálni. Ilyen gráfokat grafikus felületen is létrehozhatunk pl. yEd nevű programmal[6]. A yEddel online is rajzolhatunk GraphML formátumú gráfokat, sajnos azonban az online változat olyan formátumot generál, amit a graphwalker nem fogad el. Nincs mese, le kell töltenünk a szintén ingyenes asztali változatot. A yEd működés közben:



Próbáljuk meg a programmal megrajzolni a PIN-kódos feladatunkat! Ez a gráf lesz a modellünk, ebből fog a graphwalker teszteseteket generálni. Az eddigi ábrákat egyébként én is a yEddel készítettem.

A Graphwalkerről

Ez a nyílt forráskódú program képes arra, hogy graphML formátumú gráfokat bejárjon. Egy-egy bejárás egy-egy lehetséges tesztesetet/use case-t reprezentálhat. Ezek a bejárások egyben absztrakt tesztesetek is. Fontos megjegyezni, hogy a graphwalker nem ismer egy gráfban kilépési, ill. végpontot. Helyette a tesztgenerálást kilépési feltételekkel tudjuk megállítani. Ilyen feltételek pl. az élek vagy a gráf pontjainak lefedettsége (hány élt, pontot érintett az összesből), vagy a lépések maximális hossza, egy meghatározott időtartam, soha ne álljon le, stb. A program írója ezt azzal magyarázza, hogy hosszú, kiszámíthatatlan tesztek generálásához tervezte a graphwalkert. Megmondhatjuk a programnak azt is, milyen bejárási algoritmus szerint járja be a gráfot (véletlen bejárás, A* algoritmus, valamint a kettő keverékéből álló SHORTEST_NON_OPTIMIZED[7]). Töltsük le a programot[8], amely összesen egy jar fájlból áll, majd indítsuk el:

java -jar <jar fájl neve>.jar -v

Ha minden rendben ment és a képernyőn a graphwalker verzióját és a benne lévő csomagok listáját látjuk, akkor térjünk vissza a yEdhez! Ha megrajzoltuk a PIN-kódos feladatunk gráfját és hasonlít a cikkben lévő legelső ábrához, szabjuk kicsit graphwalker specifikussá! A program a következő követelményeket állítja a bemeneti gráf elé:

  • Legyen irányított gráf vagy páros gráf
  • Legyen pontosan egy pont, aminek ’Start’ a címkéje, innen indul a bejárás
  • A Start pontból induljon ki él, aminek van címkéje is
  • Minden pontnak legyen címkéje, (az éleknek nem kötelező, kivéve az elsőt)
  • A címkék ne tartalmazzanak szóközöket

A többi kitétellel most nem foglalkozunk, azok a graphwalker további funkcióihoz kötődnek. Hozzuk tehát valami hasonlóra a gráfunkat:



Láthatjuk, hogy az első pont a Start, belőle indul egy e_insert él, a többi pontot pedig úgy neveztük el, hogy ne legyen benne szóköz. Szintén láthatjuk, hogy a v_auth és a v_deny_sim (sikeres, ill. sikertelen hitelesítés, ezek lennének a végpontjaink) pontokat összekötöttük a Starttal. Erre azért volt szükség, mert, mint említettem, a program nem ismer végpontokat. Így egy újabb bejárásra úgy vesszük rá, hogy visszairányítjuk a Start pontba. Tévedés ne essék: aki azt gondolja, hogy így a gráf azt a programot reprezentálja, ami a sikeres PIN/PUK kód beírása után ismét kódot kér, annak ugyan igaza van, de megtehetjük, hogy megmondjuk a modellnek, hogy indítsa újra a programot a legelső él érintésekor. Ha nem kötöttük volna össze a két végpontot a Starttal, akkor a graphwalker indításakor hibaüzenetet kapnánk. A program elkezdene bolyongani a gráfban, előbb-utóbb elérne egy végpontot, amiből már nem vezet sehová sem él, ha ez a szerencsétlen helyzet olyankor áll elő, amikor még nem teljesítette a kilépési feltételt (pl. nem érintette az összes élt), akkor „No possible edges available for path” hibaüzenetet kapunk.

 Lehetőségünk van egyébként kiterjesztett modellt is alkotni, ebben már tudunk őrfeltételt megadni, ill. változókat kezelni. Ha ez megvan, generáljunk teszteseteket a modellből:

java -jar <jar fájl neve>.jar offline -g A_STAR -s EDGE_COVERAGE:100 -f <modell>.graphml

Az offline paraméter azt jelenti, hogy egyszer generáljuk ki a teszteseteket és kiírjuk a standard outputra. A –g opció a bejárási algoritmust határozza meg, jelen esetben A*-ot választottunk. A kilépési feltételünk az élek teljes lefedettsége, az utolsó paraméter pedig a bemeneti fájl. Ha minden jól megy, valami hasonló kimenetet kapunk:

Start
e_insert
v_pin_attempts
v_auth
Start
e_insert
v_pin_attempts
v_pin_attempts
v_auth


Gyakorlatilag a pontok és az élek felsorolása bejárási sorrendben. Az üres sorokat azért látjuk, mert nem adtunk minden élnek címkét. Kaptunk tehát egy nagy absztrakt tesztesetet, amely teljesen bejárja a gráfot, de ha összeszámoljuk, hányszor érintettük a Start pontot, megkapjuk a korábban emlegetett 14 esetet. Megtehettük volna azt is, hogy kilépési feltételnek a két végpont elérését adjuk, így azonban nem kapnánk meg az összes esetet, mert az algoritmus, amint eléri a végpontot, kilép. Ha mindehhez még RANDOM-ra állítjuk a bejárást, akkor van esélyünk minden esetet kigeneráltatni, de túl sokszor kellene elindítanunk a generálást.

Hogyan lesznek futtatható tesztjeink?

Lehetőségünk van minden ponthoz, ill. élhez írni egy metódust java-ban, ezeket egy osztályba tehetjük, majd megjelölhetjük executorként a lefordított osztályt, jelezve a graphwalkernek, az egyes pontok, élek érintésekor futtassa a metódusainkat. Az eszköz egyébként soap szolgáltatásokon keresztül nem csak a java-t támogatja. Ezeket a metódusoknak egy sablonját még ki is tudjuk generáltatni egy template fájl alapján. A template fájl pl. így nézhet ki:

public void {LABEL}()
{
  log.info( "{EDGE_VERTEX}: {LABEL}" );
  throw new RuntimeException( "The {EDGE_VERTEX}: {LABEL} is not implemented yet!" );
}


Ebből a program kitudja cserélni a {LABEL} és a {VERTEX} részeket a megfelelő pontokra élekre, megkönnyítve a dolgunkat az implementálásnál:

>java -jar <jar fájl neve>.jar source -f <modell>.graphml -t java.template

public void e_insert()
{
  log.info( "Edge: e_insert" );
  throw new RuntimeException( "The Edge: e_insert is not implemented yet!" );
}

public void v_auth()
{
  log.info( "Vertex: v_auth" );
  throw new RuntimeException( "The Vertex: v_auth is not implemented yet!" );
}
:
:


Ezek után nincs más dolgunk, mint bemásolni a fenti kimenetet és leimplementálni az egyes pontokhoz, élekhez tartozó tesztlépéseket (egy-egy pont, él érintésekor az azonos nevű metódus hívódik meg). Pl. a kezdő élt reprezentáló metódus indítsa el a PIN programot, várja meg amíg kéri a kódot, v_auth-ba vezető él adja be a programnak a korrekt PIN/PUK-kódot stb. Akár verdiktet is állíthatunk a graphwalker speciális osztályaival. Valamint lehetőségünk van minden információt logolni a későbbi analízis céljából. Fordítsuk le a java osztályunkat, majd létre kell hoznunk egy xml fájlt[9], ami megjelöli az osztályt futtatóként, valamint egyéb paramétereket tehetünk bele. Ez az xml pl. így nézhet ki:

<?xml?>
<!DOCTYPE MBTINIT SYSTEM "mbt_setup.dtd" >
<MBTINIT EXECUTOR="<java osztály>”>
  <MODEL/>
  <CLASS/>
  <GENERATOR>
    <CONDITION/>
  </GENERATOR>
</MBTINIT>


Ez után az xml-t adjuk meg paraméterként a graphwalkernek:

java -jar mbt-2.2-beta14.jar xml -f xml/UC01.xml

És már fut is a tesztesetünk! Futás közben a bejárásnak megfelelő tesztlépések hajtódnak végre. Érdemes még a http://mbt.tigris.org/demo.html oldalon található példát is végignézni.

Miért jó az MBT?

A módszer teljes mértékben automatikus. Automatizált teszteseteket kapunk. A modell jobb áttekinthetőséget biztosít, segíti a rendszer megértését. A legnagyobb előny a tesztek karbantarthatósága. Képzeljük csak, ha PIN2 és PUK2 kódokat is kellene kezelnünk az előző példában. Elég a modellt kiegészítenünk, és ha egy metódus kezeli az összes fajta (PIN1, PIN2, PUK1, PUK2) kód bekérését, a metódusokhoz alig kell hozzányúlni. Ugyanúgy kigeneráljuk a tesztjeinket, és már az új feature is le van tesztelve. Szintén igaz, hogy jellemzően jobb lefedettségadatokat tudunk produkálni ezzel a megközelítéssel.

Milyen hátrányai vannak?

Amikor először hallottam az MBT-ről, első gondolatom az volt, hogy modell címszó alatt újra kell implementálni a terméket. Ez nem igaz, hiszen egy modellnek nem kell hatékonynak vagy gyorsnak lenni, mint egy programnak, ettől függetlenül nagy, bonyolult rendszereket nem könnyű modellezni. Szintén igaz, hogy komoly rendszerismeretre van szükség a tesztelőktől a modellalkotáshoz. Aki foglalkozott már egy konkrét MBT eszközzel, az tudja, hogy a feleslegesen kigenerált, redundáns tesztesetek jelenléte szintén egy ide sorolható probléma. Általánosságban elmondható, hogy funkcionális tesztekre nehézkes az MBT alkalmazása. Inkább kisebb egységekre, jellemzően unit tesztekre érdemes alkalmazni.

Milyen MBT eszközök vannak?

Ma már sok modell alapú tesztelő tool létezik. Sajnos a nyílt forráskódú szoftverek még gyenge lábakon állnak. A graphwalker szimpatikus megoldás, de gyengén dokumentált és készítői nem fejlesztik rohamléptekben. A cikk elején említett Mark Utting által is fejlesztett modelJUnit hátránya pedig, hogy kódszinten kell megírni az állapotautomatát, amelyet aztán lehet grafikusan kirajzoltatni, de közel sem olyan komfortos, mint a fent taglalt graphML. A jó hír, hogy már hazánkban is van kutatási projekt és fejlesztési törekvés a modell-alapú tesztelésre[10]. Ezzel szemben több kereskedelmi szoftver is épít az MBT-re, melyek lassan-lassan alkalmazásra kerülnek az iparban. Ezek közül a Conformiq Tool Suite™-ot említeném meg. Ez egy kiforrott eszköz, de a drága licence-díj hamar eltántorítja az érdeklődőket. A Microsoftnak szintén van egy Spec Explorer nevű ingyenes kiegészítője a Visual Studio-hoz. Ehhez azonban meg kell vennünk a Visual Studio legújabb fizetős változatát, az Express verzióval ugyanis nem működik, és természetesen csak .NET technológiákkal kompatibilis. Az elérhető szoftverekről egy jó gyűjteményt találunk a https://goldpractice.thedacs.com/practices/mbt/ oldalon.

 Remélem a bevezetőben említett célom megvalósult. Aki még nem találkozott az MBT-vel, kapott egy áttekintést és egy jobb összképet erről a tesztelési megközelítésről. Ha valaki kedvet kap, kipróbálja a fent említetteket és problémába ütközik, ne habozzon, nyugodtan keressen meg engem: arpad.toth@t-systems.com

[1] https://goldpractice.thedacs.com/practices
[2] http://video.google.com/videoplay?docid=5521890509476590796#
[3] http://graphwalker.org/
[4] http://mbt.tigris.org/
[5] http://graphml.graphdrawing.org/
[6] http://www.yworks.com/en/products_yed_about.html
[7] Az algoritmus leírása megtalálható a http://mbt.tigris.org/ oldalon
[8] http://graphwalker.org/index.php?id=download
[9] A fenti xml-hez szükség van még egy fájlra, amit az xml mellé kell másolnunk, letölthetjük innen: http://mbt.tigris.org/mbt_setup.dtd
[10] http://home.mit.bme.hu/~micskeiz/pages/modelbased_testing.html
Szerző:
Tóth Árpád

<< Vissza