Szoftvertesztelés
Tesztelési folyamat • Tesztelési folyamat: különálló programegységek (függvények, objektumok) tesztelése → alrendszerek és rendszerek tesztelése (az integrálási fázis után) → az egységek közötti interakciók tesztelése → elfogadási tesztsorozat (a vásárló által végrehajtva) • Komponens tesztelés → integrációs tesztelés • Komponensek tesztelés: • a tisztán azonosítható komponensek funkcionalitásainak tesztelése • Általában a programozók (szerzők) felelősek érte, de kritikus rendszereknél külön csapat végzi
• Integrációs tesztelés: • komponensek közötti interakciók, és a teljes rendszerre vonatkozó funkcionális/teljesítménybeli elvárások tesztelése → előhozhat komponenseken belüli hibákat, mely az előző folyamat megismétlését eredményezi • Különálló csapat végzi részletes, írott rendszerspecifikációk alapján
Tesztelési folyamat • Funkció-orientált ↔ objektum-orientált tesztelés • Funkció-orientált rendszerek tesztelése: • Tisztán megkülönböztethetőek az alapvető programegységek (függvények) és programegység-kollekciók (modulok) • Alkalmasak a tradicionális integrálási stratégiák (fentről lefele, lentről felfele)
• Objektum-orientált rendszerek tesztelése • Nincsen az előzőhöz hasonló különbség az entitások között: az objektumok lehetnek egyszerűek (pl. egy lista), vagy komplexek (egy teljes irányítórendszernek megfelelő objektum) • Gyakran nincsen tiszta objektumhierarchia → a hagyományos integrálási stratégiák többnyire alkalmatlanok • Nincsen éles határ a komponens tesztelés és integrációs tesztelés között
Hiányosságok tesztelése • Cél: rejtett hibák feltárása a szoftver átadása előtt • Hiányosságok tesztelése ↔ validációs tesztelés • Validációs tesztelés: igazolja, hogy a rendszer megfelel a specifikációjának, azt várja el a rendszertől, hogy az adott tesztesetekre helyesen működjön • Hiányosságok tesztelése: a rendszer helytelen működését okozza, hibákat tár fel (nem azok hiányát, hanem jelenlétét mutatja meg) • A hiányosságtesztelés folyamatának általános modellje:
Test cases
Design test cases
Test results
Test data
Prepare test data
Run program with test data
Test reports
Compare results to test cases
Tesztesetek • a teszthez szükséges bemenetek (inputok) és a rendszertől várt kimenetek (outputok) specifikációja + ismertető, hogy mit tesztelünk. • az adatok generálása lehet automatikus, de a teszteseté nem, mivel a teszt outputot nem lehet előre megjósolni • a kimerítő tesztelés (az összes lehetséges program-végrehajtási szekvencia tesztelése) gyakorlatilag lehetetlen → a teszteknek csak a lehetséges tesztesetek egy részhalmazán kell alapulniuk • A teszteset-részhalmazok kiválasztására alkalmazott irányelvek meghatározása a szervezet feladata, nem a fejlesztőcsoporté • Az irányelvek meghatározása alapulhat tesztelési irányelveken (pl. minden utasítás legalább egyszer legyen végrehajtva), rendszerüzemeltetési tapasztalatokon. • A tesztek összpontosíthatnak a működő rendszer sajátosságaira (a menükön keresztül elérhető összes funkció legyen letesztelve, ahol felhasználói inputot várunk az összes függvény legyen letesztelve helyes és helytelen bemenetekkel egyaránt) • Eredmény: a funkciók szokatlan kombinációja eredményezhet hibát, de a legtöbbször használt kombinációk többnyire helyesen működnek
Fekete doboz tesztelés • Funkcionális vagy fekete doboz tesztelés (black-box testing): a tesztek a program- vagy komponensspecifikációkból származnak • A rendszer „fekete doboz”, melynek viselkedésmódja csak a bemeneteinek és az ezzel összefüggő kimeneteinek tanulmányozásával határozható meg (csak a funkcionalitásokkal foglalkozunk, nem az implementációval) • Fekete doboz tesztelésre átadott rendszer modellje: Input test data
I
Inputs causing anomalous behaviour
e
System
Output test results
Oe
Outputs which reveal the presence of defects
- a megközelítés egyaránt alkalmazható függvény- és objektumalapú rendszerekre - a tesztek kiválasztása általában előző tapasztalatokon alapszik (a várhatóan hibát eredményező esetek kiválasztása szakterületi ismereteket igényel), de ez a heurisztikus eljárás szisztematikus kiválasztási módszerekkel együtt is alkalmazható
Ekvivalenciaosztályozás • A bemeneti adatok különböző osztályokba esnek, melyek rendelkeznek közös jellegzetességgel (pozitív számok, negatív számok, szóközmentes sztringek stb.) • A programok általában egy osztály minden tagjára hasonló módon viselkednek (innen jön az ekvivalenciaosztályok elnevezés) • A hiányosságtesztelés szisztematikus megközelítése ezen osztályok beazonosításán alapszik: a teszteseteket úgy tervezik,hogy a bemenetek/kimenetek ezekbe az osztályokba essenek - Bemenei ekvivalenciaosztályok: minden halmazelemet hasonló módon kell feldolgozni Invalid inputs
System
Valid inputs
- Kimeneti ekvivalenciaosztályok: valamilyen közös jellegzetességgel rendelkeznek -Meghatározhatóak olyan osztályok, ahol az inputok kívül esnek a többi választott osztályon - Az érvényes és érvénytelen inputok szintén osztályokat alkotnak
Outputs
Ekvivalenciaosztályozás • először meghatározzuk az osztályok halmazát, majd minden osztályból teszteseteket választunk • Az osztályok a specifikáció, a felhasználói dokumentáció és a tesztelői tapasztalatok (az értékosztályok közül melyek jöhetnek leginkább számításba a hibák felderítésében) alapján azonosíthatóak • Az osztály határairól és közepéről is választunk teszteseteket (a programozók hajlandóak csak a középrészt, tipikus inputokat figyelembe venni, a nem tipikus határértékek elkerülhetik figyelmüket: pl. a 0 más pozitív számoktól eltérően viselkedhet) • Példa: kereső eljárás, mely elemsorozatban keres egy kulcsot, és az adott elem sorozatbeli pozícióját adja vissza. A program 4 és 10 közötti 10000-nél nem nagyobb ötjegyű számot vár bemenetként
Ekvivalenciaosztályozás procedure Search (Key : ELEM ; T: ELEM_ARRAY; Found : in out BOOLEAN; L: in out ELEM_INDEX) ; Pre-condition -- the array has at least one element T’FIRST <= T’LAST Post-condition -- the element is found and is referenced by L ( Found and T (L) = Key) or -- the element is not in the array ( not Found and not (exists i, T’FIRST >= i <= T’LAST, T (i) = Key )) 3
11 4
7
10
Ekvivalenciaosztályok: Less than 4
Between 4 and 10
More than 10
Number of input values 9999 10000
Less than 10000 Input values
50000
100000 99999
Between 10000 and 99999
More than 99999
-inputok, ahol a kulcselem a sorozat tagja (Found=true) -inputok, ahol a kulcselem nem a sorozat tagja (Found=false)
Ekvivalenciaosztályozás • A specifikációból következtetett tesztelési irányelvek: • Tesztelés egy értékkel rendelkező sorozatokkal • Különböző méretű sorozatok a különböző teszteknél • Olyan tesztek, ahol a sorozat első, középső és utolsó elemét kell kiválasztani (az osztályhatárokon lévő esetleges problémák feltárása)
• Az irányvonalakon alapuló további ekvivalenciaosztályok: • Az inputsorozatoknak csak egy eleme van • Az inputsorozatok elemszáma nagyobb, mint 1
• Ekvivalenciaosztályok és lehetséges tesztesetek: Array Single value Single value More than 1 value More than 1 value More than 1 value More than 1 value
Input sequence (T) 17 17 17, 29, 21, 23 41, 18, 9, 31, 30, 16, 45 17, 18, 21, 23, 29, 41, 38 21, 23, 29, 33, 38
Element In sequence Not in sequence First element in sequence Last element in sequence Middle element in sequence Not in sequence
Key (Key) 17 0 17 45 23 25
Output (Found, L) true, 1 false, ?? true, 1 true, 7 true, 4 false, ??
- ha a kulcselem nincs a sorozatban, az L értéke definiálatlan (??) - az inputértékek halmaza nem teljes körű → még létezhetnek hibák - lehetségesek hibák/hiányosságok az osztályok beazonosításánál is - fekete doboz: nincsenek tárgyalva a rossz sorrenddel/ típussal megadott paraméterek, adatsérülések stb. (az átvizsgálás/ automatizált statikus elemzés feladatai)
Struktúrateszt/fehér doboz tesztelés • Struktúrateszt, fehér doboz (white box testing), üvegdoboz, vagy tiszta doboz tesztelés: a teszteseteket a szoftver struktúrájának és implementációjának ismeretében választjuk • Relatíve kicsi programegységekre (alprogramok, objektumok) alkalmazzák • Kód elemzése: mennyi tesztesetre lesz szükségünk, ahhoz, hogy a a komponens minden utasítása legalább egyszer végre legyen hajtva • Az algoritmusról szerzett tudás alkalmazása: függvények implementálása, melyek segítségével további ekvivalenciosztályok azonosíthatóak Test data
Tests
Derives Component code
Test outputs
Struktúrateszt - példa • Bináris keresési algoritmus • A sorozat egy tömbként van implementálva, melyet rendezni kell, és az alsó határnál lévő értéknek kisebbnek kell lennie a felső határnál lévőnek • A bináris keresés a keresési teret három részre osztja, melyek mindegyike meghatároz egy-egy ekvivalenciaosztályt • Olyan teszteseteket kell választani, melyek esetében a kulcs az osztály határán helyezkedik el • A tesztesetek esetében a bemeneti tömb elemei növekvő sorrendbe kell legyenek rendezve • Az algoritmus ismerete alapján olyan teszteseteket kell meghatározni, melyek elemei szomszédosak a tömb középső elemével Equivalence class boundaries
Elements < Mid
Elements > Mid
Mid-point
Struktúrateszt példa class BinSearch { // This is an encapsulation of a binary search function that takes an array of // ordered objects and a key and returns an object with 2 attributes namely // index - the value of the array index // found - a boolean indicating whether or not the key is in the array // An object is returned because it is not possible in J ava to pass basic types by // reference to a function and so return two values // the key is -1 if the element is not found public static void search ( int key, int [] elemArray, Result r ) { int bottom = 0 ; int top = elemArray.length - 1 ; int mid ; r.found = false ; r.index = -1 ; while ( bottom <= top ) { mid = (top + bottom) / 2 ; if (elemArray [mid] == key) { r.index = mid ; r.found = true ; return ; } // if part else { if (elemArray [mid] < key) bottom = mid + 1 ; else top = mid - 1 ; } } //while loop } // search } //BinSearch
Útvonal-tesztelés • Strukturális tesztelési stratégia, melynek célja minden független végrehajtási útvonal kipróbálása egy komponens/program esetében → a program minden utasítása legalább egyszer végrehajtódik • Objektumorientált fejlesztés esetében: módszerek objektumokkal való kapcsolatának tesztelése • Az utak száma arányos a program méretével • Az útvonal-tesztelést a modultesztelési fázisban alkalmazzuk • Nem elemzi az útvonalak összes lehetséges kombinációját (ciklusokkal rendelkező programoknál végtelen útvonal-kombináció létezik) • A kezdőpont egy programfolyamat gráf (a programon keresztül vezető utak vázmodellje): csomópontokból áll, melyek döntéseket reprezentálnak, az élek a vezérlés irányát mutatják • A gráfot a vezérlőutasítások megfelelő diagramba történő átírásával állítjuk elő (egyszerű, ha nincsen goto)
Útvonal-tesztelés • A szekvenciális utasítások (értékadások, eljáráshívások, I/O utasítások) kihagyhatóak, a feltételes utasítások mindenik ága különálló út, a ciklusokat a ciklusfeltételt reprezentáló csomópontba visszamutató nyíllal jelöljük • Független útvonal: út, amely a folyamatgráfban legalább egy csomóponton keresztülmegy → egy vagy több új feltétel kezelése • A feltételeknek mind a hamis, mind az igaz ágát le kell futtatni • A független utak száma meghatározható a folyamatgráf ciklomatikus komplexitásának (McCabe, 1976) kiszámításával: • CC[G] = élek száma – csomópontok száma + 2 • Ahol nincs goto, eggyel több, mint a feltételek száma • Összetett feltételek esetén minden tesztet számolni kell
• Dinamikus programelemzők: komplex rendszerek esetében alkalmazzuk, a fordítókkal működnek együtt, számolják, hogy egy utasítás hányszor kerül futtatásra, majd a futtatás után futási profilt állítanak elő
Útvonal-tesztelés példa • A bináris keresés rutinjának folyamatgráfja: 1
bottom > top
while bottom <= top 2
Független útvonalak: 3
if (elemArray [mid] == key
4
8 5
(if (elemArray [mid]< key 6
9 7
1, 2, 3, 8, 9 1, 2, 3, 4, 6, 7, 2 1, 2, 3, 4, 5, 7, 2 1, 2, 3, 4, 6, 7, 2, 8, 9
Integrációs tesztelés • A programkomponenseket részleges vagy teljes rendszerré kell integrálni • Az integrációs teszteket a rendszerspecifikációkból kell kifejleszteni • A fő nehézség a folyamat során feltárt hibák lokalizálása • Az inkrementális integrációs tesztelés megkönnyíti a hibák lokalizálását: először minimális rendszerkonfigurációt kell integrálni és tesztelni, majd ezt követően adhatjuk hozzá a komponenseket, majd tesztelni kell minden egyes inkrementum hozzáadása után • Fentről lefele tesztelés: a rendszer magas szintű komponenseit a tervezés és az implementáció befejezése előtt integráljuk és teszteljük • Lentről felfele tesztelés: először az alacsony szintű komponenseket integráljuk és teszteljük • A gyakorlatban általában ezek kombinációját alkalmazzák
Inkrementális integrációs teszt
Integrációs tesztelés fentrıl lefelé
Integrációs tesztelés lentrıl felfelé
Fentrıl lefele ↔ lentrıl felfele • Szerkezeti validáció: • a fentről lefele tesztelés korábban észleli a hibákat a rendszerarchitektúrákban. Ezek általában strukturális hibák, így a javításuk is kevesebb költséggel jár
• Rendszerdemonstráció: • A fentről lefele történő fejlesztésnél a korai szakaszban csak korlátozott számú rendszer működik, így a validáció a tesztelési folyamat elején elkezdődhet
• Tesztimplementáció: • A fentről lefele történő tesztelést nehéz implementálni, mert a rendszer alacsonyabb szintjeit szimuláló programcsonkokat kell előállítani • A lentről felfele történő tesztelésnél tesztmeghajtókat kell írnunk az alacsony szintű komponensek kipróbálásához. Ezek a tesztmeghajtók szimulálják a komponensek környezetét
• Tesztmegfigyelés • Mindkét tesztelésnek problémái vannak a tesztmegfigyeléssel • Szükség van egy mesterséges környezetre, a teszteredmények generálásához és a komponensek futtatásának megfigyeléséhez
Interfésztesztelés •
A nagyobb rendszer létrehozásához modulokat vagy alrendszereket integrálunk → interfésztesztelés: minden modul/alrendszer meghatározott interfésszel rendelkezik, ezen keresztül elérhető a többi komponens számára
•
Az interfésztesztelés célja az interfészhibák vagy interfész-félreértelmezések detektálása
•
Az interfésztesztelés esetén a teszteseteket nem az egyes komponensekre, hanem a komponensek kombinációjával előállított alrendszerekre alkalmazzuk
•
Különösen fontos az objektumorientált rendszerek tesztelésénél: az objektumok alapvetően az interfészeik által definiáltak, és más objektumokkal kombinálva különböző alrendszereknél újrafelhasználhatóak. Az interfészhibákat nem lehet érzékelni a különálló objektumok tesztelésénél, mivel ezek az objektumok közötti interakciók eredményei, nem egyedi objektumok elszigetelt viselkedései
Test cases
A
B
C
Interfésztesztelés • A programkomponensek között különböző típusú interfészek léteznek → különböző típusú interfészhibák fordulhatnak elő: • Paraméter interfészek: adatok vagy függvényreferenciák továbbítódnak egyik komponenstől a másikhoz • Osztott memóriájú interfészek: egy memóriablokk van megosztva az alrendszerek között: az adat a memóriába kerül az egyik alrendszertől, és innen egy másik alrendszer kiolvassa • Procedurális interfészek: az egyik alrendszer más alrendszerek által hívható eljárások egy halmazát foglalja magába (objektumok és absztrakt adattípusok rendelkeznek ilyen interfészekkel) • Üzenettovábbító interfészek: egy alrendszer valamilyen szolgáltatást kér egy másik alrendszertől, úgy, hogy üzenetet továbbít hozzá, a szolgáltatás lefuttatásával kapott eredményeket pedig egy válaszüzenet tartalmazza (objektumorientált és kliens-szerver rendszerek alkalmaznak ilyen interfészeket
Interfésztesztelés •
Az interfészhibákat a következő három osztályba sorolhatjuk: • Interfész téves alkalmazása: egy hívó komponens meghív más komponenseket és hibát követ el interfészeik alkalmazásában (különösen gyakori paraméter interfészeknél: a paraméterek nem megfelelő típusúak, rossz sorrendűek lehetnek, vagy nem megfelelő az átadott paramétereknek a száma) • Interfész félreértelmezése: egy hívó komponens félreértelmezi a hívott komponens interfészspecifikációját, és téves következtetést von le a hívott komponens viselkedésmódjáról. A hívott komponens nem a várakozásoknak megfelelően viselkedik, így nem várt viselkedést okoz a hívó komponensnél (pl. a bináris keresési eljárást egy nem rendezett tömbre hívjuk meg) • Időzítési hibák: valós idejű, osztott memóriájú vagy üzenettovábbító interfészt használó rendszereknél fordulhatnak elő. Az adat előállítója és feldolgozója eltérő sebességgel üzemelhet, és előfordulhat, hogy a feldolgozó idejétmúlt információhoz fér hozza (az előállító még nem frissítette az osztott interfész információit)
Interfésztesztelés • Az interfésztesztelés alapelvei: • Vizsgálni kell a tesztelendő kódot és minden külső komponenshívást egyértelműen rögzíteni kell. A tesztek egy részét úgy kell tervezni, hogy a külső komponensek paraméterei az értéktartományok határaira essenek, ezek a szélsőséges értékek nagyobb valószínűséggel fedhetnek fel interfészinkonzisztenciákat • Mutatók átadásánál tesztelni kell az interfészt null értékű paraméterekkel • Procedurális interfészen keresztüli hívásoknál szükségesek olyan tesztek, melyek esetlegesen hibákat okozhatnak • Üzenettovábbító rendszereknél stressztesztelés javasolt • Osztott memória használatakor szükséges a komponensek aktiválódási sorrendjének változtatása – az ilyen tesztek felfedhetnek a sorrendre vonatkozó implicit feltételezéseket
• Megjegyzés: a statikus technikák gyakran költséghatékonyabbak, mint az interfésztesztelés, egy erősen típusos nyelv (pl. Java) lehetővé teszi több interfészhiba fordító általi felderítését, más esetekben elemzőt használhatunk (pl. C – LINT).
Interfésztesztelés • Nehézségek: • Néhány hiba csak szokatlan feltételek esetén jelentkezik. Pl.: egy objektum fix hosszúságú adatstruktúraként implementál egy sort. A hívó objektum feltételezi, hogy a sor végtelen adatstruktúraként volt implementálva, és nem ellenőrzi a lehetséges túlcsordulást egy új elem beillesztésénél → a hiba csak a túlcsordulást kierőltető tesztesetek készítésével detektálható. • További nehézséget okozhat a különböző objektumokban/modulokban található hibák egymásra hatása. Lehetséges, hogy egy objektumban egy hiba csak akkor detektálható, ha néhány másik objektum nem várt módon viselkedik. Pl.: egy objektum egy másik objektumtól egy bizonyos szolgáltatást igényel, és feltételezi, hogy a válasz helyes. A visszaadott érték lehet érvényes, de helytelen. A hibára csak akkor derül fény, ha a későbbi számítások során valamilyen hiba történik.
Stressztesztelés • Miután a teljes integrációs folyamat végbement, lehetséges a rendszer független tulajdonságainak tesztelése: pl. teljesítmény, megbízhatóság. • A teljesítménytesztek biztosítják, hogy a tervezett terhelés mellett a rendszer képes dolgozni • A teljesítmény tesztelése tesztek olyan sorozatát foglalja magába, ahol a terhelés mindaddig állandóan nő, amíg a rendszer teljesítménye elfogadhatatlanná nem válik • A rendszerek néhány osztályát úgy tervezték, hogy egy megadott teljesítményt kezeljenek. Pl.: tranzakció feldolgozó rendszer, mely percenként 100 tranzakciót tud feldolgozni, operációs rendszer, amely 200 különböző terminált képes kezelni stb. • A stressztesztelés a tervezett maximális terhelésen túl is folytatódik, mindaddig, amíg a rendszer hibázik • Különösen lényeges osztott rendszereknél, melyek terheltség esetén nagyméretű teljesítményvesztést mutathatnak (a hálózat telítődik koordinációs adatokkal)
Stressztesztelés • A stressztesztelés alapvető funkciói: • Teszteli a rendszer viselkedését szélsőséges körülmények között. Események nem várt kombinációja olyan körülményeket okozhat, ahol a rendszerre nehezedő terhelés meghaladja a maximálisan előrelátható terhelést, és fontos, hogy ilyen körülmények között a túlterhelés ne okozzon adatvesztést vagy felhasználói szolgáltatások eltűnését • A rendszer terhelése fényt deríthet olyan hiányosságokra, melyek normális körülmények között nem jelennek meg. Ezek a hiányosságok normál használat során nem okoznak rendszerhibát, de felléphet normál körülmények olyan váratlan kombinációja, amit a stressztesztelés előidéz