Luku 2.2: Ohjelmistojen testausta

Tästä sivusta:

Pääkysymyksiä: Miksi ohjelmaa testataan? Kuinka ohjelman toimivuutta voi testata käsin ja automaattisesti?

Mitä käsitellään? Tutustutaan yksikkötestaukseen.

Mitä tehdään? Lähinnä luetaan

Suuntaa antava vaativuusarvio: Ei kovin haastava.

Suuntaa antava työläysarvio:? 1 tunti

Pistearvo: S10.

Oheisprojektit: ei ole

“Program testing can be used to show the presence of bugs, but never to show their absence!”

—Edsger Dijkstra

Ohjelman testauksessa tutkitaan ohjelman laatua ja ominaisuuksia empiirisesti. Keskeinen tarkasteltava kohde on ohjelman oikea toiminta ja olennaisin tehtävä on löytää virheitä ohjelmien toiminnassa, jotta nämä virheet voitaisiin korjata. Muita mahdollisia ominaisuuksia ovat esimerkiksi ohjelman suorituskyky ja käytettävyys, mutta emme käsittele niitä tässä sen enempää.

Koska vähänkin suuremmalla ohjelmalla on usein käytännössä rajaton määrä syötteitä ja mahdollisia sisäisiä tiloja, ei testaus kuitenkaan voi osoittaa että ohjelma olisi täysin virheetön. Testaus pyrkiikin osoittamaan että ohjelma toimii laajalti sen vaatimusmäärittelyn eli spesifikaation eli speksin (ei kuitenkaan tämän speksin.) mukaisesti ja kun se ei toimi, helpottamaan osoittamaan virheiden olemassaolon ja helpottamaan niiden paikantamista. Virhe eli bug tarkoittaa siis poikkeamaa spesifikaatiosta.

Hyvä ohjelmankehitys yleensä tuottaa varsinaisen lähdekoodin lisäksi kokoelman huolellisesti valittuja testitapauksia. Nämä testaavat ohjelmaa sekä perustapauksilla kuten kaikkein tyypillisimmillä syötteillä ja tyypillisimmissä käyttötilanteissa että spesifikaation rajoilla ja erikoistapauksissa, kuten virheellisten syötteiden kanssa.

Yksikkötestaus

Yksikkötestaus on ohjelmistojen testauksen menetelmä, jossa yksittäisten ohjelman osasten toimintaa testataan, mikäli mahdollista erillään muusta koodista, jotta voidaan todeta että se täyttää sille asetetut vaatimukset. Yksikkötestaus tehdään tyypillisesti käyttäen jotakin yksikkötestauskirjastoa, joka hoitaa testien suorittamisen ja tulosten koostamisen. Kirjastot yleensä myös toimivat yhteen IDE:jen kanssa, tehden testien ajamisesta ohjelmoinnin yhteydessä helppoa ja nopeaa. Suuremmissa projekteissa yksikkötestejä suoritetaan automaattisesti aina kun projektiin tuodaan uutta koodia. Yksikkötestauksen etuja ovatkin juuri kirjastojen tarjoama automaatio ja hyvän yksikkötestauksen tuoma varmuus testattujen luokkien toiminnasta.

Yksikkötesti

Yksikkötesti on käytännössä pikkuinen ohjelma, usein tietyllä tavalla nimetty tai annotoitu metodi, joka alkaa testattavan ohjelman osasen saattamisella jonkinlaiseen alkutilaan jossa testi halutaan tehdä. Jos testattaisiin vaikkapa elokuvatietokantaa jota kokeilimme syksyllä, olisi ohjelman käynnistäminen halutulla tekstitiedostolla tämäntyyppinen osa testiä. Yhtä lailla testin alku voi olla luokkien pystytttämistä niin että testattava koodi voidaan todella testata erillään. Tässä apuun tulevat edellisessä luvussa mainitut sijaisluokat.

Kun ohjelma on saatu aseteltua sopivaan tilaan, tehdään testattavat toimenpiteet, ja tarkistetaan tapahtuiko ohjelmassa oletettu muutos. Tämä tarkistus tehdään lähes joka yksikkötestauskirjatossa käyttäen metodeja tai makroja, joiden nimi on muotoa assertXYZ. Tällä kurssilla tulemme käyttämään testaukseen Python Standard Libraryn valmiita moduleita, jotka tarjoavat valmiita lähestymistapoja testaukseen, ja joista erityisesti käytämme unittest-modulia.

Tutustu sen dokumentaatioon, ennen kuin jatkat tästä eteenpäin.

“Testipeti”

Edellä luvussa 2.1 esiteltiin testauksen ja toteutuksen apuna käytettäviä apuluokkatyyppejä: Dummy, Stub ja Mock. Näiden käyttö oikeiden luokkien sijaan mahdollistaa sen, että testin aikana luokat joita ne esittävät toimivat tarkalleen speksin mukaisesti. Erityisen kätevää tämä on jos näitä luokkia ei ole vielä edes toteutettu, tai niiden toiminnalla on huomattavia sivuvaikutuksia ja riippuvuuksia. Mahdollisuus korvata vaikkapa tulostimeen yhteyttä pitävä luokka mahdollistaa testejä, joiden ajaminen automaattisesti olisi rasittavaa ja jopa kallista. Lisäksi tällä tavoin on helppo vaikkapa luoda erikoistilanne, jossa luomamme Mock esittää vaikkapa tilanteen jossa järjestelmän levytila on loppu, vastaamalla haluamallamme tavalla metodikutsuihin.

Seuraavassa luvuissa pääsemme kokeilemaan testausta ja mock:eja käytännössä.

Kattavuudesta

Testien laatimisessa ei pidä tyytyä vain muutamiin helpoiten mieleen tuleviin tapauksiin vaan toimia systemaattisesti ja kiinnittää huomiota testijoukon kattavuuteen. Kattavuutta voidaan arvioida useille erilaisilla koodiin ja sen suorittamiseen liittyvillä mitoilla, kuten:

  • funktiokattavuus: Onko kaikkia metodeja kutsuttu?
  • lausekattavuus tai rivikattavuus: Onko jokainen ohjelman lause tai rivi suoritettu?
  • haarakattavuus: Onko jokaisen ehtolauseen (tai vastaavan) jokainen haara suoritettu?
  • ehtokattavuus: Onko jokainen ehtolauseke evaluoitunut sekä todeksi että epätodeksi?
  • polkukattavuus: Onko jokainen mahdollinen ohjelman suorituspolku suoritettu? Tätä ei yleensä ole mahdollista saavuttaa.

Näiden kattavuusmittojen selvittämiseen on olemassa valmiita työkaluja, joita voidaan käyttää testauksessa ja jotka kertovat esimerkiksi, kuinka monta prosenttia ohjelman lauseista testit käyvät läpi ja mahdollisesti näyttävät lauseet, joita testit eivät käy läpi.

Valitettavasti näiden kattavuusmittojen 100% toteutuminen (lukuunottamatta polkukattavuutta, jota puolestaan ei yleensä voi saavuttaa testeissä) ei aina yksin riitä tuottamaan ohjelman toiminnan kannalta kattavaa testijoukkoa, kuten seuraavasta esimerkistä käy ilmi.

Allaolevassa esimerkissä esitellään metodi suhde, joka laskee kahden kokonaisluvun suhteen mahdollisimman tarkasti ja palauttaa sen mikäli mahdollista. Mieti miten koodin tulisi toimia normaaleissa ja epänormaaleissa tapauksissa?

#
# Palauta annetuista luvuista pienemmän suhde suurempaan, mikäli mahdollista.
#
def suhde(n, m):
    if n < m:
       return m/n
    else:
       return n/m

Jos testijoukkomme sisältää testit:

suhde(2,4) == 2.0

suhde(4,2) == 2.0

Saadaan 100% funktiokattavuus, lausekattavuus, haarakattavuus ja ehtokattavuus. Kuitenkin jotain oleellista on jäänyt huomaamatta; mitä?

Tehtävä: testikattavuus

Ratkaise tehtävä A+:ssa.

Kattavuutta pohdittaessa ei siten pidä tyytyä vain mainittujen kattavuusmittojen 100% saavuttamiseen (johon toki siihenkin pitää pyrkiä) vaan on tarkasteltava ohjelman speksiä ja pyrittävä löytämään sen avulla oleellisia testitapauksia.

Testaus ei ole aina helppoa ja suoraviivaista. Ohjelman speksi voi itsessään jo tehdä testauksesta hankalaa, mutta paljon riippuu myös ohjelman rakenteesta, esimerkiksi kuinka helppoa ohjelman osat on irroittaa toisistaan testiä varten. Monesti olion sisäinen tila riippuu suoritushistoriasta, joloin yksittäistä operaatiota testaava testi voi mennä läpi, mutta useamman operaation peräkkäinen suorittaminen epäonnistua. Esimerkiksi ohjelmaa käytettäessä havaitun virheen uudelleen toistavan testitapauksen luominen voi tällöin olla työlästä.

Rinnakkaisohjelmoinnissa suoritushistorian rooli korostuu vielä enemmän, virheet toistuvat satunnaisesti ja mahdollisesti hyvin harvoin. Tästä puhutaan lisää myöhemmin kurssilla.

Testausvinkkejä

Koodikattavuus ei siis takaa että koodi toimisi oikein, vaan sen että mahdollisesti rikkinäisen koodin joka rivillä käytiin. Testin lopputulos saattaa kuitenkin riippua vaikkapa ensimmäisestä rivistä, joka testi suoritti, vaikka kattavuusmitta näyttääkin sen suorittaneen kaikki rivit. Kuinka testitapauksia sitten kannttaisi valita? Esimerkkinä listan testaaminen:

  • Normaaliarvoilla
    • Esim. Laita listaan luvut 1-10 ja katso että luvut todella ovat siellä. (positiivinen testi)
    • Esim. Hae listasta jossa on kymmenen alkiota alkiota joka ei ole siellä ja katso että paluuarvo on false. (negatiivinen testi)
  • Sallituilla äärirajoilla
    • Esim. Poista listasta alkio kun siellä on vain yksi alkio. Kaiken pitäisi sujua poikkeuksetta.
  • Laittomilla äärirajoilla
    • Poista alkio kun lista on tyhjä.
    • Esim. Katso tuleeko koodilta speksin mukainen poikkeus. (käsitellään tarkemmin luvussa 15.1)

Muista myös että jos käytät satunnaisuutta kun luot testidataa, alusta satunnaislukugeneraattori itse vakioarvolla

  • Virhetilanteet voi tällöin toistaa ja debugata
  • Tai tallenna käyttämäsi siemenluku, jotta voit toistaa testit

Helpompaa testausta

Osa asioista jotka tekevät testaamisesta helpompaa, ovat myös yleisesti hyvän koodin tunnusmerkkejä, joihin tutustuimme edellisessä luvussa. Kun luokkien ja piirreluokkien rajapinnat kirjoitetaan mahdollisuuksien mukaan yksinkertaisiksi ja selkeiksi, ja luokkien välillä on vähän riippuvuuksia, niitä on helpompi testata. Kovin isot luokat kannattaa jakaa useammaksi luokaksi pyrkien erottamaan eri toiminnallisuuden osat vaikkapa omiin piirreluokkiin. Tämän jälkeen jokainen piirreluokka on tarvittaessa testattavissa erikseen.

Edellisessä luvussa esiteltiin pikaisesti muutama “sijaisluokka”, joille löytyy paljon käyttöä yksikkötestauksessa, koska käytännössä luokat tarvitsevat oman toiminnallisuutensa toteuttamiseen muita luokkia. Monimutkaiseen rajapintaan on vaikea kirjoittaa oikeaa toteutusta simuloivia apuluokkia

Myös se, että luokka kutsuu käyttämiensä luokkien konstruktoreita suoraan tekee riippuvuuksien korvaamisesta haastavaa. Jos riippuvuudet vaikkapa välitetään luokan konstruktorille, ei ongelmaa tule. Vastaavasti perinnän kautta tulevat riippuvuudet on hankalampi käsitellä kuin viittausten kautta tulevat.

Test-Driven Development (TDD)

Test-driven development on testausta korostava ohjelmistokehitysprosessi, jossa kaikki lähtee liikkeelle testien kirjoittamisesta. Heti kun luokan ulkoinen rajapinta on selvillä, kirjoitetaan luokan toiminnalle spesifikaation mukaiset testit. Toimintoa ryhdytään toteuttamaan vasta kun testit ovat olemassa. Vastaavasti kun koodista löytyy virheitä, aloitetaan niiden korjaus kirjoittamalla testi joka osoittaa virheen olemassaolon ja vasta sitten ryhdytään korjaamaan. Mahdollisuus testata virhettä automaattisesti helpottaa korjausprosessia ja myöhemmin testi estää virheen paluun. Test-Driven-Development on kuvattu tarkemmin vaikkapa Wikipediassa

Palaute

Vastaa palautekyselyyn A+:ssa.