#0308 – Render vs. regressziós teszt – Megoldva!
Na kicsit technikaibb és kicsit hosszabb cikk következik, most hogy a munkahelyi őrület is alábbhagyott és a videokártya-probléma is megoldódott.
A téma nálam nagyon nem új, mondhatni végigkisért(ett)e az elmúlt 5 évet nálam. A dolog nem más, mint a regressziós teszt – vagyis olyan teszt, amikor van egy rakat teszteseted, amit lefuttatsz egy szoftver régi verziójával, majd az újjal, s a teszteredmények különbségéből meg tudod látni, hogy mondjuk egy új feature kifejlesztése behozott-e új bugot. Az ideális eset valami a következőhöz hasonló:
Van egy szoftvered, mondjuk egy engine, amiből a V1 verziót fejleszted, s közben teszteseteket (egy engine esetében pl. mini teszt-jeleneteket) irkálsz hozzá. Mikorra elkészülsz az első verzióval, lesz mondjuk X darab teszteseted, amik mind szépen futnak, stb. Ez után jön a pont, mikor összeszedsz egy feature-listát, amire azt mondod, hogy az előző verzióhoz hozzáadva ezeket megkapod a következő verziót. Miközben fejleszted a második verziót, természetesen megint irkálsz teszteseteket, mondjuk Y darabot. Ez annyit jelent, hogy a V2 verzió kifejlesztése végére van X + Y darab teszteseted, amiből az első X darab feltételezed, hogy jó, hiszen jók voltak (és jó alatt itt azt értem, hogy a futtatásuk során az elvárt eredményt látod), mikor a V1 verziót lezártad. Igen ám, de mi van, ha a V2 fejlesztése közben becsúszott egy bug, s valami, ami működött korábban, most már nem működik? Hogy lehet ezt megkerülni? Természetesen úgy, hogy minden egyes, a V2 verziót érintő változtatás után szépen újrafuttatsz minden tesztesetet. Ez biztos megoldás, amíg mondjuk egy félmaréknyi tesztesetről van szó. De mi a helyzet ha mondjuk van 40-50-100-akármennyi? Baj. Megőszülsz, mire mindezt manuálisan végigfuttatod minden egyes alkalommal. A megoldás kézenfekvő: automatizálni kell a dolgot.
És itt jön a dolog vicces része: egy render engine esetében miről szól egy test case? Röviden a következőkről:
- Bemenet: content, avagy textúrák, modellek, shaderek, etc
- Kimenet: renderelt állókép / animáció
- A döntés (avagy a teszfutás “jósága”): azt látjuk-e, amit látni szeretnénk?
- A döntés eszköze: az emberi látás / képérzékelés
- A döntés alapja (vagyis az “etalon”): első futtatáskor: amit elképzeltünk (expected result). Minden további futtatáskor: az utolsó jó eredmény (reference result).
Na, akkor itt meg is állnék egy pillanatra. Hogyan zajlik ez a döntés, algoritmikusan gondolkodva? Röviden valahogy úgy, ahogy itt az alábbi folyamatábrán látszik:
Na most, a képen két dolgot jelöltem meg más színnel – igen, azt a két dolgot, amit muszáj vagyunk emberi beavatkozással megcsinálni. A dolog szépsége, hogy ebből a kettőből is az egyik csupán, ami a tesztelés folyamatának szerves része. A konklúzió: a tesztelést mindenképpen automatizálni kell. Az egyetlen tisztázásra váró dolog a “nem-manuális” összehasonlítás, avagy a referenciával történő összehasonlítás. Mit is jelent ez? Az, hogy az engine kimenetét, ami egy kép vagy egy animáció, összehasonlítjuk egy már eltárolt – referencia kimenettel. Jobban belegondolva ez egyszerű is meg nem is. Egyszerű, hiszen az engine kimenetéről csinálunk egy screenshotot, azt eltároljuk és később ezzel hasonlítunk. Az összehasonlítás maga nem nagy művészet, több tucat algoritmus létezik, melyel jóval igényesebbek két kép pixelenkénti összehasonlításánál. (További olvasnivaló, etc. itt!)
A dolog még animáció esetén sem túlságosan bonyolult: fogunk egy adott időtartamot, aminek az elteltével csinálunk egy screenshotot, majd ha megint eltelt ez az időtartam, akkor még egyet, etc. Az, hogy ez az időtartam mennyi, mi szabjuk meg – ez gyakorlatilag egy mezei kvantálás. Egy mondjuk egy perces futás alatt veszünk 60 mintát, majd azt a 60at hasonlítjuk az eltárolt 60hoz… Easy business. Ez persze a gyakorlatban egy kis trükközést igényel, hiszen utána ebből valami kézzelfogható eltérést (mondjuk ideális esetben egy százalékos mennyiséget) kellene barkácsolnunk, hogy eldönthessük, jól futott-e le a teszteset.
Na most, amit itt elmondtam, szinte mind leprogramozható – egy aprócska feltételezéssel: az engine-ünk lehetővé tesz ilyen szintű automatizálást. Nos, alapállapotban a klasszikus engine + content + application = product felállást követve ilyesminek kellene lennie a szituációnak:
Itt ugye arról van szó, hogy hogyan épülnek egymásra a dolgok – az ábra nem pont UML-kompatibilis, de a céknak megfelel
A dolog tehát szintén egyszerű: az alkalmazást “leszedjük” az engine tetejéről és helyette egy tesztframeworkot rakunk rá. Programozói szemszögből: az engine .DLL-jét nem a Game.exe-ből, hanem az AutomatedTestFramework.exe-ből hívjuk fel
Ha ilyen fajta szétválasztást az alkalmazás és az engine között nem tudunk könnyűszerrel megejteni, akkor van egy rossz hírem: az engine-ünk a klasszikus értelemben véve nem engine – valamit rosszul csinálunk!
Mit kell tudnia egy ilyen tesztframeworknek?
- Legyen teljesen automatikus – ez muszáj ahhoz, hogy ne kelljen a gép előtt ülni amíg a tesztesetek lefutnak
- Tároljon el, amit tud – logolja az egyes tesztesetek futását, tárolja el az eredményeket – bármilyen felülvizsgálatnál jól jöhetnek
- Csináljon statisztikákat, méréseket a futás alatt – egy regresszió igazán akkor jó, ha az egymást követő futások ugyanolyan környezetben futnak le, hiszen ez is egy viszonyításai alap. Hiába adja az engine ugyanazt a vizuális kimenetet, ha épp fele annyi FPS-el teszi, mint a korábbi verzióban – de ugyanazon a rendszeren.
- Legyen hibatűrő – ha egy teszteset “beragad” és vele együtt a tesztframework is, akkor hosszú-hosszú ideig fogunk szenvedni a regresszióval…..
- Az egyes tesztesetek futása ne befolyásolja egymást.
Hát ennyit hirtelen… Gondolkodtam, hogy ezt megírom tutorialnak, de inkább csak egy kis elmélkedés, így marad blog entry
Remélem azért valakinek hasznosnak bizonyul – nekem mindenesetre támadt 1-2 jó ötletem!