Integrációs és ellenőrzési technikák (VIMIA04) Unit tesztelés Honfi Dávid, Micskei Zoltán Méréstechnika és Információs Rendszerek Tanszék Budapesti Műszaki és Gazdaságtudományi Egyetem Méréstechnika és Információs Rendszerek Tanszék 1
Tantárgy tematikája Áttekintés 2
Mi a unit teszt? Tesztelt rendszer (SUT) egy részét vizsgálja Jól körülhatárolt rész: unit, unit under test (UUT) o Osztály o Metódus Unit teszt 4
Miért jók a unit tesztek? Könnyen elkészíthetők Laza csatolásra kényszerít: tesztelhetőség Azonnali visszajelzés Korai hibamegtalálási lehetőség Forrás: Applied Software Measurement, Capers Jones, 1996 5
Hogy néz ki egy unit teszt? (JUnit) public class ListTest{ Tesztosztály List list; // SUT 1. Beállítás @Before public void setup(){ } list = new List(); Inicializáció Teszteset } @Test public void add_emptylist_success(){ list.add(1); 2. Végrehajtás assertequals(1, list.getsize()); } 4. Lebontás? 3. Ellenőrzés 6
DEMO: Festival Meglévő unit tesztek átnézése o Teszt célok? Segít megérteni az egység működését? Elnevezési konvenciók javítása o Metódus neve, teszt szerkezete Üzleti funkciókat ellenőrizzen a teszt o Segédfüggvények, konstansok 7
ALAPELVEK 8
Milyen egy jó unit teszt? A kód egy speciális funkcióját ellenőrzi Szerződés ellenőrzése a modul működéséről Példaként szolgálhat az egység használatához Megbízható (egyszerű, jó minőségű kód) 9
Hogyan lehet jó a unit teszt? 1. Alapelvek betartása 2. Bevált minták követése 3. Smell -ek elkerülése Gerard Meszaros. 2006. xunit Test Patterns: Refactoring Test Code. Prentice Hall PTR, Upper Saddle River, NJ, USA. http://xunit.org 10
Automatizált futtatás követelményei Tesztelhetőségre tervezés Front door priorizálása Ne módosítsuk a UUT-t (helyette: leszármazott) Függetlenítsük a teszteket Izoláljuk a modult 1 ellenőrzött követelmény tesztenként 12
xunit Test Patterns könyv ajánlásai Forrás: Gerard Meszaros, http://xunitpatterns.com/ 13
UNIT TESZTELÉSI MINTÁK Alapelemek 14
I. Végrehajtás Végrehajtó (pl. xunit, JUnit, MSTest) o Tesztek felsorolása, felfedezése, kiválasztása o Tesztek nyomon követése: darabszám, eredmények Tesztkészlet: csoportosításhoz o Tesztesetek összefogása tesztosztályba o Több tesztosztály összefogása Felfedezés: osztály vagy metódus szinten is o Elnevezés alapján o Attribútum segítségével o Könyvtárstruktúrával 16
Egyszerű teszteset II. Tesztdefiníció (Metódus) o Happy path : egyszerű lefutási útvonal, nagy kódfedés o Eredményeket ellenőrzi, nem vár kivételt o Arrange Act Assert törzs Elvárt kivételes teszteset o A teszt futtatása során kivételt várunk o Ha nem keletkezik, a teszt hibát jelez Konstruktor teszteset o Ha egy konstruktor összetett logikát tartalmaz o Egyszerű teszteset, de elvárhat kivételt is (rossz argumentum) o Ellenőrzés: objektum állapota megfelelő-e 17
Elnevezési konvenciók Teszt osztály neve: [Unit_neve]Test Teszt metódus neve: o Method_StateUnderTest_ExpectedBehavior o MethodName_DoesWhat_WhenTheseConditions o [feature being tested] Teszt szerkezete: // Arrange // Act // Assert // Given // When // Then 18
II. Tesztdefiníció (Osztály) Nagyobbrészt tesztmetódusokból épül fel Utility metódus: duplikáció elkerüléséhez Helper: osztály több tesztosztályhoz tartozó utility metódusok számára Tesztosztály Teszteset Teszteset Helper Utility Utility Tesztosztály Teszteset Utility 19
Friss III. Inicializálás o Minden teszteset friss környezetben fut o A környezet futás előtt közvetlenül kerül létrehozásra o Minden teszteset teljesen független egymástól Megosztott o Több eset használhat egy előre definiált környezetet o Futás idő csökkenthető o DE: tesztesetek hatással lehetnek egymásra o Lehetőleg kerülendő megoldás o Használata csak ellenőrzött körülmények között 20
IV. Eredmények ellenőrzése Állapot ellenőrzése o Procedurális: assertionök sorozatával o Objektum: adott állapotú objektummal Viselkedés ellenőrzése o Procedurális: a teszt futása közben elkapva o Elvárt: teszt futása előtt definiált helyettesítővel 21
V. Befejezés Stratégiák o GC Referenciák megszüntetése Eszkalálás o Automatikus Registry használata: pl. kollekció Végén törlés egyesével Szervezés o Inline: minden teszteset végén o Implicit: futtató rendszer hív egy külön metódust 22
VI. Helyettesítők Függőségek Helyettesítő objektumok o Eredeti helyett használjuk: megfigyelés, beavatkozás o Fontos: tesztelhetőségre tervezés 24
VI. Helyettesítők Függőségek Helyettesítő Data access Helyettesítő Import/Export Teszt hívás (GUI) A helyettesítők indirekt bemenetet adnak. 25
Stub VI. Helyettesítők típusai o Előzetesen megadott értékeket ad vissza o Nem tartalmaz ellenőrzést o Responder: valid értékek beadása, happy path o Saboteur: invalid értékek beadása o Átmeneti: üres osztály, beégetett értékekkel Spy o Indirekt kimenetek/hívások megfigyelésére (!) o Tesztesetek végén spy ellenőrzése assertekkel o Pl. anonim függvény átadása SUT-nak, registry 26
Mock objektum VI. Helyettesítők típusai o Előzetesen definiált értékek visszaadása ÉS ellenőrzés o Előzetes elvárások Hívási argumentumok Hívások darabszáma o Hatással lehet a teszteset kimenetelére (assert) Fake objektum o Light-weight implementáció (csak a lényeg marad) o Interakciót képes kezelni (hívási szekvenciák) o Példák: fake adatbázis, fake web service, fake réteg 27
DEMO: Festival Külső függőségek azonosítása Unit tesztelhetővé alakítása Helyettesítők használata a tesztben Instabil tesztfuttatás megszüntetése 28
Stub példa Stub Teszt 29
Mock példa public class PriceServiceTest{ @Before public void init(){ mockda = mock(dataaccess.class); ps = new PriceService(mockDA); } Megfelelő típusú test double kérése @Test public void SuccessfulPriceQuery(){ // Arrange when(mockda.getprodprice("a100")).thenreturn(50); // Act int p = ps.getprice("a100"); Működési szabályok megadása } } // Assert verify(mockda, times(1)).getprodprice("a100") Milyen működést kellett megfigyelnie 30
UNIT TESZTELÉSI MINTÁK Tesztelhetőségre tervezés 31
Tesztelhetőségre tervezés Tesztvezérelt tervezés (Test-Driven Development) o Tesztek készülnek el elsőként (TDD) o A kész tesztek kikényszerítik a tesztelhető kódot Vezérlési pontok (Control point) o UUT vezérlése kívülről (API vagy back door) o UUT adott állapotba állítása Megfigyelési pontok (Observation point) o UUT állapotának lekérése o UUT kommunikációjának megfigyelése 32
Dependency injection UUT Foo(int):bool C osztály A hívó félnek kelljen a függőségek átadását biztosítani! Lehetőségek o Konstruktorban o Paraméterként híváskor o Kettő között: setter Másik ötlet: dependency lookup Hogyan adjuk át a stubot a C-hez a tesztben? 33
Humble objektum A lényeges logikát szeparáljuk a környezettől A szeparált rész könnyen tesztelhetővé válik Lehetőségek o Extract method ( Poor man s ) o Ősosztályok és leszármazottak o Külön osztály a kiszervezett logikával (delegálás) 34
Teszt hook Tesztelés módosítja a tesztelt viselkedést o Lehetőleg kerülendő végső megoldás o Csak szigorú feltételek mellett használható A kód elágazásokat tartalmaz: if(testing) test else prod A tesztelhetetlen részek tesztelhetővé válnak Pl. dátum lekérdezése DateTime stub o Nem mindig lehet injektálni a stub-ot o Teszteléskor DateTime.Now helyett adott inicializálás 35
TESZT SMELLEK 36
I. Homályos (obscure) tesztek Nehéz elsőre megérteni a tesztet. Hatások o Magasabb fenntartási költségek o Több bug maradhat benn a rosszul írt kód miatt Okok o Nincs kellő figyelem a tesztek egyszerűségén o In-line megoldások tesztekben: összetett logikák sora 37
I. Homályos tesztek tipikus esetei Kíváncsi (eager) teszt: túl sokat akar ellenőrizni Mystery Guest: o Nem látszik az akció-hatás összefüggés a tesztben o Pl.: a teszteset metódusán kívül is történik lépés Túláltalánosítás: felesleges környezet felépítése Beégetett tesztadatok Indirekció: a teszt az UUT-t indirekten éri el 38
Példa Homályos teszt 39
II. Feltételes tesztlogika A tesztnek van olyan része, ami nem fut le minden futtatáskor. Hatások o Elbonyolítja a tesztet nehezíti a megértést o Nehezebb karbantartani Okok o Visszatéréstől függő tesztlogika o Kollekciók, összetett objektumok ellenőrzése o Újrafelhasználhatóságra törekvés 40
III. Duplikáció Ugyanaz a tesztkód többször ismétlődik. Hatások o Karbantarthatóság erős romlása Okok o Idő: CTRL+C és CTRL+V sokat gyorsít a tesztek írásán o Képesség: tesztfejlesztő refaktorálási képességei 41
IV. Assertion rulett Nehéz megállapítani, hogy a sok assertion közül melyik okozza a teszt hibáját. Hatások o Problémák azonosítása nehézzé, költségessé válik o Megnöveli a hibajavítások idejét Okok o Tesztesetek számának minimalizálása több ellenőrzés o Hiányzó assertion üzenet 42
Példa Assertion rulett 43
V. Instabil teszt Ugyanaz a teszteset néha hibamentes, néha hibát jelez. Okok o Nemdeterminizmus a tesztelt kódban o Erőforrásszivárgás o Erőforrásoptimalizálás o Interakciót végző teszt (pl. környezet) 44
VI. Törékeny teszt Adott teszteset nem fordul, vagy hibásan fut egy olyan változtatás után, amit a teszt nem érint. Okok o Indirekt tesztelés o Kíváncsi teszt o Interfész-érzékenység o Viselkedés-érzékenység o Adatérzékenység o Kontextus-érzékenység 45
VII. Lassúság Tesztfuttatás túl sok időt vesz igénybe. Okok o Lassú a tesztelt komponens (pl. nem izolált) o Általános környezet: túl sok inicializálás az elején o Aszinkronitás o Túl sok teszt 46
Összefoglalás Unit teszt: fontos a kódminőség/költségek miatt Unit tesztek minősége nem triviális Alapelvek o Tesztelhetőség o Szervezés o Automatizálás Bevált minták: tervezés, definíciók, helyettesítők Smellek elkerülése 47