Ohjelmistotestaus ja
siinä käytettävät työkalut

Ohjelmistotekniikan seminaariesitelmä
Tuomas Kautto
21.11.1996



    Sisällysluettelo

    1. Ohjelmistotestauksen lähtökohdat
      1.1 Ohjelman laatu
      1.2 Testauksen laatu
      1.3 Testauksen tarkoitus ja tavoitteet
    2. Testausprosessi
      2.1 Testauksen suunnittelu ja dokumentointi
      2.2 Testausvaiheet
      2.3 Virheenjäljitysprosessi (Debugging process)
    3. Staattinen analyysi
      3.1 Ohjelmamittoja
      3.2 Koodin tarkastelu ja läpikäynti (Inspections and walkthroughs)
    4. Dynaaminen analyysi
      4.1 Black box-testaus
      4.2 White box-testaus
      4.3 Top-down
      4.4 Bottom-up
    5. Ohjelmistotestauksen työkaluja
      5.1 Staattisen analyysin työkalut
      5.2 Dynaamisen analyysin / testauksen työkalut
      5.3 Testauksen hallinnan työkalut
      5.4 Testausohjelmistot ja -järjestelmät
    6. Esimerkkejä ohjelmistotestauksen työkaluista
      6.1 CTB, C Test Bed System
      6.2 CTC++, Test Coverage Analyzer for C/C++
      6.3 CMT++, Complexity Measures Tool for C/C++
      6.4 CodeGuard
    7. Lähteet

    Liite 1, CTC++ esimerkkiohjelma prime.c
    Liite 2, Esimerkki CTCPost-ohjelman tuottamasta profiiliraportista
    Liite 3, Esimerkki CMT++:n tuottamasta raportista
    Liite 4, Esimerkki CodeGuardin virheraportoinnista




1. Ohjelmistotestauksen lähtökohdat

Tänä päivänä ohjelmistojen laatuun kiinnitetään yhä enemmän huomiota. Asiakkaat ja käyttäjät vaativat ohjelmilta enemmän ja ovat laatutietoisempia kuin ennen. Kiristynyt kilpailu ja esimerkiksi ISO 9000 -sarjan laatujärjestelmien tulo ohjelmistomarkkinoille ovat osaltaan pakottaneet lisäämään panostusta myös ohjelmistotestaukseen. Muita syitä testauksen tarpeen kasvuun, ja sitä myötä testauksen automatisoinnin kasvuun, ovat olleet vaikeammin testattavien graafisten, tapahtumakeskeisten käyttöympäristöjen (Windows, X Window System, jne.) sovellusten yleistyminen, sovellusten tuottaminen yhä useampiin ympäristöihin sekä sovellusten siirtyminen ja hajautuminen yhä enemmän verkkoihin.

Toisaalta ohjelmistotestausta tukee se, että viimeisten arvioiden mukaan noin 40% kaikista ohjelmistoprojekteista epäonnistuu täydellisesti. Esimerkikisi Yhdysvalloissa menetettiin epäonnistuneissa sovelluskehitysprojekteissa vuonna 1995 yli 350 miljardia markkaa. Suomessa vastaavan luvun arvellaan olevan noin kaksi miljardia [TIE96]. Kaikkia projekteja ei ohjelmistotestauskaan kuitenkaan voi pelastaa. Useimmiten epäonnistumisien pääsyinä pidetään tavoitteen huonoa määrittelyä sekä puuttellista projektin hallintaa. Jos ohjelma jo alunperin määritellään huonosti tai jopa virheellisesti, todennäköisyys sille, että ohjelmaa koskaan tullaan käyttämään, on hyvin pieni.

1.1 Ohjelman laatu

Aina ohjelmistojen laadusta puhuttaessa, esille tulee termi SQA (Software quality assurance). SQA:n tarkoitus on nimensä mukaisesti varmistaa kehitettävän ohjelmiston laatua. SQA kattaa koko ohjelmistoprosessin aina määrittelystä tuotteen julkaisemiseen, käsittäen kaiken siihen liittyvän toiminnan kuten analyysit, dokumentoinnit, koodauksen, testausmenetelmät ja niin edelleen. Ohjelmistotestaus on vain yksi, mutta tärkeä osa SQA:ta.

Ohjelman laadulle asetetut vaatimukset vaihtelevat usein laajastikin ohjelman käyttötarkoituksen mukaan. Onhan selvää, että tapauksissa joissa ohjelmalta edellytetään esimerkiksi äärimmäistä turvallisuutta ja toimivuutta (eli ohjelma ei saa toimia "väärällä tavalla"), kuten esimerkiksi ydinvoimaloissa, lentokoneissa jne., ovat myös laatuvaatimukset ja testauksen tarve huomattavasti korkeammat kuin "tavallisissa" ohjelmissa.

Tyypillisiä hyvälaatuiselle ohjelmalle asetettuja vaatimuksia ovat mm.

    - ohjelman tulisi vastata tarkoitustaan ja määrittelyään (tulisi olla myös oikein määritelty),
    - olla helppokäyttöinen ja yksinkertainen,
    - olla helposti ylläpidettävä ja päivitettävä,
    - olla turvallinen (esim. tietoturvallinen) ja
    - olla mahdollisimman suorituskykyinen sekä virhe- ja rasitussietoinen.

1.2 Testauksen laatu

Testauksen laatua mitataan usein sillä, miten kattavasti testaus on suoritettu. Kaikista pienimpienkin ohjelmien täydellinen testaus on kuitenkin käytännössä mahdotonta. Otetaan esimerkiksi seuraava lyhyt ohjelman pätkä.

    i=0;
    do {
       switch (a) {
          case 1: ....
          case 2: ....
          case 3: ....
       }
       i++;
    } while (i < 20);
Tässä kaikkien mahdollisten eri ohjelmapolkujen lukumäärä on 320 (edellyttäen tietenkin, että muuttuja a saa arvoja 1-3 silmukan pyöriessä). Tämänkin pienen ohjelman, täydellinen, kaikkien ohjelmapolkujen läpikäynti vaatisi siis noin 3.5 * 109 (3,5 miljardia) testitapausta. Käytännössä ohjelmat koostuvat paljon suuremmista palasista, niitä on enemmän ja ne voivat olla vielä paljon monimutkaisempia.

Koska edellä esitetyn esimerkin mukaan täydellinen ohjelman läpikäynti tai testaus on usein mahdotonta (tai ainakin järjetöntä), olisi siis keskityttävä mahdollisimman kattavan, mutta järkevän, testauksen toteutukseen.

Riittävän kattavan testiaineiston valinta on aina hankalaa ja usein yhtä luovaa kuin itse ohjelman suunnittelu. Lisäksi testiaineiston valintaan liittyy yksi puhtaasti psykologinen ongelma. Yhden henkilön projekteissa tai projekteissa, joissa esim. puutteellisten resurssien takia on vähän työvoimaa, testaus suoritetaan usein ohjelmoijan tai ohjelmoijien toimesta. Itse tuottamansa koodin testaajalla on hyvin usein taipumus, ainakin alitajuisesti, valita ja johtaa testitapaukset liikaa "koodin mukaan" tai jopa vältellen mahdollisia virheenaiheuttajia. Tällöin testitapaukset harvemmin palvelevat varsinaista tarkoitustaan ja testaus on kaikkea muuta kuin kattava.

1.3 Testauksen tarkoitus ja tavoitteet

SQA:n kannalta testauksen tarkoitus on siis varmistaa ja parantaa kehitettävän ohjelmiston laatua. Toisaalta testauksen tarkoituksena on yksinkertaisesti ohjelmassa esiintyvien virheiden etsiminen ja korjaaminen. Mitä virheellä sitten tarkoitetaan? Yleisesti ottaen virheeksi voidaan käsittää mikä tahansa tila tai tapahtuma, joka poikkeaa odotetusta. Esimerkiksi ohjelmassa voi tapahtua jotain, mitä sen määrittelyn mukaan ei olisi pitänyt tapahtua. Toisaalta itse määrittely voi olla virheellinen, jolloin virhe tapahtuu, vaikka ohjelma toimisikin määritellysti.

Mitä myöhäisemmässä vaiheessa ohjelmistoprojektia virhe löydetään, sitä enemmän työtä sen korjaus teettää ja sitä kalliimmaksi se tulee. Yleisesti on esitetty, että jos virheen havaitseminen ja korjaaminen ohjelman suunnitteluvaiheessa tulee maksamaan yhden rahayksikön, tulee havaitseminen juuri ennen testausta maksamaan noin 6, testauksen aikana noin 15 ja ohjelman julkaisun jälkeen 60-100 rahayksikköä. Joissakin tapauksissa julkaisun jälkeen havaittu virhe voi itsessään aiheuttaa vielä paljon suurempia kustannuksia.

Koska ohjelmaa ei kuitenkaan voida testata täydellisesti, ei koskaan myöskään voida olla täysin varmoja ohjelman virheettömyydestä. Tästä syystä onkin haastavampaa ja järkevämpää asettaa testauksen tavoitteeksi, ohjelman virheettömyyden todistamisen sijaan, mahdollisimman monen virheen löytäminen ja korjaaminen. Käytännössä tämä edellyttää esimerkiksi testiaineiston valintaa siten, että testitapauksilla olisi mahdollisimman suuri todennäköisyys paljastaa mahdolliset virheet. Jotta virheet löydettäisiin mahdollisimman varhaisessa ohjelmistoprojektin vaiheessa, tulisi testausta tehostaa erityisesti matalampien tasojen testauksessa (ks. luku 2.2).



2. Testausprosessi

Lukuunottamatta joitakin pieniä ja yksinkertaisia ohjelmia, testaus tulisi käsittää kokonaisena prosessina ohjelmistokehityksen aikana. Testausprosessin suhdetta ohjelmistoprosessiin kuvataan usein seuraavanlaisella V-mallilla.

Kuva 2.1. Testausprosessi osana ohjelmistokehitystä.

2.1 Testauksen suunnittelu ja dokumentointi

Kuten mitä tahansa prosessia, testausprosessia on helpompi hallita, kun se on hyvin suunniteltu ja dokumentoitu. Suunnittelu säästää aikaa ja antaa selkeän kuvan suoritettavasta prosessista. Prosessin läpi kestävä dokumentointi ja raportointi puolestaan usein helpottaa projektissa mukana olevien ihmisten kommunikointia ja nopeuttaa vaiheista toisiin siirtymistä.

Testausprosessista ja sen vaiheista (ks. luku 2.2.) laaditaan omat testisuunnitelmansa. Testisuunnitelma pitää sisällään mm. lähestymistavan, resurssit, aikataulun, testattavat kohteet ja testitapaukset (usein omina suunnitelmina). Testaussuunnitelmat johdetaan kuvan 2.1. V-mallin mukaisesti. Pääsuunnitelma (master test plan) johdetaan yleensä ohjelman vaatimuksista (kuten alfa-testaussuunnitelmakin).

Testauksen raportointiin liittyy testauksesta laadittavien dokumenttien lisäksi tärkeänä osana ns. testilokit (test log). Testiloki sisältää kronologisen raportin suoritettujen testien yksityiskohdista. Useimmat testausohjelmat tuottavat lokit automaattisesti nauhoittaen tapahtumat omiin lokitiedostoihin.

Testausprosessin suunnittelu ja dokumentointi noudattaa pitkälle samoja periaatteita kuin yleensä ohjelmistoprosessin suunnittelu ja dokumentointi. Tarkempaa tietoa löytyy mm. ohjelmistoprosessia laajemmin käsittelevästä kirjallisuudesta [SOM95],[PRE92].

2.2 Testausvaiheet

Testausprosessi voidaan jakaa pienempiin ja paremmin hallittaviin osiin / vaiheisiin, joita kutsutaan myös tasoiksi (testing levels).

Kuva 2.2. Testauksen tasot (ks. kuva 2.1).

Jos testattava kohde ei läpäise jotakin testivaihetta, se palautetaan tasolle, jolla virhe on tapahtunut tai jolla virheen uskotaan olevan.. Tämän jälkeen virheet ja ristiriidat tarvittaessa paikannetaan, korjataan ja kohde testataan uudelleen. Usein puhutaan regressiotestauksesta, jolla tarkoitetaan, että kaikki virhettä edeltäneet testit suoritetaan uudelleen varmentaen, etteivät tehdyt korjaukset ja muutokset ole synnyttäneet uusia virheitä alemmille tasoille.

2.2.1 Moduulitestaus (Module testing)

Moduulitestaus (yksikkötestaus, unit testing) käsittää pienimpien ohjelmayksiköiden, esimerkiksi funktioiden, ja niistä muodostuvien pienten kokoelmien eli moduulien, testauksen. Moduulitestauksen tavoitteena on löytää selvien virheiden lisäksi ristiriitoja yksiköiden ja moduulien määrittelyjen ja toiminnan välillä sekä korjata ne.

Joissakin kirjallisuuslähteissä mm. [SOM95] yksikkö- ja moduulitestaustasot on esitetty omina kokonaisuuksinaan. Tällöin nämä yhdessä muodostavat ns. komponenttitestausvaiheen. Tässä moduulitestaus käsitetään omana kokonaisuutena ja ensimmäisen tason testausvaiheena.

2.2.2 Integraatiotestaus (Integration testing)

Integraatiotestauksella tarkoitettaan vaihetta, jossa pienemmät osat kootaan yhteen ja testataan, kunnes koko systeemi on saatu kasaan. Integraatiotestauksen tarkoituksena on varmistaa, että kasatut osat toimivat määritellysti keskenään, sekä löytää ja korjata mahdollisimman paljon yhteensoveltumattomia moduuleita.

2.2.3 Systeemitestaus (System testing)

Systeemitestauksen päätavoite on tutkia täyttääkö valmis systeemi (esim. ohjelmisto) sen määrittelyn asettamat vaatimukset. Toisaalta tavoitteena voidaan taas pitää sitä, että löydetään ja korjataan mahdollisimman paljon ristiriitoja systeemin ja sen määrittelyn väliltä. Joskus voi olla lisäksi tarpeellista testata esimerkiksi valmiin ohjelman suorituskykyä, rasituskestävyyttä, turvallisuutta ja toimivuutta verkossa.

2.2.4 Alfa-testaus (Acceptance / Alpha testing)

Alfa-testauksessa tutkitaan täyttääkö esimerkiksi tilaustyönä tehty ohjelmisto asiakkaan asettamat vaatimukset. Tätä vaihetta jatketaan, kunnes systeemin kehittäjä ja asiakas hyväksyvät tuotteen ominaisuudet.

2.2.5 Beta-testaus (Product / Beta testing)

Beta-testausta käytetään usein ennen kaupallisen tuotteen julkaisua. Tässä ohjelmistoa jaetaan tai toimitetaan potentiaalisille asiakkaille (tai joskus kaikille halukkaille), jotka sitten käyttävät ohjelmistoa ja raportoivat mahdollisista virheistä tai parannusehdotuksista tuotteen kehittäjälle. Beta-testauksen jälkeen (esimerkiksi joitakin kuukausia myöhemmin) ohjelmaa mahdollisesti muokataan ja julkaistaan uusi beta-versio tai lopullinen kaupallinen versio.

2.3 Virheenjäljitysprosessi (Debugging process)

Virheenjäljitykseksi (debugging) kutsutaan prosessia, jossa testitapahtumien paljastamia virheitä ja ongelmia yritetään paikantaa ja korjata. Debuggauksen tavoitteena on siis löytää havaittujen ongelmien ja virheiden aiheuttajat sekä korjata ne.

Virheen tai ongelman aiheuttajan paikantaminen ei aina ole helppoa. Mitä korkeammalla testaustasolla ollaan, sitä vaikeampaa todellisen aiheuttajan löytäminen käytännössä on. Tyypillinen esimerkki on tapaus, jossa ongelmasta saatetaan päästä eroon tilapäisesti, kun löydetäänkin (ja korjataan) jokin toinen virhe kuin alkuperäinen ongelman aiheuttaja. Toisaalta ongelma ei välttämättä johdu mistään selkeästä virheestä, vaan se voi aiheutua esimerkiksi tapahtuneesta pyöristysepätarkkuudesta.

Toinen ongelma, myös korkeammilla testaustasoilla, on se, että monimutkaisen virheen korjauksen seurauksena onkin aiheutettu uusi virhe. Käytännössä on osoittautunut, että hyvin suuren, lähellä kriittistä kokoaan olevan, ja monimutkaisen ohjelman korjaus aiheuttaa helposti useampia uusia virheitä.

Debuggauksen lähestymistapoina käytettään usein backtracking- tai cause elimination-metodeja. Backtracking-metodin mukaan koodia lähdetään käymään virheen esiintymiskohdasta taaksepäin, kunnes virhe löydetään. Cause elimination-metodin mukaan virheen aiheuttajaa jäljitetään sulkemalla pois "varmat tapaukset" (joiden ei uskota aiheuttaneen virhettä). Tätä jatketaan, kunnes oletetaan, että virhe sisältyy jäljelle jäänneeseen osaan. Tämän jälkeen virheen aiheuttajaa yritetään eristää mm. testaamalla jäljelle jäännyttä osaa sopivilla testitapauksilla.



3. Staattinen analyysi

Staattisessa analyysissä etsitään virheitä ja puuttellisuuksia tutkittavasta kohteesta ilman ohjelmakoodin suorittamista. Analysointi voidaan suorittaa joko käsin tai automaattisesti. Seuraavassa on lueteltu esimerkkejä siitä, minkä tyyppisiä virheitä staattisessa analyysissä voidaan paljastaa:

    - muuttujien väärinkäyttö (esim. käyttö ennen esittelyä, käyttämättömät muuttujat),
    - osoittimien ja indeksien väärinkäyttö (esim. viittaukset määriteltyjen rajojen ulkopuolelle),
    - parametrien väärinkäyttö (esim. parametrien väärä lukumäärä, parametrien väärä tyyppi),
    - kontrollivirheet (esim. saavuttamaton koodi) sekä
    - funktioiden väärinkäyttö (esim. kutsumattomat funktiot).
Suurin osa näistä virheistä voidaan paljastaa useimmiten ohjelman kääntämisen aikana. Kääntäjä onkin triviaali esimerkki staattisen analyysin työkaluista, joista kerrotaan enemmän luvussa 5.

3.1 Ohjelmamittoja

Staattiseen analyysiin liittyy myös joukko ns. ohjelmamittoja (software metrics), joilla kuvataan mm. ohjelman pituutta, kokoa ja vaikeusastetta. Näiden mittojen perusteella voidaan arvioida esimerkiksi ohjelman kompleksisuutta. Ohjelman kompleksisuuden määrittäminen voi olla hyödyllistä, sillä monimutkainen koodi on jo itsessään riskialtis virheille ja monimutkaisuuden kasvaessa myös ohjelman testattavuus vaikeutuu. Lisäksi monimutkainen koodi vaikeuttaa ohjelman ylläpitoa ja päivitystä.

Tässä esitellyt ohjelmamitat eivät sinällään anna mitään konkreettista tai varmaa tietoa siitä, että analysoitava ohjelma olisi virheellinen. Ohjelmamittojen avulla voidaan kuitenkin ennustaa, missä mahdollisia ongelmia voi esiintyä.

3.1.1 Halsteadin ohjelmamittoja

Seuraavassa on lueteltu paljon käytettyjä Maurice H. Halsteadin määrittelemiä ohjelmaa kuvaavia mittoja (tarkempaa tietoa [HAL77]).

    Kaavoissa esiintyviä muuttujia ovat

    n1 ohjelmassa esiintyvien eri operaattoreiden lukumäärä,
    n2 ohjelmassa esiintyvien eri operandien lukumäärä,
    N1 ohjelmassa esiintyvien operaattoreiden kokonaislukumäärä sekä
    N2 ohjelmassa esiintyvien operandien kokonaislukumäärä

Ohjelman pituus (program length),

    N = N1 + N2

saadaan laskemalla esiintyvien operaattoreiden ja operandien kokonaislukumäärä yhteen.

Ohjelman koko (program volume),

    V = N log2 (n1 + n2)

kuvaa ohjelman informaatiosisältöä lukuarvona.

Ohjelman vaikeusaste / virhealttius (difficulty level / error propeness),

    D = (n1 / 2) * (N2 / n2)

sen herkkyydestä / alttiudesta virheisiin.

Ohjelman taso (program level),

    L = 1 / D

on käänteinen ohjelman virhealttiuteen eli toisin sanoen matalamman tason ohjelma on virhealttiimpi kuin korkean tason ohjelma.

Halstead on havainnut, että ohjelman ymmärtämiseen kuluvan ajan (time to understand a program),

    T = V * D / 18

arvio sekunteina saadaan, kun ohjelman koko kerrotaan ohjelman vaikeusasteella ja jaetaan luvulla 18.

Virhe-ennuste (bug prediction),

    B = V / 3000

kertoo Halsteadin mukaan ohjelmassa esiintyvien virheiden lukumäärän ennusteen.

3.1.2 McCaben syklomaattinen luku (McCabe's cyclomatic number)

McCaben "syklomaattinen" luku (cyclomatic number) on ohjelmamitta, joka kertoo ohjelman loogisesta kompleksisuudesta. Esimerkiksi funktioille luku lasketaan usein seuraavanlaisesti (laskemiseen on monia muitakin vaihtoehtoja, joissa kuitenkin päädytään samaan lopputulokseen):

    v(G) = ehdollisten ohjelmahaarojen lkm + 1

G kuvastaa tässä graafia (flow graph), joka funktiosta voidaan johtaa tai piirtää (tai josta funktio on johdettu). Graafin tapauksessa ehdollisia ohjelmahaaroja kutsutaan joskus myös predikaattisolmuiksi (predicate node).

Käytännössä on havaittu että funktion tapauksessa v(G):n tulisi olla pienempi kuin 15. Tätä suuremman arvon omaavia funktioita on usein vaikea hahmottaa ja testata.

3.2 Koodin tarkastelu ja läpikäynti (Inspections and walkthroughs)

Ohjelmakoodin tarkastelu "käsin" on havaittu varsin tehokkaaksi testausmenetelmäksi varsinkin, kun tiedetään, mitä etsiä. Useimmiten tällä tavoin voidaan paljastaa tyypillisiä ja tunnettuja virheitä, joita automaattisesti ei välttämättä havaita. Suuremmissa projekteissa koodin tarkastelu tapahtuu ryhmätyönä. Jokainen testaaja tutkii ja läpikäy (esimerkiksi edeltäkäsin sovittujen testiaineistojen kanssa) koodin ensin itsekseen. Tämän jälkeen pidetään palaveri, jossa koodista sitten keskustellaan yhdessä. Koodin analysoinnissa ei keskitytä pelkästään tyypillisiin virheisiin, vaan usein kiinnitetään huomiota mm. ohjelmointityyliin.

Kokemusten perusteella on havaittu, että nämä metodit ovat tehokkaampia kuin perinteisemmät pöytätestausmenetelmät, joissa usein itse ohjelmoija kävi koodia läpi. Käytännössä näillä metodeilla voidaan löytää 30 - 70 % loogisista suunnittelu- ja koodausvirheistä.



4. Dynaaminen analyysi

Dynaamisessa analyysissä keskitytään ohjelman ja koodin käyttäytymiseen suorituksen aikana. Testaus edellyttää siis jonkinlaista toimivaa ohjelmaa tai ympäristön, missä esimerkiksi yksittäisiä funktioita voidaan suorittaa. Tällä tavoin saadaan testattua ominaisuuksia, mitkä staattisessa analyysissä olisivat mahdottomia tai ainakin vaikeita testata.

Seuraavassa on esitelty muutamia yleisimmin käytettyjä dynaamisen analyysin testausstrategioita. Valittava testausstrategia riippuu hyvin pitkälle testattavasta kohteesta, sen määrittelystä sekä testausvaiheesta. Nämä strategiat eivät myöskään ole toisiaan poissulkevia, vaan niitä voidaan käyttää ja usein käytetään rinnakkain täydentämässä toisiaan.

4.1 Black box-testaus

Black box-testaus perustuu testattavan systeemin (esim. ohjelma) tai komponentin (esim. funktio) input-output käyttäytymiseen. Black box-testauksessa ei välitetä testattavan kohteen rakenteesta tai sisällöstä, vaan tutkittavana ovat kohteen tulosteet (output) erilaisilla syötearvoilla (input). Testaajalle kohde on siis jokin tuntematon "musta laatikko", black box. Testattavan kohteen oikeellisuutta tarkastellaan vertaamalla saatuja tulosteita haluttuihin tai odotettuihin tulosteisiin. Testitapaukset johdetaan aina kohteen määrittelyn perusteella.

Kuva 4.1. Black box testaus.

Seuraavassa on lueteltu muutamia black box-metodeja, joiden mukaan testiaineisto (syötteet) voidaan valita.

4.1.1 Testitapausten jako ekvivalenssiluokkiin (Equivalence partitioning)

Jotta testitapausten lukumäärä saataisiin mahdollisimman pieneksi, mutta silti mahdollisimman kattaviksi, on yksi mahdollisuus jakaa syötejoukko ns. ekvivalenssiluokkiin. Testattavan kohteen määrittelyn perusteella voidaan nähdä, mitkä syötteet vaikuttavat kohteeseen samalla tavalla (ja tuottavat samanlaisia tulosteita). Nämä syötteet muodostavat oman ekvivalenssiluokkansa. Ekvivalenssiluokat voidaan vielä jakaa "oikeisiin" (valid) sekä "vääriin" (invalid). Tämän jälkeen syötteet eli testitapaukset tulisi valita kattavasti sekä oikeista että vääristä ekvivalenssiluokista. Esimerkkinä oikeista ja vääristä ekvivalenssiluokista voidaan ajatella ohjelmaa, joka kysyy: "Oletko varma (K/E) ?". Tällöin oikeat ekvivalenssiluokat muodostuisivat kirjaimista K ja k, sekä kirjaimista E ja e. Väärän ekvivalenssiluokan puolestaan muodostaisivat kaikki muut mahdolliset syötteet.

4.1.2 Reuna-arvoanalyysi (Boundary value analysis, BVA)

Reuna-arvoanalyysissä testitapauksiksi valitaan nimensä mukaan rajoilla (reunoilla) olevia (äärimmäisiä) arvoja. Nämä arvot voivat olla esimerkiksi mahdollisimman suuria tai pieniä, pitkiä tai lyhyitä ja niin edelleen. Reuna-arvoanalyysi perustuu olettamukseen, että jos testattava kohde epäonnistuu joillakin rajojen arvoilla, se yleensä epäonnistuu myös arvoilla, jotka kuuluvat rajojen sisään. Tällä tavoin saadaan taas testitapausten lukumäärää pienennettyä.

4.1.3 Virheen arvaus (Error guessing)

Testaajalla on usein jo kokemusperäistä tietoa erityyppisistä ongelmia aiheuttavista syötteistä, jotka eivät sisälly esimerkiksi edellämainittujen metodien antamiin tai ehdottamiin syötteisiin. Esimerkiksi 0 on syöte, joka ei välttämättä kuulu BVA:n rajoihin, mutta on aiheuttanut ja aiheuttaa virheellisiä käsittelyjä useissa ohjelmissa. Virheen arvaus-metodi perustuukin siihen, että listataan mahdolliset mieleentulevat testitapaukset, jolla testattava kohde saadaan epäonnistumaan. Tämän jälkeen suoritetaan testiajot näillä syötteillä. Tämä ei itsessään ole kovinkaan kattava black box-testausmetodi, mutta se on hyvä lisä muille metodeille.

4.1.4 Sattumanvarainen testaus (Random testing)

Yksi tapa (joskin usein huono sellainen) on valita syötejoukosta umpimähkään tietty määrä syötteitä ja suorittaa testiajot näillä. Tällä tavoin, varsinkin valitun syötejoukon ollessa suhteellisen pienen, ei voida saada varmuutta siitä, että kohde tuli edes kohtuullisesti testattua.

4.2 White box-testaus

Päinvastoin kuin black box-testauksessa, jossa testitapaukset johdettiin kohteen määrittelystä, white box-testauksessa testitapaukset johdetaan kohteen, esimerkiksi koko ohjelman sisäisestä rakenteesta ja logiikasta. Tavoitteena on valita testitapaukset siten, että kaikki kohteen (esimerkiksi ohjelman tai funktion) haarat ja ohjelmapolut tulisi käytyä läpi.

Kuva 4.2. White box-testaus.

Seuraavassa on lueteltu muutamia white box-metodeja, joiden kriteerien mukaan testiaineisto eli syötteet voitaisiin valita.

4.2.1 Lauseiden kattavuus (Statement coverage)

Lauseiden kattavuus-metodin mukaan testitapausten tulisi kattaa (suorittaa ainakin kerran) kaikki ohjelman lauseet. Käytännössä tämä on heikko kriteeri, sillä testitapauksia tulisi usein aivan liian suuri määrä.

4.2.2 Haarojen kattavuus (Branch / Path coverage)

Haarojen kattavuus-metodin mukaan kaikki ohjelman haarojen vaihtoehdot tulisi pystyä suorittamaan valituilla testitapauksilla. Haaralla (branch) tarkoitetaan tässä ohjelman kohtaa, jossa on kaksi tai useampia vaihtoehtoisesti suoritettavia lauseita (if-else, switch-case jne.).

4.2.3 Päätösten ja ehtokombinaatioiden kattavuus

Päätösten kattavuus (Decision coverage) -metodin mukaan testitapaukset tulisi valita siten, että kaikki ehdon sisältävät lauseet (esim. if-lause) saisivat kaikki mahdolliset tulokset (esim. TRUE ja FALSE). Tämä on itsessään melko heikko kriteeri, sillä useasti ehdolliset lauseet sisältävät tai peittävät toisia ehdollisia lauseita ja näin ollen kaikki mahdolliset kombinaatiot eivät tule välttämättä testatuiksi. Ehtokombinaatioiden kattavuus (Multiple-condition coverage) -metodi paikkaakin tämän puutteen, sillä sen mukaan testitapausten tulisi kattaa kaikki mahdolliset tuloskombinaatiot. Käytännössä kaikkien tuloskombinaatioiden läpikäynti on kuitenkin usein mahdotonta (ks. luku 1.2).

4.3 Top-down

Top-down-strategiassa testaus aloitetaan ohjelman korkeimman tason moduuleista ja vasta sitten edetään alempien tasojen moduulien ja komponenttien testaukseen. Jotta testattavaa moduulia alempien tasojen moduuleja voitaisiin simuloida, tarvitaan ns. "tyhmiä" moduuleita tai moduulin pätkiä (module stubs, stubs). Nämä stub-moduulit ovat hyvin yksinkertaisia, eivätkä sisällä mitään monimutkaisempaa toiminnallisuutta. Kun testaus etenee alemmalle tasolle, stub-moduulit korvataan oikeilla moduuleilla.

Kuva 4.3. Top-down-testaus.

Top-down-testauksen etuja ovat varhaisessa vaiheessa saatava alustava, toimiva ohjelma (jo psykologisestikin kannustavaa suunnittelussa mukana oleville) sekä kahden mahdollisesti erillisen testivaiheen, integraatio- ja systeemitestauksen, yhdistyminen.

Suurin haittatekijä on "ylimääräisten" stub-moduulien luominen, joka vie aikaa. Lisäksi testiaineiston valinta voi tuottaa vaikeuksia (esim. monimutkaisten moduulien stub-versioissa, jotka eivät välttämättä kuvaa täysin oikean moduulin käytöstä) ennenkuin oikeat moduulit on lisätty.

4.4 Bottom-up

Bottom-up-strategiassa testaus aloitetaan alimman tason moduuleista (moduuleista, jotka eivät käytä tai kutsu muita moduuleja). Testaus etenee ylemmille tasoille, kunnes koko ohjelma on testattu. Kuten top-down-testauksessa jouduttiin luomaan stub-moduuleita, bottom-up testauksessa joudutaan luomaan ns. testiajureita (test drivers), joilla alemman tason komponentteja voidaan suorittaa ja testata. Käytännössä useamman testiajurin sijaan, luodaan yleensä testauksen mahdollistava ympäristö, joka toimii kaikkien moduulien yhteisenä testiajurina (ks. luku 5.2).

Kuva 4.4. Bottom-up-testaus.

Bottom-up-strategian etuna on testiaineiston kohtalaisen helppo valinta. Myös siinä tapauksessa, että kriittisimmät tai virhealtteimmat moduulit sijoittuvat alemmille tasoille, on bottom-up testaus hyvä valinta.

Suurin haitta bottom-up-testauksessa on se, että mitään valmista ohjelmaa ei ole olemassa ennenkuin viimeinenkin ylimmän tason moduuli on testattu. Testausta ei myöskään voida aloittaa ennen kuin alimmankin tason moduulit on suunniteltu ja määritelty.



5. Ohjelmistotestauksen työkaluja

Kuten ohjelmistotestaus edellä, ohjelmistotestauksessa käytettävät työkalutkin jaetaan usein karkeasti sen mukaan, suoritetaanko testattavaa kohdetta testauksen aikana vai ei. Testauksessa käytetyt automatisoidut apuvälineet luokitellaan usein siis staattisen analyysin ja dynaamisen analyysin ja testauksen työkaluihin. Kolmannen luokan muodostaa testauksen hallinnan työkalut.

5.1 Staattisen analyysin työkalut

Staattisen analyysin työkalujen tarkoitus on löytää virheitä ohjelman koodista ilman minkäänlaista ohjelman suoritusta. Triviaali esimerkki staattisen analyysin apuvälineestä on kääntäjä, joka koodin kääntämisen yhteydessä ilmoittaa useista koodista havaituista virheistä.

Staattisia analysoijia käytetään yleisimmin ohjelmamittojen laskemiseen ja ohjelman rakenteen tarkempaan analysointiin. Kuten jo edellä (ks. luku 3.1) mainittiin, ohjelmamitoista voidaan päätellä ja ennustaa koodin mahdollisia ongelmakohtia sekä analysoida ohjelman kompleksisuutta.

Esimerkkeinä staattisen analyysin työkaluista mainittakoon CMT++ (ks. luku 6.3), Testwell Oy; QA C/C++/Fortran, Programming Research LTD; PC-Metric, SET Laboratories, Inc.

5.2 Dynaamisen analyysin / testauksen työkalut

Jotta suoritusaikainen testaus ja sen analysointi olisi mahdollista, tarvitaan ohjelman pohjalle jonkinlainen ympäristö, joka mahdollistaa tulosten havainnoinnin. Tällainen ympäristö muodostetaan yleensä testiajureiden (test drivers) avulla. Testiajuri voi olla joko itsenäinen ohjelma tai testattavaan ohjelmaan yhdistetty (itsenäinen) osa.

Usein testiajureiden avulla voidaan myös nauhoittaa erityyppistä informaatiota testausta suoritettaessa. Nauhoitukset suoritetaan yleensä erillisiin lokitiedostoihin, joista haluttua informaatiota sitten voidaan tarkastella. Yhä useammin, varsinkin graafisten käyttöliittymien tapauksessa, testiajureilla saadaan myös tietoa testattavan ohjelman vaikutuksesta ympäristöönsä, esimerkiksi tietoa ohjelman muistin ja resurssien käytöstä.

Itse testaus suoritetaan yleensä joko käsin (koko ajan syöttäen testitapahtumia) tai luodulla testiskriptillä (test script), joka suorittaa tai syöttää testattavalle ohjelmalle siinä luetellut testitapahtumat. Testiskriptien etuna on yhtäläisen testauksen nopeus ja helppous toistettaessa jo aikaisemmin suoritettuja testejä esimerkiksi regressiotestauksessa.

Seuraavassa on lueteltu joitakin tyypillisiä, tiettyyn tehtävään erikoistuneita dynaamisen testauksen työkaluja. Simulaattoreilla (simulators, test beds) tarkoitetaan työkaluja, joilla simuloidaan (väliaikainen) suoritusympäristö esimerkiksi ohjelman osille ja funktioille, joita sinällään ei voida suorittaa. Esimerkkinä CTB (ks. luku 6.1), Teswell Oy.

Testauksen kattavuutta ja testausta analysoivat (test coverage analysis tools) työkalut antavat tietoa testauksen laadusta (kattavuudesta). Niiden avulla pidetään kirjaa jo testatuista ja testaamattomista ohjelman osista. Esimerkkeinä CTC++ (ks. luku 6.2), Testwell Oy; QC/Coverage, CenterLine Software; C-Cover, Bullseye Testing Technology; PureCoverage, Pure Atria.

Suorituskykyä ja rasitussietoisuutta analysoivilla (performance & load / stress testing tools) työkaluilla voidaan tarkastella esimerkiksi ohjelman tehokkuutta ja simuloida useiden käyttäjien ympäristöjä. Esimerkkeinä SQA LoadTest, SQA, Inc.; PurePerformix, Pure Atria.

Dynaamisen analyysin työkaluihin luetaan kuuluvaksi usein myös testiaineistogeneraattorit (test data generators). Näiden työkalujen tarkoituksena on siis automatisoida testiaineiston luontia. Testitapahtumat johdetaan usein suoraan ohjelman koodista saadun informaation perusteella tai käyttämällä hyväksi valmiita tietokantoja. Esimerkkeinä PiSCES Test Case Generator, Reliable Software Technologies; STW/Advisor, Software Research; TESTBytes, InfoStructures, Inc.

5.3 Testauksen hallinnan työkalut

Testauksen hallinnan työkalut ovat kehitetty automatisoimaan testauksen suunnittelua, analysointia, dokumentointia ja raportointia. Markkinoilla on tarjolla ohjelmia ja ohjelmistoja, joilla luodaan oma ympäristökokonaisuus testauksen hallintaan. Tyypilliset hallintaympäristöt helpottavat testauksen organisointia, analysoivat testituloksia, tekevät yhteenvetoraportteja, lähettävät yhteenvetoja prosessissa mukanaoleville. Esimerkkeinä mainittakoon SQA Manager, Software Quality Automation Inc.; STEPMaster, Software Quality Engineering; TestDirector, Mercury Interactive; T-Plan, Software Quality Assurance Ltd.; QA Plan, Direct Technology; PureTestExpert, Pure Atria.

5.4 Testausohjelmistot ja -järjestelmät

Ohjelmistotestaukseen on kasvavassa määrin tarjolla suurempia testausohjelmistokokonaisuuksia. Testausjärjestelmät kattavat usein lähes kaiken toiminnan aina suunnittelusta projektin loppuun saakka. Suurempien testausjärjestelmien etuna on mm. eri testaus- ja prosessin vaiheiden saumattomuus, prosessin hallinnan helppous, dokumentoinnin yhtenäisyys ja useimmin monen käyttäjän (verkko)tuki.

Suurin haittatekijä suuremmissa testausjärjestelmissä on ainakin tällä hetkellä niiden hinta (hintahaitari tosin on hyvin laaja). Halvimmillaan useamman käyttäjän lisenssien hinnat pyörivät ominaisuuksista (ohjelmista) riippuen kymmenentuhannen ja sadantuhannen markan välissä. Toisaalta kalleimmillaan yhden käyttäjän lisenssi saattaa maksaa lähes satatuhatta markkaa.

Tämän hetken tunnetuin ja kehutuin testausjärjestelmä Windows-sovelluksille (3.x, 95 ja NT) on Software Quality Automation Inc.:in (SQA Inc.) SQA Suite 5.0. SQA Suite tukee useimpia olio-orientointuneita ohjelmointiympäristöjä sekä verkkokäyttöä. Paketti sisältää testiajurin SQA Robotin, testauksen hallintaohjelman SQA Managerin sekä rasitustestausohjelman SQA LoadTestin. Lisäksi esimerkkeinä testausjärjestelmistä voidaan mainita Direct Technologyn Automator QACenter (Windows-versiot) sekä AZOR Inc.:in Ferret (mm. Windows, DOS, OS/2, VMS, Nextstep, Unix).



6. Esimerkkejä ohjelmistotestauksen työkaluista

Tässä luvussa lyhyesti esiteltävät työkalut CTB, CTC++ ja CMT++ ovat tamperelaisen Testwell Oy:n testausvälineitä C- ja C++-kielisille ohjelmille. CodeGuard (16/32) on puolestaan Borland C++ 5.01:n mukana toimitettava testausapuväline.

6.1 CTB, C Test Bed System

CTB on moduulitestaustyökalu C-kielelle MS-DOSiin (saatavilla myös ainakin muutamiin UNIX-ympäristöihin). Koska C-kielistä moduulia (esimerkiksi yksittäinen funktio) ei itsessään voida suorittaa (eli se ei ole dynaamisesti testattava) on moduuleille luotava jonkinlainen ympäristö, missä testaus tulee mahdolliseksi. CTB:llä voidaan generoida tällainen moduulitestaus-ympäristö testattavalle koodille. Tätä ympäristöä voidaan kutsutaan nimellä test bed. Käytännössä voitaisiin tietenkin kirjoittaa moduuleille oma testiohjelmansa (toimisi siis testiympäristönä) ja debuggerin avulla käydä ohjelmaa esimerkiksi rivi riviltä läpi. CTB kuitenkin tekee tämän ympäristön testaajan puolesta ja antaa käyttöön lisäksi ominaisuuksia, joiden rakentaminen veisi aikaa itse testaukselta.

6.1.1 CTB:n ominaisuuksia

Parhaiten CTB tukee bottom-up black box-strategiaa. Tukea löytyy myös stub-moduuleille, joten top-down-strategian käyttö on mahdollista. Test bedin sisällä voidaan kutsua funktioita ja tutkia niiden palauttamia arvoja. Myös "omien" muuttujien (test bed variables) varaus on mahdollista test bedissä. Muutujia voidaan käyttää esimerkiksi funktioiden palautusten tutkimiseen ja vertailuun. Lisäksi test bed-ympäristössä voidaan ajaa valmiita testiskriptejä (test scripts, ks. luku 6.1.4).

6.1.2 CTB-generaattori

CTB-generaattorilla luodaan testattavista moduuleista (C-kielinen) testiajuri (test driver). Testiajuri puolestaan käännetään C-kääntäjällä CTB-kirjaston ja lopun moduulien tarvitseman C-koodin kanssa varsinaiseksi test bed-ohjelmaksi.

Kuva 6.1. CTB-generaattorin toiminta.

6.1.3 Test bed

Moduulien testausympäristöä kutsutaan siis nimellä test bed. CTB:ssä test bed on suoritettava ohjelma, jonka sisään testattavat moduulit on liitetty. Test bediä voidaan käyttää testaukseen interaktiivisesti komentotulkin tapaan tai/sekä valmiita testiskriptejä suorittaen. Test bed sisältää joukon käskyjä, joiden avulla voidaan mm. tarkastella yksittäisiä funktioita ja muuttujia (test bed variables) sekä suorittaa testattavia moduuleita (funktioita).

6.1.4 Testiskriptit (test scripts)

Testiskriptit koostuvat joukosta komentoja, joita test bedissä halutaan suorittaa. Testiskriptit muodostavat siis eräänlaisia ohjelmia tai ohjelman pätkiä joiden avulla testattavien kohteiden input-output käyttäytymistä voidaan analysoida. Kuten jo edellä mainittiin, testiskriptit nopeuttavat testien uudelleen suoritusta esimerkiksi regressiotestauksessa sekä helpottavat testauksen dokumentointia.

6.2 CTC++, Test Coverage Analyzer for C/C++

CTC++ on suoritusaikaisen testauksen kattavuutta analysoiva työkalu C- ja C++-kielisille ohjelmille DOS- ja Windows-ympäristöissä. CTC++:lla saadaan siis tietoa siitä, mitä ohjelman tai funktion osia ohjelman suorituksen aikana on jo käyty tai on käymättä läpi. Lisäksi CTC++:lla on mahdollista paikantaa ohjelman "pullonkauloja" eli esimerkiksi funktioiden ylisuuria suoritusaikoja.

6.2.1 CTC++:n osat ja ominaisuuksia

CTC++ koostuu CTC++-esikääntäjästä, itse testattavasta ohjelmasta sekä CTCPost-jälkikäsittelijästä. Esikääntäjä muokkaa testattavan C/C++-koodin ohjelman kääntämistä ja linkittämistä varten (ohjelman käyttäminen vaatii siis lisäksi C/C++-kääntäjän). CTCPost-ohjelmalla saadaan luotua raportit tuloksista. Sekä CTC++-esikääntäjää että CTCPostia voidaan käyttää joko suoraan komentorivikäskyillä tai interaktiivisesti ohjelman tarjotessa vaihtoehtoja.

6.2.2 CTC++-esikääntäjä

Testattavat kooditiedostot muokataan esikääntäjän avulla. CTC++-esikääntäjä muokkaa ja osittain generoi tiedostot, joista monitoriohjelma käännetään. Tuloksena on siis monitoriohjelma, joka toiminnaltaan vastaa "alkuperäistä" testattavaa ohjelmaa. Kuva 6.2 hahmottelee CTC++-esikääntäjän käyttöä.

Kuva 6.2. CTC++-esikääntäjän käyttö.

6.2.3 Monitori(ohjelma)

Kuten kuvasta 6.2 havaittiin, testattava ohjelma käännetään siis muokatusta (testattavasta) koodista, mahdollisesta testauksen ulkopuolelle jätettävästä koodista sekä valmiista CTC++:n mukana tulevasta (eri muistimalleille + Windowsille omat) kirjastotiedostosta. Testattavassa ohjelmassa voi olla osia tai funktioita, joita ei raportoida, eli monitori ei välttämättä käsitä koko testattavaa ohjelmaa. Suoritettaessa testiajoja monitori pitää kirjaa mm. läpikäydyistä funktioista ja funktioiden palautuksista erilliseen datatiedostoon (kuva 6.3). Datatiedosto myös päivittyy aina, kun ohjelmaa ajetaan uudelleen.

Kuva 6.3. Monitorin toiminta testattavan ohjelman suorituksen aikana.

6.2.4 CTCPost-jälkikäsittelijä

CTCPost-ohjelmalla voidaan tuottaa raportteja monitoriohjelman testiajoista. CTCPost kokoaa monitorin data- ja esikääntäjän symbolitiedostojen (.sym) tiedot taulukoiksi esimerkiksi funktioittain.

Kuva 6.4. CTCPost:n toiminta.

Käyttäjällä on mahdollisuus tuottaa mm. profiili- (profile), yhteenveto- (summary) ja "ajankäyttö"- (timing) raportit testiajon tai -ajojen aikana kerätyistä tiedoista. Profiiliraportti sisältää yksityiskohtaiset tiedot kaikista testattavan ohjelman funktioista ja siitä mitä funktion lauseita on jo käyty läpi. C++-ohjelmille saadaan lisäksi jokaiselle ohjelman luokalle Interface coverage-arvo (ks. luku 6.2.5). Yhteenvetoraportti sisältää tiivistelmän mm. funktioiden ja ohjelman TER-arvoista (ks. luku 6.2.5). Ajankäyttöraportista käy ilmi funktioiden kuluttamat ajat ohjelman suorituksen aikana.

6.2.5 Analyysin tulokset

Tuotettujen raporttien avulla käyttäjän on helppo nähdä, mitä funktioita tai funktiotasolla jopa mitä lauseita ei olla saavutettu suoritetuilla testitapauksilla. Ohjelma laskee kaikille funktioille ja koko ohjelmalle TER-arvon (Test Effectiveness Ratio). TER on prosenttiluku, joka kuvaa funktion testauksen kattavuutta. Se siis kuvaa sitä, kuinka suuri osa funktiosta on jo CTC++:n mittauskyvyn mukaan testattu tai käyty läpi. Mitä suurempi TER-arvo funktiolla tai ohjelmalla on, sitä kattavammin se on tullut käsiteltyä. C++-ohjelmien luokille ohjelmalla on mahdollista laskea nk. Interface coverage-arvo. Tämä arvo kuvastaa sitä, miten monta prosenttia luokalle kuuluvista ja periytyvistä funktioista on käyty läpi testiajon tai -ajojen aikana. Ajankäyttöraportit puolestaan antavat funktiokohtaista tietoa siitä, miten paljon aikaa on missäkin käytetty. Mitatut arvot ilmoitetaan oletuksena millisekunteina.

6.2.6 Esimerkki

Testattavana esimerkkinä on käytetty CTC++:n mukana tulevaa prime.c -esimerkkiohjelmaa (liite 1). Lähdekoodi siis muokataan ensin CTC++-esikääntäjällä, jonka antamat tiedostot (tässä tiedosto) käännetään ja linkitetään muiden tarvittavien tiedostojen kanssa valmiiksi ohjelmaksi (tässä mon.exe).

Seuraavassa valmiilla testiohjelmalla (monitorilla) mon.exe suoritetut testitapaukset:

    C:\CTOOLS\CTC> mon
    Enter a number: 1
    1 is not a prime.

    C:\CTOOLS\CTC> mon
    Enter a number: 5
    5 is a prime.

    C:\CTOOLS\CTC> mon
    Enter a number: 8
    8 is not a prime.

    C:\CTOOLS\CTC\> mon
    Enter a number: 401
    401 is a prime.

Testiajojen jälkeen CTCPost-ohjelmalla saadaan luotua monitorin datatiedostosta esimerkiksi liitteessä 2 esitelty profiiliraportti.

6.3 CMT++, Complexity Measures Tool for C/C++

CMT++ on nimensä mukaisesti C- ja C++-koodin kompleksisuuden mittaustyökalu MS-DOSiin (saatavilla myös muutamiin UNIX-ympäristöihin). Mittaustulokset perustuvat jo edellä esiteltyihin Halsteadin ja McCaben ohjelmamittoihin sekä ohjelman omiin lukuihin ohjelmarivien mitoista.

6.3.1 CMT++:n käyttö

Ohjelmaa voidaan käyttää joko suoraan komentorivikäskyillä tai interaktiivisesti ohjelman antamien vaihtoehtojen kautta. Käyttäjä voi valita joko lyhyen tiivistelmän tai tarkemmin eritellyn raportin analysoitavien tiedostojen koodista. Tarkempi erittely tulostaa kaikki laskettaviin tuloksiin tarvittavat muuttujat, esimerkiksi operaattorit, operandit ja niiden lukumäärät. Lyhyemmässä raportissa ohjelma kokoaa mittaustulokset taulukkoon, josta käy ilmi ne koodin kohdat, esimerkiksi funktiossa, joissa ilmeni rajojen ylityksiä. Nämä mittausrajat on mahdollista määritellä itse ohjelman INI-tiedostoon. Muutamia oletuksena olevia mittausvälejä on lueteltu luvussa 6.3.3.

6.3.2 Mittaustulokset

Ohjelmalla voidaan laskea C- ja C++ -tiedostoista mm. seuraavia tuloksia:

    v(G) McCaben syklomaattinen luku (McCabe's cyclomatic number),

    LOCcom kommentillisten rivien lukumäärä,
    LOCpro ohjelma(koodi)rivien lukumäärä,

    V koko (volume),
    B arvio virheiden lukumäärästä ohjelmassa sekä
    T arvio ajasta, joka menee funktion tai ohjelman ymmärtämiseen.

6.3.3 Mittaustulosten analysointi

Seuraavat hyväksyttävät mittausvälit perustuvat ohjelman mukana toimitettavaan manuaaliin.

Funktion pituuden tulisi olla 4 - 40 ohjelmariviä. Pidemmät funktiot voidaan yleensä jakaa pienempiin, joka parantaa luettavuutta. Yhden tiedoston pituuden tulisi olla alle 400 ohjelmariviä. Pitempien ymmärtäminen kokonaisuutena voi olla vaikeaa. Funktion v(G) tulisi olla alle 15. Yli 15 ohjelmapolkua sisältäviä funktioita on vaikea ymmärtää ja testata. Arvioitujen virheiden (B) lukumäärä tiedostossa tulisi olla alle 2

Kuten aiemmin ohjelmamittojen kohdalla todettiin, niin tulokset ovat luonnollisesti vain suuntaa antavia. Ne eivät siis anna mitään varmaa tietoa siitä, että ohjelma sisältää tai ei sisällä virheitä.

Testattavana esimerkkinä on käytetty liitteen 1 prime.c -ohjelmaa. Kyseisestä tiedostosta saatu tiivistetty raportti on esitetty liitteessä 3.

6.4 CodeGuard

CodeGuard 16/32 on Borland C++ 5.01 mukana toimitettava ohjelman suoritusaikaista käyttäytymistä analysoiva työkalu. CodeGuardin avulla voidaan havainnoida joitakin virheitä, joita kääntäjä ei ole havainnut. CodeGuard tukee Borlandin kääntäjillä (joissa CodeGuard -tuki) luotuja 16 ja 32-bittisiä Windows- ja EasyWin-sovelluksia.

6.4.1 Ominaisuuksia

CodeGuardin avulla voidaan paljastaa lähinnä muistin ja funktioiden käyttöön liittyviä virheitä. CodeGuard raportoi esimerkiksi muistin varauksiin, vapauttamisiin, viittauksiin ja kahvoihin liittyvistä väärinkäytöksistä sekä funktioiden palautuksiin liittyvistä virheistä ja ongelmista. CodeGuardin havaitessa virheen, se ilmoittaa (haluttaessa) virheestä omassa viesti-ikkunassa, sekä tulostaa havaitut väärinkäytökset omaan lokitiedostoon (.cgl). Virheiden raportointiin voi itse vaikuttaa CodeGuardin konfigurointiohjelman (CodeGuard Configuration Utily) avulla.

6.4.2 GodeGuardin käyttö

CodeGuard saadaan käyttöön halutulle sovellukselle kääntämällä tarkoitukseen soveltuva CodeGuard -kirjastotiedosto ko. sovelluksen kanssa. Yksinkertaisimmin tämä tapahtuu valitsemalla TargetExpertissä CodeGuard-optio päälle ennen kääntämistä. Tämän jälkeen CodeGuard toimii automaattisesti ohjelmaa suoritettaessa.

Liitteessä 4 on lyhyt esimerkki siitä, miten CodeGuard raportoi havaitsemistaan virheistä.



7. Lähteet

Borland International, Inc., CodeGuard (16/32) avustustiedosto (cg5.hlp), 1996

[HAL77] Maurice H. Halstead, Elements of Software Science, 1977

H&P Software Engineering Center Oy, Swengineering 3 / 1996

Richard A. DeMillo, W. Michael McCracken, R. J. Martin & John F. Passafiume, Software Testing and Evaluation, 1987

Petri Kuusela, Module Testing and Protocol Testing in Software Quality Assurance, http://www.to.icl.fi/~kuusela/mt

[PRE92] Roger S. Pressman, Software Engineering, A Practitioner's Approach, 3rd edition, 1992

SoFine, Suomalaisen ohjelmistoviennin kehitysohjelma, osa 8: Ohjelmistotestaus, http://www.sofine.hut.fi/vienti/ohjelmistovienti/osa8.html

[SOM95] Ian Sommerville, Software Engineering, 5th edition, 1995

ST Labs, Testing Tools Supplier List, http://www.stlabs.com/MARICK/faqs/tools.htm

Testwell Oy, CTB-, CTC++- ja CMT++ -manuaalit, 1995

[TIE96] Tietoviikko, "Sovellusprojekteissa tyritään miljardeja", numero 33, 1996



Liite 1, CTC++ esimerkkiohjelma prime.c


    #include <stdio.h>     /* C.f. CTC++ 4.0 manual Tutorial Example */
    #include <stdlib.h>
    
    int prime(unsigned u)
    {
       unsigned divisor;
    
       if (u == 1)
          return 0;
       if (u == 2)
          return 1;
       if (u % 2 == 0)
          return 0;
       for (divisor = 3; divisor < u / 2;
    	divisor += 2)
       {
          if (u % divisor == 0)
    	 return 0;
       }
       return 1;
    }
    
    int main (void)
    {
       unsigned prime_candidate;
    
       printf("Enter a number: ");
       scanf("%u", &prime_candidate);
       if (prime(prime_candidate))
          printf("%u is a prime.\n", prime_candidate);
       else
          printf("%u is not a prime.\n",
    	     prime_candidate);
    
       return 0;
    }
    




Liite 2, Esimerkki CTCPost-ohjelman tuottamasta profiiliraportista

(Käytetyt testitapahtumat on esitetty kohdassa 6.2.6)
    ****************************************************************************
    *                        CTC++ System, Version 4.0                         *
    *                     Test Coverage Analyzer for C/C++                     *
    *                                                                          *
    *                         EXECUTION PROFILE LISTING                        *
    *                                                                          *
    *                     Copyright (c) 1993-1995 Testwell Oy                  *
    *     Base Technology Copyright (c) 1988-1992 ICL Personal Systems Oy      *
    ****************************************************************************
    
    
    Monitor name          : MON
    Monitor generated at  : Tue Nov 05 12:43:08 1996
    Listing produced at   : Tue Nov 05 12:45:24 1996
    
    
    
    MONITORED SOURCE FILE : prime.c
    INSTRUMENTATION MODE  : function-decision
    
        START/       END/
          TRUE      FALSE    LINE DESCRIPTION
    ============================================================================
    
    	 4          0       5 FUNCTION prime()
    
    	 1          3       8 if (u == 1 )
    	 1                  9   return 0
    	 0          3 -    10 if (u == 2 )
    	 0            -    11   return 1
    	 1          2      12 if (u % 2 == 0 )
    	 1                 13   return 0
    	99          2      14 for (;divisor < u / 2 ;)
    	 0         99 -    17   if (u % divisor == 0 )
    	 0            -    18     return 0
    	 2                 20 return 1
    
    ***TER  75 % (12/16) of FUNCTION prime()
    ----------------------------------------------------------------------------
    
    	 4          0      24 FUNCTION main()
    
    	 2          2      29 if (prime ( prime_candidate ) )
    			   32 else
    	 4                 35 return 0
    
    ***TER 100 % (4/4) of FUNCTION main()
    ----------------------------------------------------------------------------
    
    
    ***TER  80 % (16/20) of SOURCE FILE prime.c
    ----------------------------------------------------------------------------
    
    
    
    
    
    SUMMARY
    =======
    
    Number of monitored source files  : 1
    Number of source lines            : 38
    Number of measurement points      : 16
    TER                               : 80%
    
    




Liite 3, Esimerkki CMT++:n tuottamasta raportista


    ****************************************************************************
    *                        CMT++ System, Version 1.6                         *
    *                    Complexity Measures Tool for C/C++                    *
    *                                                                          *
    *                       COMPLEXITY MEASURES REPORT                         *
    *                                                                          *
    *                    Copyright (c) 1993-1995 Testwell Oy                   *
    *    Base Technology Copyright (c) 1988-1992 ICL Personal Systems Oy       *
    ****************************************************************************
    
    This report was produced at Sun Nov 03 18:40:55 1996
    
    
    Measured source file: prime.c
    
    Measured object           v(G)  LOCpro  LOCcom          V        B         T
    ============================================================================
    prime()                      6      17       0     259.15     0.10  00:04:28
    main()                       2      12       0     171.30     0.03  00:00:55
    
    prime.c                      7      31       1-    523.19     0.16  00:09:59
    ============================================================================
    
    
    SUMMARY:
    
    Measure                          Alarmed   Measured     %          Limits
    
    ============================================================================
    Cyclomatic number of function :        0          2     0         1       15
    Cyclomatic number of file     :        0          1     0         1      100
    Executable lines in function  :        0          2     0         4       40
    Executable lines in file      :        0          1     0         4      400
    Comment ratio of file (%)     :        1          1   100        30       75
    Volume of function            :        0          2     0        20     1000
    Volume of file                :        0          1     0       100     8000
    Delivered bugs in file        :        0          1     0         0        2
    ============================================================================
    Total                         :        1         11     9
    
    




Liite 4, Esimerkki CodeGuardin virheraportoinnista

(Suoritettu ohjelman pätkä)
    // cg_esim.c
    // Järjetön esimerkkiohjelma CG:n virheraportointia varten
    #include<stdlib.h>
    
    int main(void)
    {
      int *a,*b;
      // varataan a:lle muistia ja vapautetaan samantien
      a=(int *) malloc(sizeof(int)*10);
      free(a);
      // varataan b:lle muistia
      b=(int *) malloc(sizeof(int)*2);
      // käytetään a:ta vaikka se on jo vapautettu
      if (a[9]) b[0]=0;
      // jätetään b vapauttamatta
      return 0;
    }
    

(CodeGuardin tekemä loki .cgl)
    Error 00001. 0x100630 (Thread 0xFFF9C569):
    Access in freed memory: Attempt to access 4 byte(s) at
    0x00B322A4+36.
    | cg_esim.c line 9:
    |   free(a);
    |   b=(int *) malloc(sizeof(int)*2);
    |>  if (a[9]) b[0]=0;
    |   return 0;
    | }
    Call Tree:
       0x004010B0(=CG_ESIM.EXE:0x01:0000B0) cg_esim.c#9
       0x00406C11(=CG_ESIM.EXE:0x01:005C11)
    
    The memory block (0x00B322A4) [size: 40 bytes] was allocated with
    malloc
    | cg_esim.c line 6:
    | {
    |   int *a,*b;
    |>  a=(int *) malloc(sizeof(int)*10);
    |   free(a);
    |   b=(int *) malloc(sizeof(int)*2);
    Call Tree:
       0x00401094(=CG_ESIM.EXE:0x01:000094) cg_esim.c#6
       0x00406C11(=CG_ESIM.EXE:0x01:005C11)
    
    The memory block (0x00B322A4) was freed with free
    | cg_esim.c line 7:
    |   int *a,*b;
    |   a=(int *) malloc(sizeof(int)*10);
    |>  free(a);
    |   b=(int *) malloc(sizeof(int)*2);
    |   if (a[9]) b[0]=0;
    Call Tree:
       0x0040109D(=CG_ESIM.EXE:0x01:00009D) cg_esim.c#7
       0x00406C11(=CG_ESIM.EXE:0x01:005C11)
    
    ------------------------------------------
    Error 00002. 0x300010 (Thread 0xFFF9C569):
    Resource leak: The memory block (0xB322D0) was never freed
    
    The memory block (0x00B322D0) [size: 8 bytes] was allocated with
    malloc
    | cg_esim.c line 8:
    |   a=(int *) malloc(sizeof(int)*10);
    |   free(a);
    |>  b=(int *) malloc(sizeof(int)*2);
    |   if (a[9]) b[0]=0;
    |   return 0;
    Call Tree:
       0x004010A5(=CG_ESIM.EXE:0x01:0000A5) cg_esim.c#8
       0x00406C11(=CG_ESIM.EXE:0x01:005C11)