Ohjelmoinnin peruskurssien laaja oppimäärä, kevät Luento 1: Testaus, ohjelman suunnittelutapoja Riku Saikkonen (osa kalvoista on suoraan ei-laajan kurssin luennoista) 14. 1. 2013
Sisältö 1 Ohjelmien testaustapoja 2 Yksikkötestaus: tynkäluokat, PyUnit 3 Suunnittelua: top-down- ja bottom-up-menetelmät
(ei-laajan kurssin kalvo: luento 1 sivu 25) Testaus Testaus vertaa ohjelman toimintaa sen oletettuun toimintaan (spesifkaatio) Poikkeamaa spesifkaatiosta kutsutaan virheeksi Spesifkaatio ei aina ole täydellinen Speksin tulkinnat on hyvä merkitä testin yhteyteen Testaus pyrkii osoittamaan virheiden olemassaolon Mutta ei voi todistaa virheettömyyttä Paitsi jos kaikki mahdolliset tilat ja syötteet käydään läpi jolloin kyseessä onkin jo oikeastaan mallintarkistus Hyvä testi pystyy näyttämään aiemmin tuntemattoman virheen olemassaolon Testaus pyrkii testaamaan ohjelmaa tai sen osaa tietoisesti sekä perustapauksilla, että spesifkaation rajoilla ja 10:10 erikoistapauksissa
(ei-laajan kurssin kalvo: luento 1 sivu 26) Testaus Testijoukon tulisi olla kattava Testaus ei ole helppoa Testattavuus riippuu paljon ohjelman rakenteesta, ts. kuinka helppoa ohjelman osat on irroittaa toisistaan testiä varten. Olion sisäinen tila riippuu suoritushistoriasta, yksittäistä operaatiota testaava testi voi mennä läpi, mutta useamman operaation mittainen epäonnistua. Tietyn virheen tuottavan suoritushistorian luominen voi olla hyvinkin hankalaa Esim. ohjelmaa käytettäessä havaittujen bugien toistaminen. Rinnakkaisohjelmoinnissa suoritushistorian rooli korostuu vielä enemmän, virheet toistuvat satunnaisesti ja mahdollisesti hyvin harvoin. 10:10
(ei-laajan kurssin kalvo: luento 1 sivu 27) Helpompaa testausta Määrittele luokkien väliset rajapinnat yksinkertaisiksi ja selkeiksi Monimutkaiseen rajapintaan on vaikea kirjoittaa tynkäluokkia Kapselointi (tynkä on jotain oikeaa luokkaa simuloiva pikkuluokka) Mitä vähemmän olion sisäinen tila riippuu muusta maailmasta, sitä helpompaa sitä on testata Lisäksi virheitäkin tulee todennäköisesti vähemmän Metodien ja luokkien jako pienempiin, hallittavampiin osiin Pieniä luokkia on helpompi testata Kaikki mainitut ovat muutenkin hyvän koodin tunnusmerkkejä 10:10
(ei-laajan kurssin kalvo: luento 1 sivu 28) Testauksen tasoja Järjestelmätestaus (System testing) (suoritetaan viimeiseksi) Koko järjestelmän testaaminen järjestelmän spesifkaatiota vastaan Integraatiotestaus (Integration testing) (koodin osia yhteen liitettäessä) Ohjelman osien testaaminen yhdessä Erityisesti ohjelman osien välinen tiedonvaihto Yksikkötestaus (Unit testing) (tästä yleensä aloitetaan) Yksikkötestauksessa ohjelmasta testataan pientä osaa, usein yksittäistä metodia tai luokkaa Verrataan tekniseen spesifkaatioon Testaus pyritään yleensä suorittamaan riippumattomasti 10:10 muista ohjelman osista
(ei-laajan kurssin kalvo: luento 1 sivu 32) Testausvinkkejä Testaa aina: Sallituilla äärirajoilla Esim. Poista listasta alkio kun siellä on vain yksi alkio. Laittomilla äärirajoilla Esim. Katso tuleeko koodilta speksin mukainen poikkeus Normaaliarvoilla Esim. Hae listasta jossa on kymmenen alkiota alkiota joka ei ole siellä ja katso että paluuarvo on oikein. Jos käytät satunnaisuutta, alusta satunnaislukugeneraattori itse vakioarvolla (seed) Virhetilanteet voi tällöin toistaa ja debugata Tai tallenna käyttämäsi siemenluku, jotta voit toistaa testit10:10
(ei-laajan kurssin kalvo: luento 1 sivu 33) Testikattavuuden mittoja Testeille on olemassa joitakin kattavuusmittoja: esimerkkejä kontrollivuon kattavuusmitoista: Rivikattavuus, lausekattavuus tutkivat kuinka suuri osa riveistä suoritetaan vähintään kerran testauksen aikana Ehtokattavuus Ehtolauseissa kaikkien ehtolauseiden ehtojen tulee saada kaikki arvonsa Polkukattavuus Ohjelman suoritus haarautuu ehtolauseissa. Polkukattavuus pyrkii mahdollisimman monen eri polun suorittamiseen. Jopa täydellinen rivikattavuus voi olla vaikea saavuttaa 10:10
Black-box- ja white-box-testaus toinen (testauksen tasoista riippumaton) tapa luokitella testejä black-box-testit tehdään testattavan asian speksin (rajapinnan) perusteella mahdollisesti jo ennen koodin kirjoittamista testattava asia on musta laatikko, jonka sisälle ei haluta katsoa white-box-testit tehdään koodin perusteella esim. yritetään kattaa testeillä kaikki koodin rivit tai eri lailla käyttäytyvät tilat, joissa olio voi olla tai tilakoneen tapaan käyttäytyvästä ohjelmasta kaikki tilasiirtymät eri tilojen välillä mietittävää: miten saat testeistä kattavia (esim. 100 % rivikattavuus) a) white-box-testeillä, b) black-box-testeillä?
Black-box- ja white-box-testaus toinen (testauksen tasoista riippumaton) tapa luokitella testejä black-box-testit tehdään testattavan asian speksin (rajapinnan) perusteella mahdollisesti jo ennen koodin kirjoittamista testattava asia on musta laatikko, jonka sisälle ei haluta katsoa white-box-testit tehdään koodin perusteella esim. yritetään kattaa testeillä kaikki koodin rivit tai eri lailla käyttäytyvät tilat, joissa olio voi olla tai tilakoneen tapaan käyttäytyvästä ohjelmasta kaikki tilasiirtymät eri tilojen välillä mietittävää: miten saat testeistä kattavia (esim. 100 % rivikattavuus) a) white-box-testeillä, b) black-box-testeillä? entä riittääkö, että testit ovat 100 % kattavia? (mitä kattavuus ei kerro?)
Ekvivalenssiluokat ja raja-arvoanalyysi black- ja white-box-testauksen avuksi on muutamia puoliformaaleja menetelmiä eräs tapa tehdä black-box-testejä (nimeltään equivalence partitioning / boundary value analysis): etsi syötteen osien ekvivalenssiluokat: arvoalueet, joilla testattavan osan pitäisi käyttäytyä samalla lailla etsi ekvivalenssiluokkien rajat (reuna-arvot) tee testisyötteet a) jostain ekvivalenssiluokan keskeltä, ja b) jokaisen reuna-arvon ympäriltä testaa vain yhtä arvoa kerrallaan (älä yhdistele eri testejä) esim. itseisarvon x laskeva funktio abs(x) ekvivalenssiluokat: x < 0, x = 0, x > 0 raja-arvot: x = 1, x = 0, x = 1 testataan esim. arvoja 100, 1, 0, 1, 100 ehkä myös 2, 2 (reuna-arvojen vierestä) ja pienin ja isoin (toisiksi pienin ja toisiksi isoin) x:n arvo, jos x on esim. 32-bittinen luku
(ei-laajan kurssin kalvo: luento 1 sivu 35) Testaus ja Extreme Programming Test-frst toteutusjärjestys Ohjelmointi aloitetaan testien suunnittelulla ja toteuttamisella Kun uusia ominaisuuksia toteutetaan, ne voidaan heti testata Jos ohjelma toimii väärin, kirjoitetaan ensin testi joka osoittaa virheen olemassaolon ja vasta sitten ryhdytään korjaamaan virhettä 10:10
(ei-laajan kurssin kalvo: luento 1 sivu 36) Muodollinen todentaminen Ohjelman virheettömyyttä ei voi todistaa testaamalla Jos ohjelman on välttämätöntä olla virheetön, sen toiminnallisuus todistetaan muodollisella todentamisella Formal verifcation (muodollinen todentaminen): Muodollinen todentaminen pyrkii todistamaan ohjelman oikeellisuuden, joko : Tutkimalla kaikki ohjelman tilat : mallintarkistus Matemaattisella todistuksella : invariantit, temporaalilogiikka,jne. Käytännössä tämä on raskasta ja tehtävissä vain rajallisen kokoisille ohjelmille Esim: tiedonsiirtoprotokollat, hardware Ei käsitellä tällä kurssilla 10:10
Sisältö 1 Ohjelmien testaustapoja 2 Yksikkötestaus: tynkäluokat, PyUnit 3 Suunnittelua: top-down- ja bottom-up-menetelmät
Yksikkötestaus Yksikkötestaus (Unit testing) Yksikkötestauksessa ohjelmasta testataan pientä osaa Verrataan tekniseen spesifkaatioon Testaus pyritään yleensä suorittamaan riippumattomasti Monesti joudutaan toteuttamaan ns. Testipeti Simuloi muita ohjelman osia Sisältää usein tynkäluokkia, oikeiden luokkien sijaan Helpottaa testausta Muut luokat saadaan toimimaan tarkalleen speksin mukaan ennenkuin niitä on olemassakaan Erikoistapaukset on monesti helpompi generoida näin, kuin käyttämällä muita luokkia normaalisti Muista luokista voidaan kaivaa tietoja jotka eivät muuten olisi saatavilla 10:10 (ei-laajan kurssin kalvo: luento 1 sivu 29)
(ei-laajan kurssin kalvo: luento 1 sivu 31) Tynkäluokka Tynkiä voi käyttää testatessa ja uutta koodia kehittäessä Allaolevassa esimerkissä tyngän avulla simuloidaan puuttuvaa tietokantaluokkaa luokalla joka aina löytää haluamansa. Hyöty: Kaiken koodin ei tarvitse olla kerralla valmiina vaan kehitettävää koodia pääsee ajamaan jo alkuvaiheessa. Hyöty: Tynkätietokanta voidaan pakottaa toimimaan oikein. Esimerkin koodi tekee aika karkean yksinkertaistuksen Mieti millaisessa tilanteessa tätä koodia tarvitseva osuus toimisi väärin? class CustomerDatabaseStub(object): def get_customer_by_name(self, name): # ei haeta oikeasti kannasta vaan luodaan lennossa return Customer(name) 10:10
(ei-laajan kurssin kalvo: luento 1 sivu 37) PyUnit Pyunit on helppokäyttöinen yksikkötestaustyökalu Yksikkötestausmoduuli tulee Pythonin mukana Testit kirjoitetaan omaksi luokakseen, sisältäen : Koodin joka pystyttää testitilanteen Esim luo tarvittavat oliot, avaa tiedostot jne. setup(self) Koodin joka ajaa testit ja todentaa tulokset test...(self) Koodin joka purkaa testitilanteen : sulkee tiedostot... teardown(self) 10:10
(ei-laajan kurssin kalvo: luento 1 sivu 38) PyUnit Perustesti Ota PyUnit-kirjaston nimistö käyttöön importilla Periytä luokkasi luokasta TestCase Testimetodien nimet alkavat aina test... Testattavat asiat tarkistetaan self.assertnnn komennoilla import unittest From my_integer import MyInteger class BasicTest(unittest.TestCase): def test_add_zero(self): value = MyInteger(123) zero = MyInteger(0) expected_result = MyInteger(123) result = value.add(zero) self.assertequal(expectedresult, result) If name == ' main ': unittest.main() Testit täytyy lopun if-osion main()käynnistää varsinaisesti testit 10:10
(ei-laajan kurssin kalvo: luento 1 sivu 39) test..., setup, teardown test-sanalla alkavat metodit sisältävät testejä. PyUnit suorittaa ne kaikki. Vaikkapa metodi joka kutsuu binäärisen hakupuun lisäysmetodia ja sitten hakee sen puusta contains-metodilla. Assert-metodilla vaaditaan että contains:in tulee palauttaa True. setup-niminen matodi suoritetaan ennen jokaista testiä. Kaikille testeille yhteisten muuttujien alustaminen, etteivät testit vaikuta toisiinsa jne... teardown-metodi suoritetaan jokaisen testin jälkeen. Esim setup:ssa avattujen tiedostojen sulkeminen tms. 10:10
(ei-laajan kurssin kalvo: luento 1 sivu 40) PyUnit - Assert assertnnn-metodeilla jokin ehto jonka tulee olla totta. Jos ehto ei ole totta, testin kohta ei mene läpi. TestCase-luokassa on paljon erilaisia assert-metodeja asserttrue(condition, message) Parametrin pitää olla True assertfalse(condition, message) Parametrin pitää olla False assertequal(haluttu, todellinen, message) Parametrien pitää olla samat assertisnone, assertisnotnone jne.. käytännössä kaikki muut variaatiot voi kuitenkin rakentaa käyttämällä metodia asserttrue Koko lista löytyy API:sta http://docs.python.org/library/unittest.html 10:10
Sisältö 1 Ohjelmien testaustapoja 2 Yksikkötestaus: tynkäluokat, PyUnit 3 Suunnittelua: top-down- ja bottom-up-menetelmät
(ei-laajan kurssin kalvo: luento 2 sivu 4) Top-Down Top-down toteutuksessa ja suunnittelussa aloitetaan koko järjestelmän suunnittelusta Tämä määrittely on hyvin suurpiirteinen Määrittely jakaa järjestelmän alijärjestelmiin ja kertoo järjestelmän tasolla kuinka se käyttää alijärjestelmiä joiden kuvitellaan olevan olemassa Yllämainittu toistetaan alijärjestelmän tasolla Alijärjestelmä kuvataan käyttäen apuna pienempiä osasia Lopulta päästään alimmalle tasolle Yksittäiset luokat, metodit Todennäköisesti suunnitelmaa pitää tarkentaa useaan kertaan ennenkuin se on toteutettavissa 11:31
(ei-laajan kurssin kalvo: luento 2 sivu 5) Top-Down Suunnittelussa määritellään järjestelmien ulkoiset rajapinnat ennen niiden toteutusta Toteutusvaiheessa koodi rakennetaan käyttämään osia joita ei vielä ole olemassa Voidaan kirjoittaa tynkiä jotta päästään testaamaan...ja kääntämään Kirjoita ensimmäinen versio pseudokoodilla Älä kiinnitä alussa lainkaan huomiota syntaksiin tms. Kun runko on kasassa on suunnittelua helppo tarkentaa 11:31
Top-down-menetelmän ongelmia top-down-menetelmällä voi päätyä suunnittelemaan pitkäänkin ennen kuin kokeilee suunnitelman toimivuutta käytännössä usein vasta ohjelmoidessa huomaa, ettei suunniteltu rajapinta toimikaan hyvin jos suuri osa muusta ohjelmasta on jo valmiiksi suunniteltu, rajapintojen muuttaminen voi olla työlästä suunnitelmasta saattaa unohtua ominaisuus, jota on hankala lisätä siihen jälkeenpäin tai siihen voi tulla turhaa monimutkaisuutta esim. suunnitellaan (ehkä tehdäänkin) monipuolinen apukirjasto, josta loppujen lopuksi tarvitaankin vain yksinkertaista erikoistapausta
(ei-laajan kurssin kalvo: luento 2 sivu 6) Bottom-up Bottom-up strategia taas lähtee liikkeelle yksittäisistä osasista ja yhdistää niitä kierros kerrallaan suuremmiksi kokonaisuuksiksi Ohjelmointi ja testaus voidaan aloittaa heti Osasten yhdistely myöhemmin voi olla hankalaa koska ne on suunniteltu itsenäisesti Bottom-up tukee koodin uudelleenkäyttöä Vanhat osat saadaan sellaisenaan käyttöön Top-down suunnittelussa syntyvät rajapinnat eivät välttämättä sovi sellaisenaan vanhalle koodille 11:31
Bottom-up-menetelmän ongelmia bottom-up-menetelmässä ohjelmoidaan yksittäisiä palasia miettimättä kovin paljon sitä, miten ne sopivat yhteen rajapinnat voivat olla sekavia moduulijako voi olla huono: eri moduulit eivät ole itsenäisiä tai riippuvat toisistaan syklisesti yleinen tapa, jonka ongelmat muistuttavat bottom-upia: tehdään aluksi nopeasti pieni prototyyppi ai niin, siihen pitää vielä lisätä... ja lopulta suunnittelematta tehty prototyyppi jääkin käyttöön, koska se tekee jo niin paljon, ettei sitä ehditä toteuttaa uudelleen oikein
(ei-laajan kurssin kalvo: luento 2 sivu 7) Top-Down + Bottom-Up Design Top-Down, Implement Bottom-Up Paras lopputulos saadaan yhdistämällä Top-down suunnittelulla vältetään moduulien yhteensopivuusongelmia ja saadaan kokonaiskuva projektista Yhdistettynä Bottom-up toteutukseen koodin uudelleenkäyttö helpottuu Bottom-up toteutuksella päästään (yksikkö)testaamaan koodin osaset heti niiden valmistuessa 11:31
Entä käytännössä? sopiva yhdistelmä top-down- ja bottom-up-menetelmiä riippuu tilanteesta ja ohjelmoijista jotkut ohjelmat vaativat enemmän suunnittelua kuin toiset jotkut vähemmän
Entä käytännössä? sopiva yhdistelmä top-down- ja bottom-up-menetelmiä riippuu tilanteesta ja ohjelmoijista jotkut ohjelmat vaativat enemmän suunnittelua kuin toiset esim. ohjelma tekee monimutkaisia asioita tai on iso tai sen toimivuus tai turvallisuus on tavallista tärkeämpää jotkut vähemmän ohjelman rakenne voi olla etukäteen pitkälle tiedossa (esim. edellisestä versiosta tai muista samanlaisista ohjelmista) tai se on yksinkertainen (onkohan tosiaan?) joskus tätä ei tiedä etukäteen esim. tekee jotain niin uutta, ettei etukäteen tiedä, mitä ohjelman pitäisi tehdä tai miten se kannattaa tehdä usein (melkein aina?) ohjelman tavoitteet muuttuvat sitä tehdessä