7. Tesztelés

Az első fejezetben tárgyalt számítógépes problémamegoldás következő lépése a tesztelési stratégia fejlesztés. Az előbbiekben létrehoztunk egy-egy részletes megoldást az eredeti probléma részfeladataira, ezek alapján előállt egy algoritmus (például pszeudokód segítségével formalizálva). Azt gondolhatjuk, hogy mivel ezt jól megterveztük, a munka nagy részével kész is vagyunk. Azonban mind a tervezés során, mind az algoritmus megalkotása során véthettünk hibákat, pár speciális helyzet elkerülhette a figyelmünket. Szükségünk van arra, hogy megvizsgáljuk, vajon tényleg megfelelően működik az algoritmusunk. A szerteágazó teszteléshez szükségünk lesz egy tesztelési stratégiára. Ezt nem csak magának az algoritmusnak a teszteléséhez használhatjuk fel, hanem a fejlesztés későbbi lépéseiben is. Ne feledjük, azért van szükségünk egy algoritmusra, mert ez alapján írni akarunk egy számítógépes programot. Egy program a számos technikai részlet miatt nem minden esetben működik ugyanúgy, mint az elképzelt algoritmus. Másrészt a program megvalósítása, azaz az implementáció során is ejthetünk hibákat. Ezért a tesztelési stratégiánkkal nem csak az algoritmus helyességét tudjuk ellenőrizni, hanem később az elkészült programét is.

Meg kell jegyeznünk, hogy a szoftvertesztelés manapság egy külön önálló szakma. Ennek összes lehetőségét és trükkjét nem áll módunkban áttekinteni ennek a jegyzetnek a keretében. Ennek ellenére a legfontosabb szempontokat minden programozónak ismernie kell.

A tesztelés megtervezésekor első körben az algoritmus alapvető tulajdonságait kell szem előtt tartanunk. Minden esetben véget ér az algoritmus? Nem fordulhat elő valahol végtelen ciklus, vagy végtelen rekurzió? Minden eshetőségre felkészültünk? Nincs véletlenül valamelyik elágazásnak kidolgozatlan ága? Minden utasításból elérhető valamelyik végállapot? Bolondbiztos a megoldásunk? Nincs esetleg olyan input kombináció, amely nem megfelelő működést eredményez? Ugyanarra a bemenetre mindig ugyanazt a kimenetet kapjuk? Ezeken túl természetesen azt is ellenőriznünk kell, hogy helyes-e a megoldás? Minden esetben az elvárnak megfelelő eredményt kapjuk? Értelmezhető egyáltalán a megoldás? A megoldás pontosság megfelelő? Csak egy vagy több helyes megoldás is létezik? Teljes a megoldásunk? Kielégítettük mind a formai, mind a tartalmi követelményeket? A végrehajtás vagy az implementáció során minden egyes utasítás értelmezhető? Zavarhatja-e a megvalósítás során fellépő technikai körülmények közül bármi a sikerességet?

Ezen kérdések mindegyikére megnyugtató választ kell találnunk. Ha bármely esetben problémába ütközünk, akkor ki kell derítenünk annak forrását. Lehet, hogy a feladat megértésével volt a baj. A fejlesztő és a felhasználó egyeztetései az igényekről és a lehetőségekről is pontatlanok lehettek. Előfordulhat, hogy az eredeti probléma részekre bontásánál vagy a részek megoldásainak egyesítésénél követtünk el hibát. Tévedhettünk az algoritmus elvi működésével vagy lejegyzésével kapcsolatban is. Talán a tervezés során nem vettük figyelembe az alkalmazni kívánt technológia korlátait. Vagy csak egyszerűen a tökéletes terv megvalósítása során nem voltunk teljesen precízek. Bármi is legyen az ok, ha a tesztelés során bármikor elbukunk, akkor legtöbbször vissza kell menni a szoftver életciklus elő lépéseihez és előröl kezdeni a fejlesztést.

A speciális esetek kezelése általában jóval nagyobb erőfeszítéseket követel a programozótól, mint az általános esetek többnyire megfelelő megoldásainak megtalálása. Vannak persze típushibák, amelyekre a gyakorlott fejlesztő mindig számít, ennek ellenére minden probléma más, mindig találkozhatunk váratlan különleges esetekkel.

A tesztelés legtöbb esetben nem lehet teljes. Nem tudunk minden létező esetet kipróbálni, ellenőrizni. A tesztelési stratégia fejlesztése során azonban törekednünk kell arra, hogy az esetlegesen felmerülhető problémák minél szeresebb köre ellenőrizve legyen. Úgynevezett teszteset-eket kell létrehoznunk. Ezek egy bizonyos körülmény, egy bizonyos bemeneti érték kombinációt tartalmaznak. Ezek alkalmazása esetén egyértelműen ki kell derülnie, hogy a megoldásunk (ebből a szempontból) helyes-e vagy sem. Az ilyen tesztestekből többre is szükségünk van, ezeket együtt tesztkészlet-nek nevezzük. Ennek komponensei különböző problémaköröket kell, hogy lefedjenek.

Elemezzünk egyetlen konkrét esetet! Tegyük fel, hogy egy másodfokú egyenlet szeretnénk megoldani. Az egyenlet mindig Ax^2 + Bx + C = 0 alakra van hozva, azaz bemenetként a három együtthatót (A, B, C) kapjuk meg. Meg kell mondanunk milyen érték esetén áll fenn az egyenlőség. Írjuk meg az algoritmust pszeudokódban! Kérjük be az adatokat! A másodfokú egyenlet megoldó képletét használva, először határozzuk meg a diszkrimináns értékét, majd az egyenlet gyökeit! A négyzetgyök kiszámolásához tételezzük fel, hogy használhatjuk az y=sqrt(x) függvényt anélkül, most meg kellene írnunk.

1
2
3
4
5
6
   output "Add meg a másodfokú egyenlet együtthatóit!"
   input A, B, C
   D = B*B-4*A*C
   X1 = (-B+sqrt(D))/(2*A)
   X2 = (-B-sqrt(D))/(2*A)
   output "A megoldások: ", X1, X2

Nem is volt olyan nehéz. Mindössze 6 sor. Választanunk kell egy tesztesetet, amivel eldönthetjük, hogy ebben az adott esetben ez eredmény helyes-e. Az A=3, B=7 és C=2 kombináció esetén matematikailag az egyik gyöknek -1/3-nak kell lennie a másiknak pedig -2-nek. Ez teljesül is.

A teszteléshez próbáljunk meg kitalálni olyan teszteseteket is, amelyek esetén az algoritmus elbukhat! Matematikában jártas személynek azonnal feltűnik, hogy az algoritmusban van egy osztás, és mint tudjuk, a nullával való osztás problémát jelenthet. Itt a nevező 2*A, ami akkor lehet nulla, ha A értéke is nulla függetlenül B és C értékétől. Válaszunk tehát egy olyan tesztesetet a tesztkészletbe, ahol másodfokú tag együtthatója nulla! Egy lehetséges teszteset az A=0, B=3 és C==9 bemeneti érték kombináció. Erre az algoritmus nem megfelelően működik. Át kell gondolnunk az algoritmust és aztán ennek megfelelően módosítanunk is kell, ami lehet egy kiterjesztés az A=0 esetre. Mit jelent matematikailag ez az eset? Ha az A együttható értéke 0, akkor már nem is beszélhetünk másodfokú egyenletről, hiszen egy Bx+C=0 alakú elsőfokú egyenletünk van. Mondhatjuk azt, hogy ennek megoldása nem része a feladatnak. Igazunk van, azonban az algoritmust érdemes bolondbiztossá is tenni. Ha egy alulképzett felhasználó elsőfokú egyenletet ad meg az algoritmusnak arra is tudnia kell reagálni. Kiterjesztéssel és bolondbiztossá tétellel módosítsuk hát az algoritmust! Matematikai tanulmányaink alapján tudjuk, hogy egy ilyen elsőfokú egyenlet megoldása az x=-C/B kifejezéssel kapható meg.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
   output "Add meg a másodfokú egyenlet együtthatóit!"
   input A, B, C
   if A!=0 then
     D = B*B-4*A*C
     X1 = (-B+sqrt(D))/(2*A)
     X2 = (-B-sqrt(D))/(2*A)
     output "A megoldások: ", X1, X2
   else
     X = -C/B
     output "A megoldás: ", X
   endif

Még mindig nem nyugodhatunk meg, mivel egy újabb osztás okozhat így problémát. Alkossunk erre egy új tesztesetet! Az A=0, B=0 és C=1 bemenetek nem megfelelő működést eredményeznek. Az algoritmus módosítására van szükség, miután újra átgondoltuk a problémát. Ekkor az egyenletből a C=0 kifejezés marad, ami pedig már nem is egyenlet, nincs megoldása, azonban az algoritmust még bolondbiztosabbá kell tennünk, kiterjesztve azt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
   output "Add meg a másodfokú egyenlet együtthatóit!"
   input A, B, C
   if A!=0 then
     D = B*B-4*A*C
     X1 = (-B+sqrt(D))/(2*A)
     X2 = (-B-sqrt(D))/(2*A)
     output "A megoldások: ", X1, X2
   else
     if B!=0 then
       X = -C/B
       output "A megoldás: ", X
     else
       output "Nincs megoldás."
     endif
   endif

Az is hamar szemet szúrhat, hogy egy gyökvonás is van az algoritmusban. Erről azt tanultuk, hogy negatív argumentum esetén nem értelmezhető a valós számok halmazán. Tehát problémába ütközhetünk, ha B*B-4*A*C<0. Válasszunk egy ilyen tesztesetet is (például: A=1, B=2 és C=2) és tegyük meg a szükséges módosításokat!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
   output "Add meg a másodfokú egyenlet együtthatóit!"
   input A, B, C
   if A!=0 then
     D = B*B-4*A*C
     if D>=0 then
       X1 = (-B+sqrt(D))/(2*A)
       X2 = (-B-sqrt(D))/(2*A)
       output "A megoldások: ", X1, X2
     else
       output "Nincs megoldás."
     endif
   else
     if B!=0 then
       X = -C/B
       output "A megoldás: ", X
     else
       output "Nincs megoldás."
     endif
   endif

Tovább kell keresnünk a hibalehetőségeket. Ha a diszkrimináns értéke pontosan nulla, akkor az X1 és X2 értéke ugyanaz, nem mondhatjuk, hogy két megoldása van az egyenletnek, pedig a program most két értéket is kiír. Módosítás szükséges, ami megoldja egy A=1, B=2 és C=1 tesztest által feltárt problémát.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
   output "Add meg a másodfokú egyenlet együtthatóit!"
   input A, B, C
   if A!=0 then
     D = B*B-4*A*C
     if D>=0 then
       if D>0 then
         X1 = (-B+sqrt(D))/(2*A)
         X2 = (-B-sqrt(D))/(2*A)
         output "A megoldások: ", X1, X2
       else
         X = -B/(2*A)
         output "A megoldás: ", X
       endif
     else
       output "Nincs megoldás."
     endif
   else
     if B!=0 then
       X = -C/B
       output "A megoldás: ", X
     else
       output "Nincs megoldás."
     endif
   endif

Az algoritmusunk így már elvileg elég jól működik, bár sokkal összetettebb, mint az első verzió. Később a valós programozási nyelvek és tényleges számítógépes implementáció során látni fogjuk, hogy technikai okok miatt még ez az algoritmus is számos ok miatt elbukhat. Előzetesen elég annyit tudnunk, hogy sok esetben nem mindegy, hogy egész számokról beszélünk vagy valósakról, mert más lehet a megoldás. Azt is fontos tudnunk, hogy a számítógép nem minden értéket tud tárolni (legalábbis elég pontosan), így a változók esetén felmerülő korlátozások miatt is szükségünk lehet további tesztesetekre (tesztelve a túl nagy vagy túl kicsi értékeket is).

A matematikai helyesség mellett azt is figyelembe kell venni, hogy az adott érték értelmezhető-e a valós világbeli probléma esetén. Például, ha a fenti egyenlettel egy síkidom területét akartuk meghatározni, akkor negatív értékeket nem fogadhatjuk el, még akkor sem, ha matematikailag helyes megoldásnak tűnnek.

Ezen elgondolások és ellenőrzések során összeállt egy elég sok tesztesetet tartalmazó tesztkészlet. Ezzel a kifejlesztett tesztelési stratégiával kell ellenőriznünk az elméleti algoritmusunkat és a később implementált szoftvert is.

A

B

C

Elvárt kimenet

3

7

2

A megoldások: -2, -1/3

0

3

9

A megoldás: 3

0

0

1

Nincs megoldás.

1

2

2

Nincs megoldás.

1

2

1

A megoldás: -1

0.000001

1000000

0

A megoldások: 0, 1000000000000

7.1. Szójegyzék

tesztelés

Egy folyamat, amellyel egy algoritmus (program) helyességét és működőképességét ellenőrizhetjük. Ha a tesztelés sikertelen, akkor módosításokra van szükség.

tesztkészlet

Összetartozó tesztesetek halmaza.

teszteset

Bemeneti értékek kombinációjának, az erre vonatkozó elvárt kimenetnek és a teszt végrehajtás feltételeinek együttese.

7.2. Kérdések, feladatok

  • Általában létezik olyan inputkombináció, amely nem kerül be a tesztkészletbe és így arra sohasem teszteljük a programot? Vagy mindig minden lehetőséget ellenőrzünk?

  • Adott az alábbi algoritmus pszeudokódja, amellyel egy tízes számrendszerben megadott éréket (N) átalakíthatunk más alapszámú (B) számrendszerbeli formába. Hozz létre egy tesztkészletet a helyes működés vizsgálatához! Milyen bemeneti értékek esetén működik jól az algoritmus, azaz milyen feltételek mellett használható? [S702]

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
       input N
       input B
       R=0
       P=1
       while N!=0 do
         R=R+(N%B)*P
         P=P*10
         N={N/B}
       enddo
       output R