Hogyan alkalmazzunk automatikus tesztbemenet-generálást?
Napjainkban egyre nagyobb népszerűségnek örvendenek az automatizált tesztelési módszerek mind az ipari felhasználás, mind az akadémiai kutatások esetén. A két terület között azonban láthatóan eltérő az automatizálási törekvés: míg akadémiai területen sok kutatás a tesztek előállításának automatizálását célozza meg, addig az ipari alkalmazások döntő részében az automatizálás a tesztek menedzselésére, futtatására törekszik. Jelen cikkben a két szféra közötti szakadék áthidalására történt erőfeszítéseim során nyert tapasztalatokat mutatom be.
Az akadémiai területet tekintve aktív kutatások folynak többek között az egységtesztelés terén is, hiszen a szoftverminőséget alapvetően meghatározó tesztelési szintről van szó. Az fejlesztési irányok azonban az egységtesztek előállításának vizsgálata felé mutatnak, ami magába foglalja a struktúra alapján történő származtatásukat is. Ebben az esetben a tesztelt szoftver forráskódja alapján történik a tesztek generálása, természetesen szem előtt tartva a fedettségi statisztikákat. Fontos kiemelni azonban, hogy a forráskód struktúrájának alapján nehézkes teljes teszteseteket származtatni, hiszen valójában nem vagy csak nehezen lehet megfogalmazni valós, elvárt kimeneteket.
A tesztbemenetek generálásáról
Az ide kapcsolódó kutatások tehát valójában tesztbemenet-generálással foglalkoznak, amelyekből előálló bemeneteket kiegészítve ugyanakkor tesztesetté is előléptethetünk. Bemenetek forráskód-struktúra alapján történő generálására több technika is létezik, amelyek közül az egyik legismertebb a szimbolikus végrehajtás. A módszer a kód végrehajtását egy magasabb absztrakciós szinten, szimbolikus változók segítségével végzi, a végrehajtás eredményeként pedig indirekten konkrét tesztbemenetek állnak elő.
A szimbolikus végrehajtás a működési elvéből fakadóan viszonylag sok kihívással küzd. A teljesség igénye nélkül ezek például a ciklusok kezelése, a mély bejárási tér, többszálúság ellenőrzése, környezeti interakció. Megoldásuk a legtöbb kapcsolódó kutatás témájaként szolgál jelenleg is, de a sok biztató eredmény ellenére vannak bizonyos tényezők, melyek maradéktalanul nem oldhatók meg.
A szimbolikus végrehajtást technikájának implementációjaként több eszköz is készült, habár az egyes eszközök képességei között meglehetősen nagy különbség lehet. A legismertebbek között említhető a NASA által fejlesztett Symbolic PathFinder, a Microsoft kutatórészlege által készített Pex, és a nyílt forráskódú projektként létező KLEE is. Lassan harmadik éve, hogy a Pex eszköz vizsgálatával foglalkozom, ugyanis a megvizsgált eszközök közül a Pex-et találtam a legfejlettebbnek a képességeiket összemérve. Ezt a feltételezést a későbbi tapasztalataim is alátámasztották.
A Microsoft Pex a szimbolikus végrehajtást parametrizált egységteszteken (PUT – Parameterized Unit Test) keresztül alkalmazza a tesztelni kívánt kódegységen. A PUT lehetőséget ad előfeltételek, elvárt kimenetek, izoláció és más tesztelési logika megvalósítására is. Az eszköz a PUT paraméterlistája alapján generál bemeneteket a végrehajtás során. Ezeket a bemeneteket a parametrizált tesztekbe helyettesítve konkrét tesztesetek kaphatók, melyek el is menthetők későbbi felhasználásra.
Tapasztalatok komplex rendszerekben
Az eszközt két komplex szoftverprojekten alkalmaztam tesztek előállítására a tesztbemenet-generálás segítségével. Az első egy összetett modellező eszköz volt, amelyet korai stádiumában vizsgáltam meg. A folyamat során feltérképeztem azokat a tényezőket, melyek befolyásolják a hatékony tesztgenerálást.
A Pex használatakor az első problémát az izoláció megvalósítása jelentette, ahol egységtesztelés lévén, mind a külső függőségeket, mind a fájlrendszer felé történő hívásokat le kellett választani. A komplex bejárási tér miatt azonban a hívások pontos, hely szerinti beazonosítása különösen nehéz feladat volt a bejárás átláthatatlansága miatt.
A másik probléma, amelybe beleütköztem a Pex használatának első néhány lépése során a komplex objektumok létrehozása és megfelelő állapotba történő beállítása volt. Ezt a Pex számára Factory minta segítségével lehet megadni, amelyre az egyszerű konstruktorokkal rendelkező osztályokon kívül a legtöbb esetben szükség is van. Ehhez kapcsolódóan érdemes említést tenni a dinamikusan, futási időben példányosított objektumok kezeléséről is, melyet a Pex nem tud kielégítően kezelni. A projekthez tartozó eredmények viszont bizakodásra adtak okot, hiszen az eszköz a vizsgált 136 metódushoz 371 teszteset generált, melyek körülbelül 99%-os kódblokk lefedettséget biztosítottak.
A felfedezett hibák között a következőket érdemes kiemelni: defenzív programozás hiánya (rosszindulatú bemenetek), inkonzisztens változtatások, kisebb funkcionális hiányosságok, illetve dinamikusan elérhetetlen kódrészletek. Ugyanakkor nem szabad elfeledni, hogy a pusztán kódlefedettségen alapuló szoftverellenőrzés hatékonysága egyes esettanulmányok szerint megkérdőjelezhető lehet.
Az eredmények láthatóvá tették számomra a Pex és a szimbolikus végrehajtás komplex rendszerekben történő alkalmazási lehetőségeit, így egy ipari partner segítségével egy tartalomkezelő rendszer végleges változatához is készítettem teszteket. A forráskód mérete ebben az esetben már egy nagyságrenddel nagyobb volt (10.000 LOC), így az eszköz használata több előkészületet igényelt. A vizsgálat azonban felderített több olyan hiányosságot is, melyeket a meglévő tesztkészlet nem vizsgált. Közülük is érdemes említést tenni egy biztonságtechnikai problémáról, illetve egy olyan változókezelési gondról, mely az adatkezelési réteg és az üzleti logikai réteg között okozhatott kommunikációs zavart.
A két projektből nyert tapasztalatok rámutattak arra, hogy a Pex képes lehet ipari méretű alkalmazások esetén is olyan teszteket előállítani, melyek a tesztek manuális tervezése és implementációja során kimaradtak. A generált tesztbemenetek között azonban sok esetben irrelevánsak is voltak, melyek a valós működés során nem fordulhattak elő. Ezeket a szimbolikus végrehajtás számára előfeltételek segítségével lehet megfogalmazni, amellyel ezek a bemenetek kizárhatók a generálásból.
Érdemes alkalmazni? És ha igen, akkor hogyan?
Az előzőekben az eszköz használatából nyert, saját tapasztalataimat mutattam be. Felvetődik így a kérdés, hogy új felhasználók (például tesztelő mérnökök) hogyan alkalmazhatják a gyakorlatban, illetve, hogy egyáltalán érdemes-e alkalmazni? A kérdéssel a Microsoft is foglalkozik, de több másik szimbolikus végrehajtást alkalmazó eszköz esetén is már készültek kapcsolódó esettanulmányok. A saját tapasztalataim alapján a válasz egyértelműen igenlő, azonban a mérnökök általi hatékony használathoz véleményem szerint jól meghatározott folyamatra, lépésekre, illetve támogató eszközökre lehet szükség. Az igenlő választásomat az a tény is alátámasztja, hogy a Microsoft a Visual Studio 2015-ös verziójában már Smart Unit Tests néven integráltan szállítja a Pex eszközt.
Az eszköz hatékony használatához a tesztgenerálást a fejlesztéssel párhuzamosan javasolom. Az iteratív folyamat bevezetésével elkerülhetők azok a használat során felmerülő nehézségek, melyeket a tapasztalataink között is bemutattunk (pl. példányosítás, izoláció). Az alkalmazandó lépések a tesztelő mérnök számára a következők lehetnek egy agilis fejlesztési folyamat során.
- Specifikáció: Interfészek kidolgozása a fejlesztőkkel közösen figyelembe véve a tesztelhetőségi szempontokat.
- Definiálás: Parametrizált egységtesztek definiálása minden vizsgálandó egység számára az AAA (Arrange-Act-Assert) mintát követve, ezen kívül pedig az izolációs környezet megtervezése (kódstruktúra elemzésével).
- Implementáció: Mockok és csonkok, illetve orákulumok létrehozása a specifikáció alapján.
- Végrehajtás: Objektumokat előállító Factory metódusok létrehozása, majd Pex futtatása minden parametrizált egységteszten.
- Iteráció: Eredmények visszacsatolása a fejlesztők számára, majd ismétlődően a Pex futtatása a módosított kódon és visszajelzés.
Összegzés
Általánosságban tekintve nem csak a Pex, hanem a többi szimbolikus végrehajtást implementáló eszköz is alkalmas lehet arra, hogy csökkentsük az esetlegesen előforduló hibák számát, ezáltal a forráskód minőségét magasabb szintre emeljük. Ezen kívül az eszközök által készített tesztkészlet felhasználható a későbbiekben regressziós célokra is, hiszen a generálódott tesztek elmenthetők és behelyezhetők például folytonos integráció folyamatába is. Összefoglalva tehát elmondható, hogy a tesztbemenet-generálását kísérleti szinten érdemes lehet kipróbálni, de a fejlesztési folyamatba történő integráció körültekintést és megfelelően előkészített környezeteket igényel.
Szerző: Honfi Dávid
A szerző
-
Dávid a BME mérnök informatikus mesterképzésének végzős hallgatója. Immáron három éve foglalkozik
szoftverek automatizált tesztelésével, azon belül pedig tesztgenerálással. Jelenlegi kutatásai során az akadémiai szférából származó eredmények mérnöki gyakorlatba történő átültetését vizsgálja.