OHJELMISTOJEN TESTAUS JA HALLINTA Syksy 2015 / Auvo Häkkinen JUnit ja EasyMock (TilaustenKäsittely) Tehtävässä tarvittava koodi löytyy osoitteella http://users.metropolia.fi/~hakka/oth/mockesimerkki.zip Kopioi koodi ja luo tehtävää varten projekti. Vie projekti versionhallintaan omaan repositorioosi. Tutustu aluksi annettuun koodiin. Tehtävässä käsitellään tuotetilauksia. Kuhunkin tilaukseen liittyy asiakas ja tuote. Tilaus yhdistää tuotteen ja asiakkaan. Tilausten käsittely laskee tuotteelle alennushinnan, ja vähentää sen asiakkaan saldosta. Hinnoittelija-luokan tilalla on vain tynkä (engl. stub). Näin koodi saadaan kääntymään. Ongelma: Miten testata TilaustenKäsittely-luokka, kun sen tarvitsema Hinnoittelija-luokka on vielä toteuttamatta? Jos yksikkötestattavana oleva luokka käyttää toisen luokan metodeja, eikä tuota luokkaa ole vielä toteutettu, voi luokan tilalla käyttää sijaista (engl. test double / fake). Sijaisen ulospäin näkyvä käyttäytyminen on sama kuin todellisella luokalla, mutta se voi tehdä tehtävänsä oikoen ja / tai paluuarvo voi olla kovakoodattu. Kun oikea luokka on aikanaan saatavilla, sillä korvataan testien aikana käytetty sijainen, ja testit ajetaan uudestaan. 1 Tynkä ja kovakoodatut paluuarvot Ensimmäinen testaustapa olisi laatia JUnit-testit siten, että kovakoodataan Hinnoittelijan tynkään kaikki testeissä tarvittava, mm. kiinteä alennusprosentti. 2 Jäljittelevän luokan käyttäminen Monipuolisempaa testausta varten voi Hinnoittelija-luokasta laatia jäljittelijän (engl. fake = jäljennös, huiputtaja). Varsinaisen sovelluksen sekaan ei tietenkään saa sotkea mitään testaamiseen liittyvää, joten luo Hinnoittelijaa jäljitteleväfakehinnoittelija -luokka Test Packages- osioon.
public class FakeHinnoittelija extends Hinnoittelija { private float alennus; public FakeHinnoittelija(float alennus) { this.alennus = alennus; public float getalennusprosentti(asiakas asiakas, Tuote tuote) { return alennus; Nyt alennusprosentti ei enää ole kovakoodattu, vaan sitä voidaan vaihdella tarpeen mukaan dynaamisesti testimetodeissa. Luo seuraavaksi JUnit-testiluokka, joka testaa tilausten käsittelyä käyttäen yllä olevaa Hinnoittelija-olion jäljittelijää. public class TilaustenKäsittelyFakeTest { @Test public void testaakäsittelijäwithfakehinnoittelija() { // arrange float alkusaldo = 100.0f; float listahinta = 30.0f; float alennus = 20.0f; float loppusaldo = alkusaldo - (listahinta * (1 - alennus / 100)); Asiakas asiakas = new Asiakas(alkuSaldo); Tuote tuote = new Tuote("TDD in Action", listahinta); Hinnoittelija hinnoittelija = new FakeHinnoittelija(alennus); // act TilaustenKäsittely käsittelijä = new TilaustenKäsittely(); käsittelijä.sethinnoittelija(hinnoittelija); käsittelijä.käsittele(new Tilaus(asiakas, tuote)); Aja testit. Vihreää. 3 Mock-olion käyttäminen Testaa seuraavaksi tilaustenkäsittelyn toimintaa käyttäen Mock-olioita. Tätä tapaa käytettäessä ei tarvitse luoda erikseen jäljittelijää luokasta Hinnoittelija, vaan Mock-ympäristö matkii Hinnoittelija-luokan ulospäin näkyvää käyttäytymistä. Mock-toteutuksia on tarjolla useita, esim. EasyMock, JMock, Mockito, jne.. Tässä harjoituksessa käytetään EasyMock-kehikkoa (engl. framework). Luo uusi JUnit-testiluokka ja kopioi siihen tämä koodi
public class TilaustenKäsittelyEasymockTest { @Test public void testaakäsittelijäwitheasymockhinnoittelija() { // arrange float alkusaldo = 100.0f; float listahinta = 30.0f; float alennus = 20.0f; float loppusaldo = alkusaldo - (listahinta * (1 - alennus / 100)); Asiakas asiakas = new Asiakas(alkuSaldo); Tuote tuote = new Tuote("TDD in Action", listahinta); Hinnoittelija hinnoittelija = createmock(hinnoittelija.class); // record expect(hinnoittelija.getalennusprosentti(asiakas, tuote)).andreturn(alennus); replay(hinnoittelija); // act TilaustenKäsittely käsittelijä = new TilaustenKäsittely(); käsittelijä.sethinnoittelija(hinnoittelija); käsittelijä.käsittele(new Tilaus(asiakas, tuote)); verify(hinnoittelija); Koodi ei käänny. Mukaan tarvitaan EasyMock-kehyksen käyttämät kirjastot. Tee ensin sovelluksesi hakemistoon lisäkirjastoja varten alihakemisto lib. Näin ne säilyvät sovelluksen muun koodin mukana. Etsi viereisessä kuvassa näkyvät jar-pakkaukset wwwhaulla ja talleta ne luomaasi hakemistoon. Liitä kirjastot sitten sovellukseen klikkaamalla projektin nimeä hiiren oikealla painikkeella, ja valitse Properties. Klikkaa avautuvassa ikkunassa Libraries ja sen jälkeen Add JAR/Folder. Jos Fix Imports ei osaa ottaa tarvittavia kirjastoja käyttöön, lisää käsin seuraavat rivit import static org.easymock.easymock.expect; import static org.easymock.easymock.createmock; import static org.easymock.easymock.replay; import static org.easymock.easymock.verify; Kun saat koodin kääntymään, aja testit. Vihreää.
Tutki koodia. Koodi poikkeaa aiemmasta tavasta muutamassa kohdassa. Nyt testaajan ei tarvitse koodata erikseen jäljittelevän luokan toimintaa, vaan toiminta kuvataan EasyMockkehikkoa käyttäen.... Hinnoittelija hinnoittelija = createmock(hinnoittelija.class); // record expect(hinnoittelija.getalennusprosentti(asiakas, tuote)).andreturn(alennus); replay(hinnoittelija);... verify(hinnoittelija); Matkittavan luokan nimi annetaan createmock()-kutsun parametrina. Record-vaiheessa rekisteröidään ("opetetaan") 1) minkä nimisiä metodeja tullaan kutsumaan testattavasta luokasta, ja 2) mitä arvoja kutsuista oletetaan saatavan. Tätä kohtaa ohjelmoitaessa on kurkistettu testattavaan TilaustenKäsittely-luokkaan. Sieltä näkyy mitä Hinnoittelija-luokan palveluja se käyttää (ja missä järjestyksessä). Kutsuttavat metodit ja odotetut paluuarvot rekisteröidään Mock-oliolle. Jos jotain metodia kutsutaan useita kertoja, on se myös rekisteröitävä yhtä monta kertaa. "Opettamisen" jälkeen toimintaa matkiva Mock-olio siirretään replay()-kutsulla tilaan, jossa se on valmis matkimaan puuttuvalle oliolle meneviä kutsuja. reset()-kutsulla voi tyhjentää aiemmat nauhoitukset, ja sen jälkeen voi opettaa uudet. Testin lopussa oleva verify()-kutsu varmistaa, että kaikkia Mock-oliolle nauhoitettuja metodeja on todella myös kutsuttu testin aikana. TEHTÄVÄ Laita TilaustenKäsittely-luokan käsittele()-metodi kommentin sisään ja kopioi sen tilalle alla oleva koodi public void käsittele(tilaus tilaus) { Asiakas asiakas = tilaus.getasiakas(); Tuote tuote = tilaus.gettuote(); hinnoittelija.aloita(); float prosentti = hinnoittelija.getalennusprosentti(asiakas, tuote); if (tuote.gethinta() >= 100) { hinnoittelija.setalennusprosentti(asiakas, prosentti + 5); prosentti = hinnoittelija.getalennusprosentti(asiakas, tuote); float alennushinta = tuote.gethinta() * (1 - (prosentti / 100)); asiakas.setsaldo(asiakas.getsaldo() - alennushinta); hinnoittelija.lopeta();
Tässä käytetään muutamaa uutta Hinnoittelija-luokan metodia. Lisää näiden tyhjät rungot Hinnoittelija-luokkaan, jotta koodi kääntyy. Laadi sitten Mock-olioita käyttäen JUnit-testit, joilla testaat, että tilauksen käsittely toimii oikein. Nyt se toimii hieman eri tavalla, kun hinta < 100 tai hinta >= 100. Kumpikin tilanne on testattava. Kun kutsuttava metodi ei palauta mitään, se rekisteröidään EasyMockissa ilman expect ja andreturn avainsanoja, esim. hinnoittelija.aloita(); Aja testit. Kun vihreää, niin tehtävä on valmis. o-o-o Lue ja opettele lisää: http://easymock.org/ ja sieltä välilehti Documentation.