Preskušanje programske opreme, uporaba razhroščevalnikov
Uvod
Cilji te lekcije so:
Spoznati terminologijo (hrošč / napaka/ …)
Možnosti za ravnanje z napakami
Spoznati tehnike za ravnanje z napakami
Opredeliti aktivnosti testiranja
Namen testiranje enot
Spoznati metodi črne in bele škalte; primerjava metod
Namen testiranja integracije, načini
Testiranje sistema – testiranje učinkovitosti
Testiranje sprejemljivosti (alfa test, beta test)
Testna ekipa
n
Za začetek nekaj programerskih napak
Programska
oprema lovskega letala F16 je imela napako, ki
je povzročala, da se je letalo obrnilo na glavo, kadarkoli je prečkalo
ekvator (napako so odkrili na simulatorju in v resnici ni nikoli
prišlo do
tega pojava).
Med
1985 in 1987 je računalniško krmiljena naprava Therac-25,
uporabljena za obsevanje v bolnišnicah, povzročila zaradi
programerske napake pacientom preveliko sevanje. Nekaj pacientov je
celo umrlo.
4. junija 1996 je evropska vesoljska raketa Ariane 5
eksplodirala 40 sekund po vzletu. Eksplozijo je povzročila programerska
napaka. (Izguba: cca 1 milijarda avstralskih dolarjev).
Pomembnostpreskušanjaprogramskeopreme
Vsak
programer se sreča z nevarnostjo napak. Na nekatere, ki so bolj
slovnične napake, ga opozori že prevajalnik. Druge nastopijo v času
izvajanja programa. Nekatere so tako zahrbtne, da nastopijo le
ob določenih podatkovnih pogojih. Te je najtežje odkriti, ker
včasih niti niso jasno ponovljive.
Napake in opozorila, kijihjavižeprevajalnik ne bomo posebej obravnavali. Brez odprave napak tako ali tako ne moremo
dobiti izvedljivega programa. Smiselno je tudi, da odstranimo vse
vzroke za opozorila, ki sama po sebi niso usodna, skrivajo pa
nevarnost, da je lahko kaj narobe.
Posvetili se bomo resnejšim napakam, ki se zgode ob času izvajanja programa. Programerji
ločijo med programskimi napakami in programskimi izpadi. V primeru izpada
program ne naredi tistega, kar od njega pričakujemo. Programska napaka pa je
tisto, kar lahko pripelje do programskega izpada (ali pa tudi ne). Do izpada pride, če pride do nastopa
določenih računskih pogojev.
Poznamo
več načinov preskušanja programske opreme, vendar
preskušanje kompleksnih programov ni rutinski postopek pač
pa že kar proces raziskovanja.
Besedo
preskušanje povezujemo tudi s pojmom „dinamična analiza” programa. Pripeljala
naj bi do njegove večje zanesljivosti, učinkovitosti, prenosljivosti in
možnosti vzdrževanja.
Osnovni izrazi
Hrošč (bug):Algoritmični vzrok za napako.
Napaka (error): Sistem je v takšnem stanju, da ga bo nadaljnje
izvajanje privedlo do odpovedi.
Odpoved (failure): Vsak odklon opazovanega obnašanja od specificiranega
obnašanja.
Zanesljivost (reliability): Merilo, ki pove koliko se opazovano obnašanje sistema ujema
s specifikacijami obnašanja.
Ravnanje z napakami
Verifikacija (verification): Predvideva hipotetično okolje, ki se ne ujema z
realnim okoljem
Modularna
redundanca (modular redundancy) To pomeni podvajanje modulov. Tak pristop
je drag!
Razglasitev
napake za lastnost sistema (declaring bug as a feature); slab
pristop ):
Krpanje (patching): Pripelje k “edinstvenim” sistemom (one-of-a-kind
systems)
Testiranje
(testing): - nikdar preveč, običajno ni dovolj dobro opravljeno
Razumno ravnanje z napakami
Nobenega ne-trivialnega modula ali sistema ni mogoče
popolnoma testirati. Za kaj takega obstajajo teoretične omejitve. Prav
tako imamo tudi praktične omejitve: na voljo so nam leomejen čas in
omejeni stroški.
Testiranje lahko pokaže samo prisotnost napak, nikdar pa ne dokaže njihove odsotnosti (E.W. Dijkstra).
Tako nam preostane le razumno ravnanje z napakami:, kot ga nakazuje tudi spodnja slika:
Poglejmo si to bolj podrobno osnovne prijeme:
Izogibanjenapakam (preden je sistem izdan – before
release):
Uporabimo primerno programersko metodologijo, ki zmanjša
kompleksnost
Uporabimo nadzor nad verzijami, da preprečimo
nekonsistentnost sistema
Uporabimo verifikacijo, da se izognemo algoritmičnim napakam
oz. hroščem
Odkrivanje napak (med izvajanjem sistema):
Testiranje: Generiramo napake na planiran način in preskušamo sistem
Razhroščevanje: Začnemo z neplanirano odpovedjo
Nadzor (monitoring): Spremljamo informacije o stanju
sistema; poiščemo napake, ki vplivajo na učinkovitost
Tolerarinje napak: pomeni okrevanje po napaki, ko je sistem že izdan
– (after release):
Preskušanjeenotjeposvečenopreskušanjunajmanjšihprogramskihkomponent.Testiranje izvede razvijalec Cilj: Potrditev, da je enota pravilno
implementirana in zagotavlja načrtovano funkcionalnost.
Pri preskušanju enot uporabljamo takoimenovano dinamično analizo, ki jo sestavljajo:
Testiranje z metodo
črne škatle (black-box testing). To pomeni testiranje vhodov/izhodov posameznih enot.
Testiranje z metodo
bele škatle (white-box testing). To pomeni testiranje interne strukture oz. logike podsistema ali
objekta.
Testiranje na osnovi
podatkovnih struktur. Podatkovni tipi določajo testne primere.
Testiranje z metodo črne škatle
Usmerjeni smo v obnašanje vhodov/izhodov. Če se za vsak
podan vhod izhod ujema s predvidenim, potem enota opravi test. V večini
primerov je nemogoče preizkusiti vse možne vhode (testne primere). Zato želimo zmanjšati
število testnih primerov z ekvivalenčnim particioniranjem. Vhodne pogoje razdelimo
v ekvivalenčne razrede. Testne primere
izberemo iz vsakega ekvivalenčnega razreda.
Primer 1: vhod je veljaven za nek interval vrednosti (npr.
med 100 in 5000). Določimo tri ekvivalenčne razrede:
Pod intervalom (x < 100)
Znotraj intervala (100 x 5000)
Nad intervalom (5000 < x)
Primer 2: vhod je veljaven za množico diskretnih vrednosti
(npr. {A, B, C}). Določimo dva ekvivalenčna razreda:
Veljavna diskretna vrednost (x {A, B, C})
Neveljavna diskretna vrednost (x {A, B, C})
Testiranje z metodo bele škatle
Testne primere naredimo na osnovi strukture programa. Pri identifikaciji dodatnih testnih primerov
uporabimo naše poznavanje programa. Preverjamo
posamezne stavke na primer z izbiro
operatorjev v izrazih. Preverjamo lahko tudi zanke, tako da:
Določimo
pogoje tako, da se zanke ne izvede (izjema so REPEAT zanke)
Izvedemo
zanko natančno enkrat
Izvedemo
zanko več kot enkrat
Končno lahko preverjamo programske poti tako, da zagotovimo, da se izvedejo vse poti v programu.
Pri tem moramo preveriti vejitve tako, da zagotovimo, da je vsak možen izhod iz
pogoja testiran vsaj enkrat.
Preskušanje integracije
Preskušanjeintegracijenaj bi pokazalonapakepriinterakcijiprogramskihkomponent in napakepriuporabniškihvmesnikih. Testiramo
skupine podsistemov
(zbirke razredov) in nenazadnje celoten sistem. Testiranje izvede
razvijalec. Cilj:
Testiranje povezav med posameznimi podsistemi in njihovo delovanje kot
celota. Pri preskušanju integracije lahko uporabimo različne
pristope:
Big
bang integracija (neinkrementalna)
Integracija od spodaj navzgor (Bottom-up)
Integracija od zgoraj navzdol (Top-down)
Sendvič pristop
Sistemsko preskušanje
Sistemskopreskušanjepreverja, alicelotniprogramskisistemzadoščazahtevam, kismojihpostavilinazačetkurazvojaprograma. Testiramo celoten sistem. Testiranje
izvede razvijalec. Cilj: Ugotoviti, ali sistem izpolnjuje zahteve (funkcionalne
in globalne).
Preskušanje učinkovitosti
Delujoč sistem sicer lahko formalno zadošča zahtevam,
vendar lahko z dodatnim preskušanjem ugotovimo še njegovo
učinkovitost, zanesljivost, kvaliteto. Poznano zato še vrsto
preskusov. Nekateri od teh preskusov presegajo problematiko kvalitetne
programske opreme in so tu našteti le zaradi celovitosti:
Stresno testiranje (stress testing): testiramo skrajne limite sistema
(maks. št. uporabnikov, najzahtevnejša povpraševanja, ...)
Volumensko testiranje (volume testing): testiramo kaj se zgodi pri
obravnavi ogromnih količin podatkov
Konfiguracijsko testiranje (configuration testing): testiramo različne konfiguracije
strojne/programske opreme
Testiranje kompatibilnosti (compatibility testing): testiranje kompatibilnosti z
obstoječimi verzijami sistema
Preskussprejemljivostiomogočakončnimuporabnikomprograma, da se odločijo, aliprogramskiproduktsprejmejoalizavrnejo. Preskussprejemljivostiše boljpodrobnodelimonaalfa in betapreskušanje (gledena to, alipoteka preskus pri razvijalcu, ali pa res že pri končnem uporabnikuoziromajavnosti). Cilj: Prikazati, da sistem
izpolnjuje kupčeve zahteve in je pripravljen za uporabo. Izbiro testov opravi
naročnik / kupec
Alfa
testiranje
Naročnik
uporablja sistem pri razvijalcu
Programska
oprema se uporablja v nadzorovani postavitvi
Razvijalec
je zmeraj pripravljen odpraviti napake
Beta
testiranje
Izvaja
se pri naročniku, razvijalec ni prisoten
Na
sistemu se opravlja realistično preskušanje v ciljnem okolju
Potencialna
stranka lahko izgubi zaupanje
Razhroščevalniki
Razhroščevalnik
(debugger) je računalniški program, ki ga uporabljamo za
preskušanje in razhroščevanje drugih
programov. Razhroščevanje dejansko pomeni odpravljanje
napak.
Ko pride do izpada programa, pokaže razhroščevalnik položaj
v originalni izvorni kodi, v nekaterih primerih pa kar v strojni kodi
(prevedenega) programa, ki ga preskušamo.
Program izpade (se "sesuje" oziroma "obesi") zaradi neke programske
napake. Morda na danem mestu CPE poskuša izvajati dostop do
nedostopne lokacije v pomnilniku.
Razhroščevalniki
tipično nudijo še boljše možnosti
preskušanja. Tako omogočajo koračno sledenje programa od
enega do drugega programskega stavka. Najmanj, kar lahko tako vidimo,
je, če program res poteka v predvidenem zaporedju stavkov. Ker vmes
lahko tudi opazujemo stanja programskih spremenljivk, dobimo tako res
podroben vpogled v delovanje programa. Je pa tako pregledovanje
programa kljub temu zamudno.
Razhroščevalniki, ki nudijo možnost
koračnega sledenja programa, imajo tipično naslednje opcije v "orodni
vrstici" ikon, kot jih prikazuje leva slika. Stvar spominja na gumbe na
predvajalniku. Poleg normalnega teka programa imamo tudi možnosti za
njegovo izvajanje od ukaza do ukaza.
Ker je posebno pri programih, ki vsebujejo
kakšne zanke, tako sledenje programa zamudno (kar pomislimo na zanke,
ki se morda ponavljajo več stokrat), imajo tipični razhroščevalniki
možnost določanja takoimenovanih "prekinitvenih točk" (breakpoints).
Razhroščevalnik kaže izvorno kodo, mi pa v njej izberemo točko, kjer
želimo, da razhroščevalnik izvajanje programa samodejno ustavi. Takih
točk je seveda lahko več.
Če program ne pride do izbrane prekinitvene točke, pomeni, da se je že prej
zgodilo nekaj čudnega. Postavimo zato kakšno vmesno prekinitveno
točko in poskusimo znova.
Leva slika prikazuje primer takega razhroščevalnika in
vzpostavljeno prekinitveno točko za program v jeziku Python.
Kako se torejlotitipreskušanjapreprostih programov?
Pri
lastnih, manjših programih seveda ne bomo uporabljali
metodologije, ki jo uporabljamo pri kompleksnih programih. Tudi
odgovornost ob pojavu napak ni tako velika. Pa vendar želimo, da
program opravlja svojo funkcijo.
Najprej moramo spoznati, kaj se s
programom dogaja:
oSe
ustavi (se obesi), oDaje
napačne rezultate, oNjegovo
izvajanje je nenavadno počasno.
Pripravimopodatke, zakaterepoznamo, kako se mora program obnašati in kajmoraizpisovati.
Čenimamodrugemožnosti, vstavljamo v program vmesneizpise, kijihpotemspremenimo v komentaralipovsemizločimo.
Iskanje
napak zelo poenostavi uporaba primernega razhroščevalnika in
postavljanje prekinitvenih točk na sumljiva mesta.
Imejmo ogromno potrpljenja!! In bodimo vztrajni!!
Dajmo program
preskušati komu drugemu (in zbiramo povratne informacije). Sami
smo namreč pogosto ne vidimo lastnih napak. To je tudi smisel že
omenjenih alfa in beta testov.