A software tesztelés problémái - nem teljesség

Mindig is bosszant, amikor akár egy operációs rendszerben, akár egy felhasználói programban bukkanok rá valamilyen hibára. Minél ostobább egy hiba, annál inkább tűnik érthetetlennek, hogy a mai korszerű programfejlesztő eszközökkel hogyan maradhatnak ilyen hibák egy termékben.

Talán mégsem annyira fejlett a programírás elmélete és gyakorlata, mint amennyire hinni szeretnénk ebben? Talán a kapkodás, a pénzhajhászás magyarázza a primitív hibák jelenlétét? Vagy valami egészen másról van szó?

Elkezdtem ezen gondolkodni, és elképzelni egy olyan tesztelő rendszert, amivel ki lehetne szűrni minden programozási és rendszer hibát. És arra jöttem rá, hogy ilyen rendszer nem készíthető, legalábbis ami a tesztek egyik fajtáját, a "black-box" (fekete-doboz, vagy külső) tesztelést illeti.

Ez olyan tesztelési technika, amikor a tesztelő nem rendelkezik a forrásprogrammal, csak a végleges termék van a kezében. Erről kell megállapítania, hogy hibátlan-e vagy sem. Mondjuk, hogy a kezében van a specifikáció, sorban ellenőrzi az abban foglalt elvárásokat, ha mindegyiket sorra vette, és nem talált a futtatás során hibát, akkor ugye hibátlannak tekinthető a vizsgált termék. Egyáltalán nem. A specifikáció ugyanis nem biztos, hogy tartalmazza a különleges eseteket is. Elfogy a hely a lemezen, elfogy a memória, megszakad a hálózati kapcsolat, a felhasználó olyan adatokat ad a programnak, amit a specifikáció szerint nem adhatna, de a felhasználó már csak ilyen, szándékosan, vagy véletlenül olyan feltételek közé viheti a programot, amire a specifikáció készítői nem gondoltak.

De még ha rendelkezünk is egy teljes specifikációval, amely minden határesetre és különleges helyzetre tartalmaz viselkedési leírást, akkor sem teljesíthetjük tökéletesen a tesztelési feladatunkat.

Ennek belátásához nézzünk meg egy nagyon egyszerű esetet, kapunk valahonnan egy max() függvényt (mondjuk egy DLL-ben), ami két egészet vár, és visszaadja a kettő közül a nagyobbikat. Le tudjuk-e úgy tesztelni black-box módszerrel ezt a függvényt, hogy kijelenthessük, a függvény 100%-osan hibamentes és minden körülmények között vissza fogja adni a két kapott egész érték közül a nagyobbat?

Írjunk egy tesztprogramot, tegyük bele a max(1, 2) és a max(2 ,1) hívást, mindkét esetben 2-t kell, hogy kapjunk eredményül. A biztonság kedvéért hívjuk meg a függvényt max(2, 2) módon is, ekkor is 2-t kell, hogy kapjunk. Készen vagyunk vajon a teszteléssel? A kód 100%-osan hibamentes?

Elvileg igen. A gyakorlatban számolnunk kell a rosszindulatú kódoló esetével, ez egy olyan programozó, aki szándékosan hibát rejt el a kódban, ezt ráadásul nagyon fondorlatos módokon tudja megtenni. Az elvileg 100%-osan hibamentes kódunk tehát valójában rengeteg hibát rejthet magában.

Mert mi van, ha a kódban van egy részlet, ami a kapott argumentumoktól függően működik, és ha valamelyik argumentum mondjuk 1 000 000, akkor nem a nagyobb, hanem a kisebb argumentumot adja vissza. Ezt csak úgy tudjuk tesztelni és a hibát detektálni, ha 1 000 000-ig mindenféle kombinációban meghívjuk a max() függvényt. És mivel nem tudhatjuk, hogy hol van a hiba elrejtve, ki kell próbálnunk nemcsak 1 000 000-ig, hanem a gépen ábrázolható maximális nagyságú egész számig minden hívás kombinációt.

Ez a teszt már tovább fog tartani, mint a legelső, lefuttatjuk, nem találunk hibát. Vajon 100%-osan hibátlannak nyilváníthatjuk most a függvényt?

Nem, még nem. A programozónk lehet még ennél is rosszindulatúbb, és elrejthet dátumhoz és időponthoz kötött hibát is. Ennek megtalálásához az összes elképzelhető dátumra és időpontra be kell állítanunk a gépünk óráját, és meg kell ismételnünk az összes lehetséges egész szám kombinációra a tesztet, hiszen a programozó a dátum-, és az argumentum vizsgálatot össze is kapcsolhatta úgy, hogy a függvény elképzelhető, hogy csak az elkövetkező ötven év egyetlen bizonyos másodpercében egyetlen kombinációra fog hibás választ adni, a többi esetben hibátlan lesz. De elég egyetlen hiba ahhoz, hogy a kódot ne tekinthessük tökéletesen hibamentesnek.

Azt hiszem, már látjuk a reménytelen jövőt. A rosszindulatú kódolónk az argumentumok és a rendszeridő mellé odaveheti a program méretét, az összes-, és a szabad memória méretét, azt, hogy egy bizonyos regiszterben páros-, vagy páratlan szám van-e éppen, letárolhatja, hogy hányszor hívták már meg, és ettől függően működik jól, vagy rosszul. Gyakorlatilag végtelenek a lehetőségek, emiatt pedig teljesen esélytelen, hogy emberi idő alatt végezzünk.

Persze mondhatjuk, hogy ezek a példák eléggé abszurd rosszindulatot tételeznek fel a kódolónk részéről, de mi most nem azt vizsgáltuk, hogy mennyire életszerű ez az elképzelés, hanem annak szerettünk volna utánajárni, hogy elvileg lehetséges-e a tökéletes black-box tesztelés.

Én az eddig elmondottak alapján bizonyítottnak látom, hogy külső eszközökkel nem állapítható meg egy programról, vagy kódról, hogy az hibátlan-e vagy sem. Még százalékos becslést sem tudunk mondani, hiszen nem tudjuk, hogy egy-egy felderített hibára hány felderítetlen jut.

Ezzel nem szeretném még véletlenül sem azt a látszatot kelteni, hogy felmentést érdemelnek mindazok, akik hibás kódot adnak ki a kezükből. Szó sincs erről. Egyszerűen tény, hogy a black-box tesztelés nem adhat nekünk teljes biztonságot.

Hogy akkor hol keressük a teljes biztonságot? Elsősorban a saját magunk számára készített saját kódunkban bízhatunk meg, hiszen arról tudjuk, hogy nem egy rosszindulatú kódoló írta, és nem abból a célból, hogy kárt okozzon vele. Másrészt fokozottan megbízhatunk azokban a nyílt forrású kódokban, amiket elvileg sokan ellenőrizhetnek, így kiderülhet, ha esetleg rosszindulatú kód van benne. Bár attól, hogy valami nyílt forrású, még nem biztos, hogy ellenőrzött. Elvileg minden nyílt forrású kód mellé kapnunk kellene egy ellenőrzési listát arról, hogy ki, mikor, milyen részeket, és milyen mélységig elemezte a kódot. Mondanom sem kell, ilyen manapság nem létezik, és elképzelni is nehéz hány ember-év kellene egy-egy nyílt forrású program ellenőrzési listájának elkészítéséhez. És ez a lista, csak az első módosításig lenne megbízható, utána felül kellene vizsgálni minden egyes módosítás után.

Mivel láttuk, hogy a black-box tesztelés milyen határok között megbízható, nézzük meg, hogy vajon javíthatunk-e a megbízhatóságán. Ehhez a "black-box"-ot valamennyire ki kell nyitnunk, azaz bele kell néznünk a kódba is. Ehhez nyomkövetőt, visszafordítót, vagy automatikus elemző programot használhatunk. Így már némileg több esélyünk van arra, hogy rátaláljunk egy-egy furcsa konstansra, vagy feltételes ugró utasításra, ami rosszindulatú kódra utalhat. Ez például egy max() függvény esetében még működhet is, de hát egy max() függvényt mi magunk is megírhatunk ahelyett, hogy a rengeteg időt az elemzésére fordítanánk. Csakhogy a kódok legtöbbször sokkal-sokkal bonyolultabbak a példánkban szereplő max() függvénynél. Egy nagyobb programban nyomkövetéssel, vagy visszafejtéssel rátalálni valamilyen rejtett kódra anélkül, hogy a teljes program működését megértenénk, nagyon nagy feladat.

Úgy néz ki tehát, hogy a "nyitott black-box" technikával, vagy akár a nyílt forrású kód elemzésével sem fogunk tudni eljutni a 100%-os bizonyosságig.

Hogy más eszközökkel juthatunk-e ennél messzebbre, arra példaként a legjobb, ha a víruskeresőket vizsgáljuk meg egy kicsit közelebbről, vajon ők milyen szintre jutottak a rosszindulatú kódok automatikus felderítésében. Sajnos azt mondhatjuk, nagyon sok a bizonytalanság. Amikor egy amúgy népszerű, széles körben használt, megvásárolt kereső heurisztikus elemzését kell kikapcsolni, mert olyan programokat töröl le, amik nélkül nem tudunk dolgozni (nem egyet saját magunk írtunk), akkor úgy tűnik, az elemzés vagy olyan kódot is gyanúsnak talál, ami nem az, vagy pedig túl engedékeny és elnéző, ezzel a valóban vírusos kódot is veszélytelennek mutatja.

Talán az automatikus kódelemzéstől 100%-os teljesítményt várni ugyanolyan lehetetlen elvárás, mint olyan program megírásában bízni, amely bármely másik programról megmondja, hogy végtelen ciklusba kerül-e, vagy megáll majd valamikor. Erről már tudjuk, hogy lehetetlen. Talán a kártékony kód és a programhibák automatikus elemzéssel történő 100%-os detektálása is ugyanilyen lehetetlen feladat. Erre persze nem tudok bizonyítással szolgálni.

Itt lenne az ideje annak, hogy valaki elkészítsen egy, Hilbert 23 pontos listájához (ami a matematika megoldandó problémáit sorolta fel) hasonló listát a nyitott, még megoldandó számítástechnikai problémákról, ezen a listán szerepelhetne a kérdés: "Lehetséges-e programhelyesség bizonyító programot írni, ha nem, akkor miért nem?". Talán már látjuk is a választ a kérdésre, valószínűleg a megállási probléma megoldhatatlansága a kulcs itt is.

Tudom, hogy ha elkészülne egy ilyen program, akkor először ennek a helyességéről kellene meggyőződni, és talán ez az az ok, ami miatt soha nem fog elkészülni ez a program sem...


Nyíregyháza, 2018. szeptember 27 - 2019. április 7.