Minél több (az összes?) hiba föllelése, minél „olcsóbban”, azaz minél kevesebb vizsgálattal.
Összes lefutás (»[1] összes bemenő adat kombinációja) helyett szisztematikus „próbálgatás”, de úgy hogy esély legyen minden hiba földerítésére.
Megjegyzem: a tesztelés nemcsak a hibásság felderítéshez nélkülözhetetlen, hanem a hatékonyság szempontjából kritikus pontok felderítéséhez is.
Def:
A teszteset a bemeneti adat (esetleg feltétel) és a hozzátartozó kimeneti adat (esetleg feltétel) kettőse.
Def:
Próba a tesztesetek azon halmaza, amelyre korlátozzuk a vizsgálatot a teszteléskor.
Def:
Ideális próba, amellyel a program összes hibája kideríthető.
Def:
e-szinten megbízható próba, amellyel 1-e valószínűséggel derítjük ki a program összes hibáját.
Def:
A program érvényes bemenete azon adatok együttese, amelyek teljesítik a program előfeltételét. Amik nem, azok alkotják az érvénytelen bemenetek halmazát. [2]
A jó teszt tulajdonságai:
1. Csak olyan teszteseteket tartalmaz, amelyek nagy valószínűséggel egy még felfedetlen hibát mutatnak ki a programban. (Pl. LNKO(x,y)-ra az [(5,5); 5][SzP1] után fölösleges a [(10,10); 10][SzP2].)
2. A meg nem ismételhető tesztesetek kerülendők.[3]
3. Teszteseteket mind az érvénytelen, mind az érvényes adatokra kell készíteni.
4. Minden tesztesetből a lehető legtöbb információt „ki kell bányászni”. (Eközben persze feltételezésekkel élhetünk a program működésének logikájára vonatkozólag. Pl. mikor jogos kijelenteni „LNKO(x,y)-ra az [(5,5); 5] után fölösleges a [(10,10); 10]”, s mikor nem[SzP3]?) Mit tapasztaltunk mi helyett?
5. A programtesztelő csak a program írójától eltérő („elfogulatlan”) személy lehet, mert a szerző nem képes hatékonyan „rombolni saját hitelét”.
Statikus: „papíron”/„fejben” végzett ellenőrzés (esetleges számítógépes „támogatással”, amikor a számítógép csak adatokkal szolgál a program szerkezetére, alkotó elemeire vonatkozólag). Ekkor nincsenek bemenőadatok.
Dinamikus: a program futtatása (számítógépen). Módszerek abban térnek el egymástól, hogy más és más módon válogatnak a bemenőadatok közül.
A tesztelő igyekszik a kódot megérteni. (Nagy szerepe van a technikai elvek betartásának!)
Leghatásosabb, ha a szerző maga magyarázza el a programját (a tesztelőnek).
Formai hibák legegyszerűbbike: a szintaktikus hiba. (Ezeket a fordítóprogramok automatikusan kiszűrik. Az értelmezőprogramok pedig valamely dinamikus módszerrel vehetők rá ezek felderítésére.)
Rafináltabb formai hibák (tekinthetők egyszerűbb szemantikai hibáknak is) kiderítésére célszerű speciális programokat, vagy a fordítóprogram esetleg meglévő speciális szolgáltatásait felhasználni. Ilyen mellékesen nyerhető információ a kereszt(„be-kasul”)hivatkozási táblázat (cross reference table) korábban említett fajtája:
objektum |
előfordulási hely |
hatáskör |
információhaladási irány |
N |
Program |
globális |
input-output |
N |
Beolvas |
paraméter |
output |
N |
Feldolgoz |
paraméter |
input |
I |
Feldolgoz |
lokális/saját |
(input-output) |
Van |
Program |
globális |
input-output |
Van |
Feldolgoz |
paraméter |
output |
… |
… |
… |
… |
vagy az alábbi fajtája:
objektum |
típus |
előfordulási hely/hozzáférés |
Program.N |
Egész |
55(K), Beolvas.18(V),
Feldolgoz.15(K), Feldolgoz.17(V/K) |
Feldolgoz.i |
Egész |
1(V), 2(K), 3(V), 5(K) |
Program.Van |
Logikai |
75(V), Kiír.6(V) |
Feldolgoz.Van |
Byte |
Feldolgoz.8(V),
Feldolgoz.16(K) |
… |
|
… |
Magyarázatok az utóbbi táblázathoz:
· Az objektum megadása: programegység-azonosító + „.” + egyedi azonosító (pl. Program.N[SzP4]), ha egy adathoz a „Program” egység-azonosító van hozzárendelve, akkor garantáltan globális adatról van szó.
· az „előfordulási hely/hozzáférés” tartalmazza azon forrás sorok felsorolását (programegység + „.” + relatív sorszám formájában; pl. Beolvas.18), ahol akár az értékére (K), akár a címére történik hivatkozás (V), más szóval akár felhasználjuk az értékét, akár módosítjuk azt.
(Egy roppant „gyenge epigon”, Turbo Pascal lehetőségre láthatunk példát, amelyhez a fordításhoz előzetesen beállítandó: Option/Linker/Detailed, s keletkezik egy ún. MAP-file, legalább minimális szerkezeti információt szerezzünk a programbeli fontosabb „tárgyiasult” fogalmak –úm. konstans, változó, eljárás stb.– memóriában elhelyezkedéséről.)
Például a fenti táblázat és a kód összevetése segíthet az alábbi anomáliák felderítésében:
·
felhasználatlan objektum – nem
(csak) ott deklarálódik, ahol felhasználódik
Gyakran ez történik, amikor „lendületből” deklarálunk munkaváltozókat,
majd –praktikus okok miatt– később a vele kapcsolatos munkálatokat belefoglaljuk
egy/több alprogramba, de a deklaráció „mementóként” őrzi egy elhamarkodott
döntésünk emlékét…
·
felhasználatlan érték – pl.
ugyanarra vonatkozó „közeli” értékadás
(i:=1;
For i:=1 to N do …)
Létrejöttének szokványos magyarázata: While-os ciklusnak indult (pl. a
keresés tétel részeként), de később (pl. a kiválogatás tételbeli) For-ossá lett.
·
értéknélküli változó – előbb
hivatkozunk rá, mint értéket kap
(Procedure
Elj(Const l:Integer);
Var i,j:Byte;
Begin
If T(x[l]) then i:=1 else j:=2;
k:=i; …)
· identikus transzformáció („áltranszformáció”) –
X:=1*J-0 |
1ÛI ??? |
· konstans értékű kifejezések –
While (i<1) And (i>N)
do … |
i<1) And (i>N)
º False (ha N³1) |
· végtelen ciklus –
a hibás |
a helyes? |
While (i<=N) And … do |
While (i<=N) And … do |
For i:=1 to N do |
vagy
a hibás |
a probléma forrása |
While f(i) do |
g(x,y) nem y-invariáns |
·
értéknélküli függvény, operátor –
Function Abs(Const
x:Real):Real;
Begin
If x<0 then Abs:=-x else Abs:=x;
End;
· algebrai „képtelenségek” –
If f(x)+f(x)<>2*f(x) then … If (p(x) And q(x))<> |
??? f(x) nem x-invariáns ??? p(x),q(x) nem x-invariánsak |
A programot ez esetben működés közben vizsgáljuk. Cél, hogy lehető legkevesebb számú tesztesettel a lehető legnagyobb számú hibát derítsünk föl. Hogy hogyan válogassuk össze a teszteseteket? Válasz: vagy a programból, vagy a feladatból kiindulva.
A program „belsejének” (szövegének) „ismertsége” szempontjából beszélhetünk:
· fekete doboz módszerről (vagy adatvezéreltről)
· fehér doboz módszerről (vagy logikavezéreltről)
Többnyire a feladatspecifikáció alapján kell a teszteset-csoportokat kitalálni.
Bemeneti adatok halmazát partícionáljuk („hézagmentesen” és egyértelműen felosztjuk), majd minden osztályból egy-egy reprezentánst jelölünk ki, amelyre alapozunk egy-egy tesztesetet. A partícionáláshoz alapul az utófeltétel szolgál.
Specifikáció |
Ekvivalenciaosztályok/Próba |
(Lineáris) keresés(H*,F(H,L)):L ÈL´N Be: NÎN, XÎH*, T:H®N Ki: VaneÎL, SorszÎN Ef: N=Hossz(X) Uf: Vane º $iÎ[1..N] : T(xi)
Ù |
Vane=Igaz ® [(1,(x1:T(x1)));(Igaz,1)] Vane=Hamis ® [(1,(x1:ØT(x1)));(Hamis)] |
Az előfeltétel által meghatározott érvényes és érvénytelen adatokat is külön osztálynak kell tekinteni.
Specifikáció |
Ekvivalenciaosztályok/Próba [(N,(x1:?T(x1)… xN:?T(xN)));Sorsz] |
Kiválasztás(H*,F(H,L)):N Be: NÎN, XÎH*, T:H®L Ki: SorszÎN Ef: N=Hossz(X) Ù $iÎ[1..N] : T(xi) Uf: SorszÎ[1..N]
Ù T(xSorsz) |
Ef=Igaz ® [(1,(x1:T(x1));1] Ef=Hamis ® [(1,(x1:ØT(x1));HIBA] A HIBA: a program jelzése az Ef nem teljesülésekor. (Az Ef-beli N=Hossz(X) figyelembevételére nincs igazán mód!) |
Érdemes a partíciók „határainál” külön eseteket definiálni.
Összefoglalóan az osztályok megalkotásáról:
a) adatÎ[a..b] ® O(adat)=O1ÈO2ÈO3ÈO4ÈO5, ahol O1:={x: xÎ(a..b)}, O2:={x: x<a}, O3:={x: x=a}, O4:={x: x=b}, O5:={x: x>b}
b) adatÎN [pl. darabszám] ® O(adat)=O1ÈO2ÈO3ÈO4ÈO5, ahol O1:={x: xÎ(0..max)}, O2:={x: x<0}, O3:={x: x=0}, O4:={x: x=max}, O5:={x: x>max} – nem elfelejtendő, hogy a programbeli adat-megfelelőt teszteljük, s így valójában –legalább– Z-beli. (Gondoljunk a „Bolondbiztosság” elvre!)
c) adat: ?T(adat) ® O(adat)=O1ÈO2, ahol O1:={x: T(x)}, O2:={x: ØT(x)}.
… a reprezentáns megválasztásáról:
a) több szempont szerinti osztályozás esetén célszerű úgy választani a reprezentánst, hogy egy teszteset több (igaz más-más szempont szerinti) osztályt is érintsen.
Pálda:
az előbbi LineárisKeresés-es példában N=3 helyett N=MaxN-t választva két legyet is ütünk egy csapásra: 1) N felső határértékénél tesztelünk, 2) a sorozat egyik szélsőséges helyzetű (azaz utolsó) elem megtalálására is rákérdezünk.
A szerint, hogy mennyire akkurátusan végezzük a program ismert szerkezetének bejárását, beszélünk:
· utasítás (egyszeri) lefedése
· döntés lefedése
· részfeltétel lefedése
(próba)stratégiáról.
Lépések:
1)
próbastratégiát választunk,
2)
a stratégia szerinti
tesztutakhoz tesztpredikátumot rendelünk,
3)
a predikátumok kijelölte
ekvivalencia-osztályokból kiválasztunk egy reprezentánst.
A következő példaprogramot fogjuk boncolgatni:
If f(x) then y:=B(x);
While p(x) do
Begin
C(x,y,z)
End;
1.3.2.1.1. Utasítások (egyszeri) lefedése
…kor úgy választjuk ki a teszteseteket, hogy minden utasítást legalább egyszer végrehajtsunk.
A tesztesetekben most csak az inputot jelezzük (a példa „elvi szinten” kezelése miatt az outputról semmit sem tudván):
If f(x) then y:=B(x); |
(x: f(x)) |
If f(x) then y:=B(x); |
(x: p(x)), |
1.3.2.1.2. Döntés lefedése
…kor minden feltétel teljesülését és nem teljesülését is elő igyekszünk idézni. (Ha ez nem lenne lehetséges, akkor lenne olyan ága a programnak, amelyet sohasem lehet végrehajtani, tehát fölösleges, vagy valamelyik feltétel hibás.)
If f(x) then y:=B(x); |
(x: f(x)) |
If f(x) then
y:=B(x); |
(x: Øf(x)) |
If f(x) then y:=B(x); |
(x: p(x)) |
If f(x) then y:=B(x); |
(x: Øp(x)) |
Megjegyzés:
A fenti 4 esetet kevesebb tesztesettel is le lehet fedni.
Mivel a p(x) ciklusfeltétel, elvárható, hogy előbb-utóbb a ciklus termináljon. Így elegendő olyan x, amelyre (legalább egyszer) belép a ciklusba. Ha ui. lenne olyan x, amelyre a C(x,y,z) sohasem állít elő p-t ki nem elégítő értéket, akkor a program végtelen ciklusba kerül, s így hibát derítettünk föl. (Tehát a 4. teszteset nem is szükséges.)
Az is világos, hogy esetleg 2 tesztesettel is próbálkozhatunk: pl. az (x: f(x))Ç(x: p(x))¹Æ esetén az (x: f(x)Ùp(x))-szel, és az (x: Øf(x))Ç(x: Øp(x))¹Æ esetén az (x: Øf(x)Ù Øp(x))-szel. (Persze egyéb kombinációk is kínálkoznak.)
1.3.2.1.3. Részfeltétel lefedése
…kor a feltételek minden egyes részfeltételét mind igazra, mind hamisra igyekszünk beállítani. (Ha ez nem lenne lehetséges, akkor van fölösleges vagy rossz részfeltétel.)
(x: f(x)), (x: Øf(x)) |
Itt is lehetséges, hogy kevesebb tesztesettel is célhoz érhetünk.
Lépései:
1.
a minimális számú olyan tesztút
meghatározása, amelyek a próbastratégiának megfelelően fedik le a
programgráfot.
2.
a program „szimbolikus
végrehajtásával” az előfeltételt transzformálva állítsuk elő a tesztpredikátumokat.
Figyeljük ezt meg egy példán!
A
program |
A programgráf |
… Db:=0; |
|
Lássuk a tesztutakat
és a tesztpredikátumokat
az
Ef: N³0 Ù "iÎ[1..N]:
ÁtiÎ[1..5] előfeltétel mellett!
… Tesztpredikátum levezetése: Ef Ù
i=1 Ù Øi£N
[SzP8] Þ |
|
… Tesztpredikátum levezetése (a ciklusmag érintett ága pontosan egyszer fut le): Ef
Ù
i=1 Ù
i£N
Ù
Þ |
|
… Tesztpredikátum levezetése (a ciklusmag érintett ága pontosan egyszer fut le): Ef
Ù
i=1 Ù
i£N
Ù
Þ |
|
A reprezentáns kiválasztását, azaz a tesztesetek megválasztását már az Olvasóra hagyjuk.
A hibajelenség milyenségétől függhetnek a további teendőink. Ismerjük meg tehát, milyen jellegzetes hibajelenségekkel találkozhatunk:
· Szintaktikai hiba („nyelvtani” hiba) – értelmezős nyelveknél
· Szemantikai hiba (végrehajtási hiba) – a beépített hibafigyelő rendszer jelzései („Range Checking Error”, „Divide by 0”, „Overflow”)
· Végtelen ciklus – magába mélyed, folyamatosan ugyanazt vagy szisztematikusan, de nem az elvárásoknak megfelelőeket irogat ki a képernyőre (fájlba)…
· („Normálisnak” tűnő terminálás után) nem jeleníti meg a kívánt eredményt –
· Helytelen eredmény jelenik meg –
Ha már vannak „jelenségek”, nekiláthatunk a hiba helyének (okának) felderítésének. Ha van elképzelésünk a hiba természetére, akkor tudunk ellene tenni, azaz nekifoghatunk a hibajavításnak. A két elméletileg elkülöníthető lépés (hibakeresés, -javítás) megvalósításához nyújtanak támpontot az alábbi elvek.
Hibakeresés:
· Alaposan végig kell bogarászni… („sohase improvizálj a hibakeresésnél!”)
· Ha hiba van, akkor másutt is hatása lehet… („hiba hibát szül”)
· A hibák száma, súlyossága a mérettel lineárisnál jobban nő… :-(
Hibajavítás:
· Amíg nincs lokalizálva a hiba, ne javítsunk!
· Nem tünetet, hanem a hibát…
· Javítás után minden korábbi tesztet újból…
· A javítás eredményessége a mérettel rohamosan csökken… :-(
· A javítás visszanyúlhat akár a tervezési fázisba…
Indukciós |
Dedukciós |
|
|
Feltevés: Tesztek Þ szabályszerűség alapján: egyre bővülő adatosztály. Megerősítés: újabb teszt, ami igazolja (?) |
Feltevés: Tesztek Þ szabályszerűség alapján: egyre szűkülő adatosztály. Megerősítés: újabb teszt, ami igazolja (?) |
Pl.: x=-1, -7, -50 – nem OK. Þ negatívakra nem OK (?) Újabb teszt: [x=-37; ?] |
Pl.: x=12, 20 – OK. Þ nem OK: Prím(x) Ú Páratlan(x) Ú Újabb
teszt: [x=37; ?] A 37 prím is, páratlan is, de nem 10 alatti és nem 4-gyel osztható. |
Egy jól hangzó „elvi” módszer: visszalépéses technika – (fejben) a hibától vissza… amíg jót nem találunk…
Egy praktikusabb: teszteléssel segített …
Erősen függ az alkalmazott fejlesztői környezettől… A legfontosabbak, amelyeket persze magunk is „előidézhetünk” (csak ez sok többlet munkával jár, és számolhatunk a „kvantumhatással”, azaz a nyomkövetés célú kód „interferálhat” a vizsgált programmal magával):
· Adatkiírás – adatfigyelés
· Töréspontok – feltételes, feltétel nélküli, aktuális pontig
· Nyomkövetések – „makró-” (eljáráshívás 1 lépésben) és „mikró-lépésenként” (kód-utasításonként)
·
Eljárás-verem kiírás – a hívott
alprogramok és aktuális paraméterei
·
Post mortem lista – hibás
megállást megelőző valahány utasítás…
·
…
A Programírási folyamat elemei
1.0.
A cél, alapfogalmak és -elvek
1.3.1.1.
Ekvivalencia osztályok
1.3.2.2.
Tesztpredikátumok generálása
[1] Lehet több is (gondolj pl. a Randomize-ra) és kevesebb is (ui. több bemenetre is haladhat ugyanazon ágakon).
[2] Gondoljon a feladat- és programspecifikációra! A programspecifikáció többek közt abban tér el a feladatspecifikációtól, hogy az eredeti előfeltételnek eleget nem tévő adatokra is „helyesen”, azaz tervezett hibaüzenettel reagál. Ez okból érvényes bemenet alatt –természetesen– a feladatspecifikációban szereplő előfeltételre gondolunk első közelítésben, és persze a kódolás adta szűkítő feltételekre.
[3] Apropó: Randomize. A Randomize okozta megismételhetetlenség elkerülése: kiiktatása a programból a tesztelés idejére.
[SzP1]A jelölésről: [(x,y); LNKO(x,y)].
[SzP2]Mivel a megoldó program alighanem oszthatósággal operál, és 10 = 2*5.
[SzP3]Ha az algoritmusban szerephez juthat a „prímség”, akkor lehetséges, hogy a működésben különbség van; így a 2. teszteset információt hordoz.
[SzP4]A „Program”-kulcs-szó esetleg elhagyható, ha megállapodunk abban, hogy jelentse ez azt: a program egy globális adatáról van szó.
[SzP6]A (x1:?T(x1)… xN:?T(xN)) jelölésről: az x-sorozat, amely elemei (egyenként) vagy T-tulajdonságú vagy nem. Döntésünktől függően.
[SzP7]Mindenek előtt While-ciklusra kell áttérni.
[SzP8]A Db nem szerepel az Ef-ben, ezért nem vesszük figyelembe a levezetésnél.