Yksikkötestaaminen

Yksikkötestaaminen () on tietokoneohjelman testaamisen ja laadunvarmistuksen menetelmä, jossa lähdekoodin osat testataan yhdessä tai erikseen. Yksikkötestaamisessa käytetään pienintä palaa testattavasta ohjelmasta ja se eroaa koko ohjelman testaamisesta yhtenä kokonaisuutena. Yksikkötestaamisessa testattava pala eristetään muusta ohjelmasta ja tarkistetaan toimiiko se kuten sen odotetaan toimivan. 

Yksikkö tarkoittaa sovelluksen osaa. Yleensä yksikkö on yksittäinen toiminto, mutta esimerkiksi olio-ohjelmoinnissa yksikkö voi olla kokonainen luokka. Testeistä saadaan kattavia rakentamalla niitä ensin pienimmille testattaville yksiköille ja sen jälkeen niiden välisille yhteyksille. Ongelmien kartoittamiseksi jokainen testitapaus tulee testata erikseen. 

Yksikkötestauksen tavoitteena on eristää ohjelman jokainen osa ja osoittaa, että yksittäiset osat toimivat oikein. Yksikkötesti tarjoaa tiukan sopimuksen, joka koodinpalan on täytettävä. Yksikkötestaus löytää ongelmat kehityssyklin alkuvaiheessa. Vikoja voi ilmetä ohjelmoijan toteutuksessa ja yksikön määrityksessä. Testisarjan kirjoittaminen pakottaa miettimään syötteitä, lähtöjä ja virheolosuhteita, joiden avulla yksikön käyttäytyminen saadaan määriteltyä paremmin. Yksikkötestauksen kustannukset ovat huomattavasti alhaisemmat, kuin näiden virheiden löytäminen ohjelmistokehityksen loppuvaiheessa. 

Yksikkötestaaminen tehdään usein automatisoidusti, mutta myös manuaalinen testaus on mahdollinen.

Ohjelman eri osat voidaan testin ajaksi korvata yksinkertaistetuilla korvikkeilla. Korvattavat osat eivät kuulu varsinaiseen testattavaan yksikköön.

Yksikkötestaamisen lisäksi voidaan tehdä integraatiotestaaminen, jossa eri yksiköt testataan yhtenäisenä kokonaisuutena.

Standardit, kuten IEC 61508, saattavat vaatia ohjelman yksikkötestaamista.

Hyödyt 
Yksikkötestauksella löydetään usein ongelmia jo kehityssyklin varhaisessa vaiheessa. Näitä ovat sekä ohjelmiston kehittäjän tekemät virheet toteutuksessa että yksikön määrittelyssä olevat puutteet tai puuttuvat osat. Perusteellisten testien kirjoittamisprosessi pakottaa ohjelmoijan pohtimaan syötteitä, tulosteita ja virhetilanteita ja siten määrittelemään yksikön halutun käyttäytymisen tarkemmin. Virheiden löytyminen projektin alkuvaiheessa on taloudellisesti huomattavasti halvempaa kuin virheen havaitseminen, tunnistaminen ja korjaaminen myöhemmin. Jos ohjelma julkaistaan virheellisenä, sen koodissa esiintyvät virheet aiheuttavat helposti kalliita ongelmia ohjelmiston loppukäyttäjille. Huonosti kirjoitettua koodia voi olla mahdotonta tai vaikeaa yksikkötestata, joten yksikkötestaus usein pakottaa kehittäjät kirjoittamaan parempaa koodia. Yksikkötestauksen etuja ovatkin juuri automaatio ja hyvän yksikkötestauksen tuoma varmuus testattujen luokkien toiminnasta.

Testivetoisessa kehityksessä (TDD), jota käytetään usein ohjelmistokehityksessä ja scrumissa, luodaan yksikkötestit jo ennen itse koodin kirjoittamista. Kun testit suoritetaan hyväksytysti, kyseinen koodi katsotaan valmiiksi. Samaa yksikkötestiä ajetaan kyseistä usein, aina kun suurempaa koodikantaa kehitetään, joko koodin muuttuessa tai automatisoidun prosessin avulla rakennuksen yhteydessä. Jos yksikkötestit eivät läpäise testiä, se katsotaan joko virheeksi muuttuneessa koodissa tai testeissä itsessään. Yksikkötestit mahdollistavat vian tai virheen jäljittämisen helposti. Koska yksikkötestit ilmoittavat kehittäjille ongelmasta ennen kuin koodi siirretään testaajille tai asiakkaille, mahdolliset ongelmat havaitaan varhaisessa vaiheessa kehitysprosessia.

Yksikkötestaus mahdollistaa koodin myöhemmän refaktoroinnin tai järjestelmäkirjastojen päivittämisen, jolloin  varmistetaan, että moduuli toimii edelleen. Menettelyssä kirjoitetaan kaikille funktioille ja metodeille testitapauksia, jotta aina kun muutos aiheuttaa virheen, se myös tunnistetaan mahdollisimman nopeasti. Yksikkötestit havaitsevat sellaiset muutokset, jotka saattavat rikkoa ohjelmistolle asetetut vaatimukset.

Järjestelmän toiminnan dokumentointi 

Yksikkötestaamisen dokumentointi ja raportointi on keskeinen osa ohjelmistoprojekteissa, jotka noudattavat ketteriä menetelmiä, laatuvaatimuksia tai sääntelyvaatimuksia. Dokumentoinnin laatiminen auttaa osoittamaan toteutettujen yksikkötestiesi kattavuuden ja täydellisyyden sekä tunnistamaan ja priorisoimaan virheitä. Niin ikään dokumentointi auttaa jakamaan palautetta ja kehitysehdotuksia projektin tiimin ja sen sidosryhmiesi välillä. Lisäksi kattavasti laadittu dokumentointi auttaa tarkastelemaan ja hienosäätämään projektin testausstrategiaa ja näin parantaa koodin laatua.

Yksikkötestaus tarjoaa elävän dokumentaation kehitettävästä järjestelmästä. Kehittäjät, jotka haluavat oppia, millaisia toimintoja tarkasteltava yksikkö tarjoaa ja miten sitä käytetään, käyttävät yksikkötestejä saadakseen lähtökohtaisen käsityksen yksikön rajapinnasta (API). Yksikkötestitapaukset ilmentävät ominaisuuksia, jotka ovat kriittisen tärkeitä yksikön toiminnallisuuden toteutumisen kannalta. Nämä ominaisuudet saattavat osoittaa joko sopivan tai sopimattoman tavan käyttää yksikköä sekä myös niin sanotut negatiiviset käyttäytymismallit, joita yksiköllä ehkä on. Yksikkötestitapaus itsessään dokumentoi näitä kriittisiä ominaisuuksia, vaikkakin monet ohjelmistokehitysympäristöt eivät perustu pelkästään siihen, että koodi dokumentoi kehitteillä olevaa tuotetta. Toisaalta jotkut kehitysympäristöt tarjoavat mahdollisuuden automaattisesti generoida dokumentaatiota testitapauksista: tämä dokumentaatio on esimerkiksi HTML-raportti, joka tarjoaa yksityiskohtaisen yhteenvedon yksikkötesteistä ja niiden tuloksista.  

Kun ohjelmistoa kehitetään testivetoisella lähestymistavalla, yksikkötestin kirjoittaminen rajapinnan määrittämiseksi yhdessä testiläpäisyn jälkeen kirjoitettavien refaktorointitoimenpiteiden kanssa, saattaa joissakin tapauksissa korvata muodollisen suunnittelun. Jokainen yksikkötesti itsessään on tavallaan luokkia, metodeja ja käyttäytymishavainnointia määrittelevä suunnitteluelementti.

Dokumentaation ylläpitäminen kehittyvän koodipohjan kanssa on haasteellista, sillä dokumentaatio vanhentuu helposti uusien ominaisuuksien lisääntyessä. Tämän välttämiseksi on suositeltavaa pitää projektin dokumentaatio samassa arkistossa kehitettävän koodin kanssa ja näin mahdollistetaan laadukkaan "commitin" luominen, joka sisältää koodimuutoksen, päivitetyt yksikkötestit ja niihin liittyvän dokumentaation samassa kokonaisuudessa.

Haittapuolet ja rajoitteet 
Testien kehittäminen voi vaatia paljon aikaa: jokaista Java-koodiriviä kohden tarvitaan keskimäärin 3–5 JUnit-koodiriviä riittävän kattavuuden saavuttamiseen.Tämä voi johtaa siihen, että investointi yksikkötestaukseen ei ole vaivan arvoista. Usein jos ohjelma on hyvin buginen, myös yksikkötestin koodi toimii kehnosti. Kuitenkin testien kirjoittamista ja ylläpitoa voidaan nopeuttaa parametroiduilla testeillä. Ne mahdollistavat yhden testin suorittamisen useita kertoja eri tulosarjoilla, joka vähentää testikoodin päällekkäisyyttä. Tavalliset yksikkötestit on suljettuja menetelmiä ja testaavat muutamia ehtoja, kun taas parametroidut testit testaavat minkä tahansa parametrin. 

Yksikkötestauksessakaan ei havaita jokaista ohjelman virhettä, koska se ei pysty arvioimaan jokaista suorituspolkua muissa kuin triviaalisimmissa ohjelmissa. Tämä ongelma on ratkaisematon. Tämän lisäksi yksikkötestaus määritelman mukaan testaa vain yksiköiden toimivuutta, joka johtaa siihen, ettei integrointivirheitä tai laajempia järjestelmätason virheitä huomata. Järjestelmätason virheiksi voidaan määritellä useissa yksiköissä suoritettuja toimintoja tai ei-toiminnallisia testialueita, kuten suorituskykyä. Tämän takia yksikkötestaus tulee tehdä muiden ohjelmistojen testaustoimintojen yhteydessä, jotta testauksesta saadaan tarpeeksi kattava. Yksikkötestaus ei vastaa integraatiotestausta, jolloin integrointi oheislaitteiden kanssa tulee sisällyttää itse integraatiotestaukseen.

Yksi haaste on yksikkötestien kirjoittamiseen liittyvä, realististen ja hyödyllisten testien asettamisen vaikeus. Asiaankuuluvat alkuehdot tulee luoda testiä tehdessä, jotta testattava sovelluksen osa käyttäytyy kuin osa koko järjestelmää. Jos alkuehdot puuttuvat tai ne eivät ole asetettu oikein, testi ei käytä koodia realistisessa kontekstissa joka heikentää testin arvoa ja tarkkuutta.

Jotta yksikkötestauksesta hyödytään, tulee pitää kirjaa suoritetuista testeistä ja kaikista muutoksista, jotka on tehty ohjelmiston yksikön lähdekoodiin. Versionhallintajärjestelmän käyttö on välttämätöntä. Jos myöhempi versio ei läpäise tiettyä testiä, jonka sen aiempi versio oli läpäissyt, löytyy versionhallinnasta luettelo lähdekoodin muutoksista, joita on tehty yksikössä tämän ajankohdan jälkeen. Tällöin paljastuu, missä kohtaa virhe on sattunut. Tämän lisäksi on tärkeä ottaa käyttöön kestävä prosessi, joka varmistaa, että testitapausten epäonnistumiset tarkistetaan säännöllisesti ja niihin puututaan välittömästi. Jos tämä prosessi unohdetaan, testien tehokkuus heikkenee ja positiivisten tulosten määrä vähenee.

Sulautetun järjestelmäohjelmiston yksikkötestaus on ainutlaatuinen haaste yksikkötestauksessa. Koska tämä ohjelmisto kehitetään eri alustalle kuin millä se lopulta ajetaan, testiohjelma ei kovinkaan helposti toimi ohjelman käyttöympäristössä kuten vaikka työpöytäohjelmien kanssa.

Ketterä ohjemistokehitys yksikkötestauksessa 
Ketterässä ohjelmistokehityksessä kehitysprosessi koostuu iteratiivisista vaiheista, joita kutsutaan sprinteiksi ja joista jokainen sisältää suunnittelu-, koodaus- ja testausvaiheet. Yhden sprintin aikana, kun ohjelmistoprojektin vaatimukset on kerätty ja kehitysprosessi on jo suoritettu, suoritetaan yksikkötestaus. Ketterän ohjelmistokehitysparadigman perusteella yksikkötestausta ei välttämättä tarvitse tehdä tiukasti yksittäisen sprintin loppuvaiheessa, mutta yleensä se tapahtuu tässä vaiheessa.

Yleensä yksikkötestaus käsittää yhden käyttäjätarinan. Pohjimmiltaan jotkut tiimin jäsenet kirjoittavat testiskriptit, jotka ohjelmistokehittäjät sitten suorittavat. Näiden skriptien tarkoituksena on testata joitakin erittäin spesifisiä ohjelmiston osia arvioidakseen uusien osien oikean toiminnan ja tehokkuuden, mikä on varsin tehokasta juuri ketterässä ohjelmistokehityksessä, koska sprinttien avulla kehittäjät voivat sopeutua uusiin vaatimuksiin ja ohjeisiin varsin nopeasti. Tämä vähentää riskiä joutua noudattamaan tiukkaa, ennalta määritettyjä vaatimusten sarjaa kehitysprosessin alusta loppuun asti.

Jos yksikkötesti epäonnistuu, kaikki tiedot dokumentoidaan, mukaan lukien yksikkötestin syötteet, odotetut tulokset, todelliset tulokset ja vaiheittaiset ohjeet siitä, kuinka samat tulokset voidaan toistaa. Lisäksi, jos virheitä ilmenee, kehitys palaa sprintin suunnittelu- ja koodausvaiheisiin.

Toisaalta, jos testiskripti palauttaa odotetut tulokset, yksikkötesti voidaan luokitella ”läpäistyksi”. Tällöin kehitys siirtyy sprintin viimeisiin vaiheisiin. Näiden vaiheiden aikana suoritetaan joitakin tärkeitä testausvaiheita, kuten koodin tarkistusta. Tämän jälkeen edistymisestä raportoidaan sidosryhmille, jotka lopulta päättävät, ovatko tulokset tyydyttäviä vai eivät.

Testaamisen työkalut 
Yksikkötestauskehykset ovat useimmiten kolmannen osapuolen tuotteita, joita ei jaeta osana kääntäjäpakettia. Ne auttavat yksinkertaistamaan yksikkötestausprosessia, koska ne on kehitetty useille eri kielille.

Yksikkötestaamiseen on useita alustoja kuten JUnit, NUnit ja KUnit.

Kielitason yksikkötestauksen tuki 
Jotkut ohjelmointikielet tukevat suoraan yksikkötestausta. Tämä tarkoittaa, että niiden kielioppi mahdollistaa yksikkötestien suoran ilmoittamisen ilman kirjaston tuontia.

Kielet, joissa on sisäänrakennettu yksikkötestaustuki:

 Apex 
 Cobra
 Crystal
 D
 Go
 Julia

 LabVIEW
 MATLAB
 Python
 Racket
 Ruby
 Rust

Joillakin kielillä ei ole sisäänrakennettua yksikkötestaustukea, mutta niillä on perustettu yksikkötestauskirjastot tai -kehykset. Näitä kieliä ovat:

 ABAP
 C++
 C#
 Clojure
 Elixir
 Java
 JavaScript
 Objective-C

 Perl
 PHP
 PowerShell
 R (testthat)
 Scala
 Tcl
 Visual Basic .NET
 Xojo (XojoUnit)

Lähteet 

Ohjelmiston testaaminen