Testimise alused

From Wikiversity

< 4. nädala teemad

Mõisted[edit]

  1. Testimine on programmist vigade otsimine. Vigu võib otsida nii programmi käivitades kui ka koodi läbi vaadates.
  2. Testimise eesmärk on vigade leidmine, mitte tõestamine, et vigu ei ole.
  3. Staatiline testimineon testimine, mille käigus programmi ei käivitata, vigu otsitakse lähtekoodis.
  4. Dünaamiline testimine on programmi käivitamine eesmärgiga seal vigu otsida.
  5. Edukas test on selline test, mis aitas leida vigu.
  6. Hea test avastab võimalikult palju vigu.
  7. E. W. Dijkstra kirjutas 1972 aastal: "Programmi testimise abil saab näidata, et programmis on vigu, kuid ei saa tõestada vigade puudumist."

Tihti nimetatakse testimiseks vaid 'dünaamilist testimist (st vigade otsimist programmi täites).

Staatiline testimine[edit]

Staatiliseks testimiseks nimetatakse tegevust, kus programmi koodist otsitakse vigu seda lugedes.

  • Koodi läbivaatus on tavaliselt kollektiivne üritus.
  • Koodi läbivaatuseks on kontrollküsimustikud, et seda kuidagi süstemaatilisemalt teha.
  • Küsimustiku eesmärgiks on probleeme mingil viisil süstematiseerida ja sel viisil probleemseid kohti leida.
  • Erinevate keelte puhul on sobilikud veidi erinevad küsimustikud, sest küsimustik sõltub keele iseloomust ja eripäradest.

Järgnev küsimustik on lühendatud näide küsimustikust, mis on prof. Jaak Tepandi Tarkvara kvaliteedi ja standardite konspektis.

Staatilise testimise küsimusi[edit]

Andmete kirjeldamine

  • Kas muutujad on ilmutatult kirjeldatud? Sõltub keelest, paljud kompilaatorid kontrollivad seda, Pythonis tuleks muutujale enne kasutamist midagi omistada.
  • Kas muutujad on õigesti algväärtustatud?
  • Kas muutujad on sarnaste või segadusse ajavate nimetustega, näiteks VOLT ja VOLTS, L1 ja Ll? Selle teema kohta on mitmeid viiteid struktuurprogrammeerimise tehnikate juures, samas aga ka erinevate keelte stiilisoovitustes.

Andmete kasutamine

  • Kas muutuja väärtuse kasutamisel on muutujal mõistlik väärtus olemas? Väärtud preaks olema mutuujasse sattunud läbi sisendi või omistamise. Pythonis tekib küll muutuja siis, kui talle midagi omistatakse, kuid konkreetne väärtus on siin väga oluline.
  • Kas massiivi indeks võib programmi töö käigus minna väljapoole lubatud piire (Pythonis kehtib see stringide, jadade, ennikute kohta)?
  • Kas indeksile omistatakse täisarv?
  • Kas alamprogrammid muudavad „salaja“ andmestikke? Õnneks ei juhtu see Pythonis päris salaja, sest eelnevalt tuleks funktsioonis muutuja globaalsena kirjeldada.

Arvutusvead

  • Kas arvutusi tehakse lubamatute tüüpidega? Seda võib aidata interpretaator või kompilaator tuvastada, kuid mitte kõigi keelte puhul. Pythoni interpretaator ei luba aritmeetikavaldistes suvaliselt muutujaid kokku panna.
  • Kas kasutatakse koos (ühes avaldises) eri tüüpi andmeid, mis omavahel ei sobi? Näiteks stringe ja arve. Pythoni interpretaator aitab siin oma veateadetega osa vigu avastada.
  • Kas juhtub üle- või alatäitumist? Tulemus kaob muutuja väärtuse väiksuse tõttu, või läheb muutuja väärtus üle andmetüübi piiri. Pythonis on täisarvud küll pea piiramatu suurusega, kuid üldiselt tuleb sellega arvestada.
  • Kas toimub jagamist nulliga?
  • Kas muutujad on väljaspool sisulisi piire? Näiteks tõenäosus >1.
  • Kas tehete järjekord on õige? Kui vaja, tuleb õige tulemuse saamiseks kasutada sulge. Sulge võiks kasutada ka juhul, kui kahtled tehete õiges järjekorras.

Võrdluste vead

  • Kas võrreldakse erinevat tüüpi muutujaid? Enamasti ei ole mõtet võrrelda võrreldamatuid suurusi (nt "kas 2 on suurem kui porgand?")
  • Kas esialgne spetsifikatsioon | algoritm on võrdlustehetesse õigesti tõlgitud? Vead võivad kergesti tekkida, kui näiteks 'suurem' asemel 'suurem-võrdne' kasutatakse. Sõltuvalt andmetüübist ei pruugi ka "suurem" ja "mitte väiksem-võrdne" samaväärsed olla.
  • Kas võrdlustehete ja loogikatehete järjekord on esitatud õigesti? Kas oleks vaja kasutada lisaks veel sulge?
  • Kas loogikatehte tulemus sõltub kompilaatorist ja tema seadetest? Arvutamisel võidakse kasutada täielikku või osalist arvutamist ja sellest võib mõne muutuja väärtus sõltuda.
  • Kas võrreldakse ujukoma muutujat mingi täpse arvuga ja võrdumistehtega? Lähtudes ujukomaarvude kujutamise põhimõtetest ei ole nad tihti täpsed ja seetõttu ka kaks muutujat võrdsed.

Juhtstruktuuride vead

  • Kas programmi struktuuri määravad taanded (või sõltuvalt programmeerimiskeelest muud märgised) on õigetesse kohtadesse paigutatud? St kas tsüklite ja valikulausete piirid on õigesti märgitud ja vastavad vähemalt esialgsele algoritmile.
  • Kas tsükkel lõpetab töö? St kas tingimused või tsüklimuutujad on kirjeldatud nii, et tingimused True-st False-iks muutuda saavad?
  • Mis juhtub, kui tsükli tingimus on kohe False? See võib olla täiesti normaalne, kuid see ei tohiks edasist programmi käiku halvasti mõjutada (nt jätta mõned olulised muutujad algväärtustamata, funktsioonid välja kustumata vms). Normaalne ei ole ilmselt ka olukord, kus tsükli tingimus kunagi True olla ei saagi.
  • Kui for-tsükli muutuja alumine raja on suurem kui ülemine, kas siis tulemus vastab oodatule? St et tsükkel sel juhul täitmata jääb.

Sisendi ja väljundi vead

  • Kas faili atribuudid on õiged (lugemine, kirjutamine jms)?
  • Kas kõik vajalikud failid on õigel ajal avatud ja suletud?
  • Kas faili lõputingimus on failis antud korrektselt? Nt tühjad read tekstifaili lõpus võivad põhjustada täitmisaegse vea.
  • Kas veaolukorrad/katkestused on õigesti käsitletud? Näiteks reaktsioonid failide puudumisel, vigaste sisendite korral jms.
  • Kas väljastatavates tekstides on sisulisi või grammatilisi vigu?

Dünaamiline testimine[edit]

Dünaamiliseks testimiseks nimetatakse programmi täitmist / käivitamist eesmärgiga leida vigu. Selleks tehakse tavaliselt järgmised sammud:

  • Koostatakse sisendandmete komplektid (milleks kasutatakse mitmeid erinevaid meetodeid).
  • Leitakse kõigi sisendandmete komplektidele vastavad väljundi/tulemuste komplektid, mis on vastavad ülesande püstitusele.
  • Programm käivitatakse sisendandmetega. Kui väljund on selline nagu oodati, on programm selle testi läbinud. Kui väljund ei ole selline nagu oodati, on ilmselt programmis viga. Viga tuleb üles otsida, parandada ja programmi uuesti testida.

Järgnev loetelu on väga väike osa erinevatest dünaamilise testimise meetoditest. Põhjalikumat ülevaadet saab lugeda Kaisa Kaljuma seminaritööst.

  • Funktsionaalne testimine, ka musta kasti testimine (black box testing).
  • Testimine programmi teksti põhjal, ka valge kasti testimine (white box testing).
  • Testimine ekspertteadmiste põhjal, nn veaotsing.
  • Testimine juhuslike sisenditega.

Funktsionaalne testimine[edit]

Funktsionaalse testimise põhimõtted on järgmised:

  • Programmi vaadatakse kui musta kasti – me ei tea, mis seal sees on, me ei tea, millistest struktuuridest programm koosneb.
  • Testide koostamisel võetakse aluseks programmi spetsifikatsioon ehk ülesande kirjeldus, mitte programmi kood.
  • Oluline arusaamine on, et kõigi võimalike andmetega ei suudeta kunagi programmi kunagi testida - see ei oleks reaalsel võimalik.
  • Kaks olulisemat meetodit testandmete leidmiseks on
    • Ekvivalentsiklassid
    • Piirjuhud

Ekvivalentsiklassid[edit]

Ekvivalentsiklasside kasutamise põhiideeks on see, et sarnaste andmete puhul peaks programm ühte moodi käituma. Kui ühe andmetega tekib/ei teki viga, tekib/ei teki ka teisega. Sellest tekib küsimus: kuidas määrata sarnaseid andmeid? Andmed jaotatakse mingite tunnuste abil erinevat tüüpi ekvivalentsiklassidesse. Erinevad ekvivalentsiklasside tüübid võivad olla:

  • Järjestatud tõkestatud vahemik
  • Järjestatud tõkestamata vahemik
  • Sarnaselt käituvate andmete hulk
  • Erinevalt käituvate andmete hulk
  • Valik / tingimus (nt ei/ja)

Kui andmed on jaotatud klassidesse, siis nendest klassidest andmete valimine peaks toimuma järgmisel viisil:

  • Järjestatud tõkestatud vahemik kujutab endast sisendandmate hulka, kus andmed (nt täisarvud) on järjestatud. Andmete vahele on võimalik tõmmata konkreetsed piirid ja ühes piirkonnas peab programm andmetega ühtemoodi käituma, teises piirkonnas aga teistmoodi. Selliseid vahemikke võib olla järjest määratud ka mitu tükki. Vahemiku puhul tuleb valida testandmete komplektid vahemiku seest, vahemiku eest ja vahemiku tagant.
  • Järjestatud tõkestamata vahemik on eelmisega sarnane sisendandmete hulk, kuid näiteks üks ots võib nö lahti olla. Testandmed tuleb sel juhul võtta vahemiku seest ja vahemikust väljaspoolt.
  • Samamoodi käituvate elementide hulk on ekvivalentsiklass, mis koosneb samamoodi käituvatest andmetekomplektidest, kuid need andmed ei ole järjestatud. Selliseid üksteisega mittekattuvaid hulki on tavaliselt mitu tükki. Kui on määratud ekvivalentsiklass, siis tuleb valida testandmed hulga seest ja huljast väljaspoolt.
  • Erinevalt käituvate elementide hulga moodustavad sisendandmete komplektid, mida ei õnnestu mingite ühiste tunnuste abil gruppeerida. Tihti moodustavad sellise klassi erinevad erandid. Sellise klassi puhul tuleb testida programmi kõikide elementidega.
  • Tingimus tähendab valikut kahe erineva variandi vahel. Selle klassi puhul tuleb programmi testida mõlema variandiga (tingimus täidetud/täitmata)

Piirolukorrad[edit]

Piirolukorrad on seotud ekvivalentsiklassidega, kui klassi moodustavad järjestatud vahemikud. Piiridel esineb kõige rohkem vigu ning programmi käitumist piiridel olevate andmetega tuleb eraldi testida. Piiride kontrollimiseks koostatakse eraldi testid. Piirid võivad olla erinevat tüüpi ja andmete valimisel kehtivad järgmised soovitused:

  • Kui piir on ujukomaarv, siis tuleb testid valida sellelt piirilt, sellest veidi suuremad väärtused ja väiksemad väärtused. Ujukomaarvude puhul on võimatu määrata täpset arvu, seetõttu ka täpset piiri. Lähtuda saab vaid teatud täpsusest.
  • Kui ülemist (alumist) piiri pole antud (tõkestamata vahemik), tuleb testiks valida absoluutväätuselt väga suur positiivne või negatiivne arv.
  • Kui piiriks on täisarv N, tuleb testideks valida väärtused N, N-1, N+1 - st täpselt piiril olev arv, eelmine arv ja järgmine arv. Täisarvude puhul (erinevalt ujukomaarvudest) on alati võimalik määrata eelnev ja järgnev väärtus.
  • Kui sisendandmeteks on järjestamata hulgad, siis piirolukordi ei teki.

Näide ekvivalentsiklassidest ja piirolukordadest[edit]

Ülesanne on järgmine:

Tulumaksu saab palgalt arvutada mitmel erineval viisil. Vaatleme kahte võimalust.

a) Midagi sellist, mis praegu kehtib EV-s. Määratud on maksuvaba miinimum, nt 2000 kr. Kui kodanik saab palka kuni 2000 kr, siis maksu palgalt maha ei võeta. Suurema palga korral arvutatakse tulumaks tulumaksumäära järgi (nt 21%) sellelt palgalt, mis on üle 2000 kr.

b) Astmeline tulumaks. On määratud maksuvaba miinimum (nt 1200 kr kuus). Tulumaksu määr on aga erinev, sõltudes inimese palgast. Näiteks nii: kui inimene saab palka kuni 6000 kr, siis on maksumääraks 15%. Kui palk on üle 6000 kr ja kuni 16000 kr, siis on makumääraks 26%. Ning 16 000 kr suuremal sissetulekul on maksumääraks 33%.

Kasutaja sisestab oma palga suuruse ja programm annab vastuseks, kumb süsteem talle soodsam on. Lisaks annab programm teada, mitu krooni kodanik kätte saab ning mitu krooni võrreldes teise süsteemiga võidab.

Sisendandmeteks on palk.

Nö tavalise tulumaksu korral moodustuvad ekvivalentsiklassid palgad, millelt tulumaksu ei arvestata ja palgad, kus tulumaksu arvestatakse. Klassi tüübiks on järjestatud vahemik ja testid võiksid olla järgmised:

Klassid:

  • Tulumaksuvaba palk: 1000 kr
  • Palk tulumaksuga: 10000 kr

Piirolukorrad (eeldusel, et palk on määratud krooni täpsusega):

  • Piir kahe klassi vahel: 2000 kr (kas sellelt palgalt tuleb tulumaks arvutada või mitte?)
  • Eelmine ja järgmine palk: 1900 kr (tulumaksuta) ja 2001 kr (tulumaksuga)
  • Väga suur arv: sisuliselt poleks vaja kontrollida, kuid sõltuvalt keelest ja kasutatavast andmetüübist võib tekkida andmetes ületäitumine.
  • Alumised piirid: 0 krooni, negatiivne arv

Lisaks tuleb iga testi kohta välja arvutada oodatavad tulemused, milline on kättesaadav palk ja tulumaks. Kui programm annab õige vastuse palgaga 1000 kr, siis võib eeldada, et õige tulemus saadakse ka kõigi teiste tulumaksuvabade palkade korral.

Astmelise tulumaksu korral on ekvivalentsiklasse rohkem, mille määravad erinevad maksumäärad. Samuti lisanduvad piirolukorrad kõigi klasside piiridel. Siinkohal võiks harjutamiseks mõelda, millised need täpsemalt on - määrata konkreetsed palgad ja nendele vastavad oodatavad tulemused.

Testimine programmi teksti järgi[edit]

Testide koostamisel on aluseks programmi kood, täpsemalt programmistruktuur. Erinevad tehnikad testimisel on:

  • Lause adekvaatsuse kriteerium – testid tuleb koostada nii, et iga lause programmis peab vähemalt üks kord mingi testis olema töötanud.
  • Haru adekvaatsuse kriteerium – testid koostatakse eesmärgiga, et iga haru programmis (nt if-lause mõlemad pooled) peavad olema mingis testis vähemalt üks kord töötanud (ka tühjad harud).
  • Andmetepõhine testimine – testid määravad andmetüüpide ja -struktuuride piirid.
  • Tsüklite testimine – mitu tsüklit koos võivad põhjustada probleeme, teinekord on tark neid spetsiaalselt testida.

Valge kasti testimise kohta kohta võib vajadusel täpsemalt lugeda eelpool mainitud prof. Jaak Teppandi Tarkvara kvaliteedi ja standardite konspektist.

Veaotsing[edit]

Kogenud arendaja oskab vea kohti ette aimata. Tõenäoliste vigade leidmine võib sõltuda mitmesugustest eelteadmistest, milleks on

  • üldised teadmised
  • teadmised konkreetse rakendusvaldkonna kohta
  • teadmised riist- või tarkvarakeskkonna (näiteks konkreetse programmeerimiskeele) kohta
  • teadmised arendusmetoodika kohta
  • teadmised konkreetse arendaja või tellija kohta jne

Juhuslikud sisendid[edit]

Juhuslikud sisendid on täpselt see, mida nimi ütleb. Ja tavaliselt nii alguses tehakse – kuidas Sa oma programme testid, kui ülesandega pole teste kaasa antu? Trükitakse sisendiks suvalised arvud, vaadatakse, kas programm annab mingi vastuse. Kui annab, ollakse õnnelik. Sellist metoodikat on püütud välistada, kuid teatud juhtudel osutub ta üsnagi praktiliseks.

Näiteks on juhuslike sisendite kasutamine väga efektiivne koormustestidel, mille eesmärgiks on näha, kuidas programm tuleb toime suuremate andmehulkadega. Selleks peab siiski teada olema, millised valdavalt andmed on ja genereerida siis sobivad andmehulgad. Andmed genereeritakse juhuslikult. Kindlasti ei sobi see meetod ainukeseks testimismeetodiks.

Kokkuvõte[edit]

Ühtegi kirjeldatud meetodit ei tohiks kasutata ainukesena. Igal meetodil on oma plussid ja miinused. Näiteks:

  • musta kasti meetodil ei saa leida harusid programmis, kuhu programmi täitmine kunagi ei jõua
  • valge kasti meetodiga ei saa aga teada, kas üldse tehakse seda, mida tarvis on
  • Ükski testimismeetod ei anna täielikku kindlust ja garantiid, et vead on kõrvaldatud. Aga nagu juba alguses mainitud - testimisega ei saa tõestada, et programmis vigu ei ole.
  • Alati on hea, kui keegi teine (st mitte programmi koostaja) testid koostab ja testib, sest see on reeglina tulemuslikum.

Testimisest Pythoniga seonduvalt[edit]

Refereeritud raamatust "How to Think Like a (Python) Programmer"

Programmi kirjutamisel tuleks seada eesmärgiks: programm kompileerugu / käivitugu esimesel katsel! Eesmärgi saavutamiseks loe enne esimest käivitamist programm läbi ning veendu:

  • ühelgi muutujal ei ole nimeks võtmesõna
  • juhtlause lõpus (if, while, for, def) on koolon
  • treppimine on järjekindel. Soovitav on kasutada ainult tühikuid või ainult tab-e. Taanded on ühe suurusega
  • kõigil stringidel on ja lõpusümbol
  • kõigil sulgudel on paarilised
  • võrdlemine toimub == ja mitte =-ga ja omistamine = ja mitte ==-ga

Täitmisaegsed vead

Täismisaegne viga ehk Run-time error on viga, mis avaldub programmi töö käigus.

  • Kui programm jääb "rippuma", st tundub, et ta mitte midagi ei tee, siis kahtlusta lõputut tsüklit. Selgust aitab saada aitab väljatrükk tsükli sees. Põhjuse väljaselgitamisel aitavad kaasa muutujate väärtuste uurimine tsükli sees.
  • Erindid, millega programm tihti oma töö lõpetab, on järgmised:
    • NameError – põhjuseks on mingi sõna kasutamine programmis, mis interpretaatorile tuttav ette ei tule. Selleks võivad olla eelnevalt kirjeldamata ehk algväärtustamata muutujad (või ka vead muutujanimes), funktsioonide kasutamine, mis paiknevad eraldi moodulites ning mida ei ole programmi imporditud ja lõpuks valesti kirjutatud keelesõnad.
    • TypeError – põhjuseks on ebasobiva andmetüübi kasutamine - näiteks pannakse aritmeetika või loogikatehtes arvutustes kokku erinevat tüüpi väärtused - stringud ja arvud.
    • IndexError – jadas kasutatakse liiga suurt või liiga väikest indeks, lühidalt piiridest väljajäävat indeksit.

Semantikavead

Semantikaviga ehk loogikaviga (semmantic error) on viga, mille tõttu programm teeb valesid asju (või teatud juhtudel valesid asju), leiab valesid tulemusi jms. Semantikavigu peab aitama avastada testimine.

Vea lokaliseerimiseks tuleb aru saada seosest oma programmi koodi ja arvuti käitumise vahel. Paremini aitab käitumist mõista programmi väljatrükkide lisamine ja programmi töö katsetamine samm haaval ja/või osade kaupa. Tavaliselt aitavad sellele kaasa programmeerimiskeskkonnaga kaasas olevad silumisvahendid (debugging).

Vigade tekkimist aitab vähendada neid paremini avastada järgmiste võtete kasutamine

  • Ära kirjuta liiga pikki avaldisi.
  • Jälgi tehete järjekorda, lisa sulge, kui kahtled.
  • Mõtle rahulikult järgi, võimaluse korral distantseeru, eriti kui:
    • Sa saad tigedaks,
    • Leiad, et arvuti vihkab Sind,
    • Hakkad programmi juhuslikul viisil täiendama ja teed programmi juhuslikke muudatusi mõttega, et äkki läheb juhuslikult korda.

Silumise teoreemid

  • Parem on teha vigu õppimise ajal ja meelega kui pärast ja kogemata – katseta, arvuti ei lähe katki.
  • Ainult üks asi on hullem veateatest: puuduv veateade – sisuline viga, mida ei märka.
  • Veendu, et kood, mida Sa vaatad on sama kood, mida Sa käivitad – programm võib olla avatud mitmes kohas/aknas või on jäänud salvestamata.
  • Pane tähele ja jäta meelde, et interpretaator annab veateade siis, kui ta vea avastab. St märgib programmis ära koha, kus ta vea leidis. Kuid see ei pruugi olla vea tekkekoht – tavaliselt tekib viga varem.
  • Aita end väljatrükkidega. Mõistlikul viisil ja mõistlikus kohas olevad print()-laused aitavad programmi käitumise loogikast paremini aru saada ja sel teel ka välja mõelda, mida ja mis moodi parandada.
  • Ning viimane ja kõige tähtsam: Ära tee juhuslikke muudatusi!

< 4. nädala teemad