Preskušanje programske opreme, uporaba razhroščevalnikov

 Uvod

Cilji te lekcije so:
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).

 

Pomembnost preskušanja programske opreme

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, ki jih javi že prevajalnik 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

Ravnanje z napakami

  1. Verifikacija (verification): Predvideva hipotetično okolje, ki se ne ujema z realnim okoljem
  2. Modularna redundanca (modular redundancy)  To pomeni podvajanje modulov. Tak pristop je drag!
  3. Razglasitev napake za lastnost sistema (declaring bug as a feature); slab pristop ):
  4. Krpanje (patching): Pripelje k “edinstvenim” sistemom (one-of-a-kind systems)
  5. 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:

Izogibanje napakam (preden je sistem izdan – before release):
Odkrivanje napak (med izvajanjem sistema):
Tolerarinje napak: pomeni okrevanje po napaki, ko je sistem že izdan – (after release):

Preskušanje programske opreme

Poznamo štiri nivoje preskušanja programov. Vsak naslednji nivo vključuje tudi predhodnji nivo:
Poglejmo si te nivoje bolj podrobno:

Preskušanje enot

Preskušanje enot je posvečeno preskušanju najmanjših programskih komponent.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
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 lele 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 element {A, B, C})
Neveljavna diskretna vrednost (x not-element {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:
 
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šanje integracije naj bi pokazalo napake pri interakciji programskih komponent in napake pri uporabniških vmesnikih.  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:

Sistemsko preskušanje

Sistemsko preskušanje preverja, ali celotni programski sistem zadošča zahtevam, ki smo jih postavili na začetku razvoja programa. 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:

Preskus sprejemljivosti

Preskus sprejemljivosti omogoča končnim uporabnikom programa, da se odločijo, ali programski produkt sprejmejo ali zavrnejo. Preskus sprejemljivosti še bolj podrobno delimo na alfa in beta preskušanje (glede na to, ali poteka preskus pri razvijalcu, ali pa res že pri končnem uporabniku oziroma javnosti).  Cilj: Prikazati, da sistem izpolnjuje kupčeve zahteve in je pripravljen za uporabo. Izbiro testov opravi naročnik / kupec
 


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 torej lotiti preskušanja preprostih 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:

o       Se ustavi (se obesi),
o       Daje napačne rezultate,
o       Njegovo izvajanje je nenavadno počasno.

Pripravimo podatke, za katere poznamo, kako se mora program obnašati in kaj mora izpisovati.

Če nimamo druge možnosti, vstavljamo v program vmesne izpise, ki jih potem spremenimo v komentar ali povsem izloč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.