Yksikkötestausesimerkki

Yksikkötestaustutoriaali: Record -luokka (8.11.2005)

Oheinen tutoriaali havainnoillistaa esimerkkinä erään luokan yksikkötestauksen testaussuunnitelman mukaan. Omien luokkien yksikkötestauksen pitäisi sujua ilman liiempiä ongelmia jos ymmärrät tämän esimerkin. Työkalujen käyttöä voi myös harjoitella tekemällä esimerkissä kuvatut vaiheet. Pidä kuitenkin huoli siitä että et päivittele CVS:sä olevaa maito-moduulia ilman syytä!

Ennen testauksen aloittamista seuraavien pitäisi olla kunnossa:

Esimerkissä Maito-moduuli on tuotu repositoriosta (check out) Eclipseen projektiksi (katso CVS-ohjeesta pikavinkki CVS:n käyttöön Eclipsessä).

Testattava komponentti

maito.datacollecting.Record

 /*
 * Record.java
 *
 * This software is released under the GNU GPL license
 */

 package maito.datacollecting;

 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Vector;

 public class Record {

    private HashMap fields;
    private String recordID;

    /**
    * Class constructor
    *
    * @param newID 
    * The ID to be set for the new record, must not be null
    * (In case of null value, ID is assumed to be an empty String)
    */
    public Record(String newID){
        if (newID != null) {
            recordID = newID;
        }
        else recordID = "";
        fields = new HashMap();
    }

    /** 
    * Returns a list of keys in this record
    *
    * @return a HashSet of all keys in this Record
    */
    public HashSet getFieldNames() {
        return new HashSet(fields.keySet());
    }

    /** 
    * Gets the value(s) of the field with the specified key.
    * Record keys are Strings and values are Vectors containing Strings
    *
    * @param fieldName The key of the required field
    * @return The required value, null if field not found
    */
    public Vector getField (String fieldName) {
        return (Vector) fields.get(fieldName);
    }

    /** 
    * Gets the ID of this record
    *
    * @return The ID of this record
    */
    public String getID() {
        return recordID;
    }

    /** 
    * Adds a value to a field. If specified key already exists,
    * adds a new String value to the existing Vector, otherwise creates a new one
    *
    * @param fieldKey A key to be inserted or modified, must not be null
    * @param fieldValue A value to be inserted
    * @return true if value stored, false if there was an error
    */ 
    public boolean setField (String fieldKey, String fieldValue) {
        if ((fieldKey) == null) {
            return false;
        }
        else if (fields.containsKey(fieldKey)) { // Vector already exists
            Vector tempVector = (Vector)fields.get(fieldKey);
            tempVector.addElement(fieldValue);
            fields.put(fieldKey, tempVector);
            return true;
        }
        else { // Create a new Vector
            Vector newVector = new Vector(5); // Most keys should not have
                //even this many entries - keyword list being the exception
            newVector.addElement(fieldValue);
            fields.put(fieldKey, newVector);
            return true;
        }
    }

    /**
    * Two records are considered the same only if they have the same id. 
    *
    * @return true if two records are equal, false if not
    */
    public boolean equals (Record record) {
        if (record == null) {
            return false;
        }
        return this.getID().equals(record.getID());
    }
 }

Testit toteuttava luokka

Testattava luokka on sen verran pieni ja yksinkertainen, että testejä ei tarvita suurta määrää, eivätkä ne ole luonteeltaan sellaisia, että ne olisi järkevää jakaa kovin moneen test() -metodiin setUp() ja tearDown() -metodien hyväksikäytön mahdollistamiseksi. Periaatteessa olisi JUnit-hengen mukaista jakaa testitapaukset aika moneenkin erilliseen test -metodiin (http://junit.sourceforge.net/doc/faq/faq.htm#tests_12), mutta tästä voidaan surutta poiketa, jos se ei tunnu järkevältä.

Kaikki Record -luokan metodit testataan erilaisilla parametreillä. Koska parametreinä käytetään vain String- ja Record -olioita, ekvivalenssiluokkia on oikeastaan kaksi: null ja olio. Testit on johdettu metodien kommentoinnin ja itse koodin perusteella. Näillä testeillä saadaan 100% lausekattavuus, mutta vähempikin olisi riittänyt. Jokaisen luokan testaukssa glass-box -menetelmällä on kuitenkin päästävä 100% lausekattavuuteen, eli se ei ole tavoite, vaan ehto.

maito.datacollecting.RecordTest

 package maito.datacollecting;

 import java.util.HashSet;
 import java.util.Vector;

 import junit.framework.TestCase;

 public class RecordTest extends TestCase {

    public RecordTest(String name) {
        super(name);
    }

    public void testRecordGetSets() {
        Record r = new Record("id");
        assertTrue(r.getID().equals("id"));

        assertTrue(!r.setField(null, "unset"));
        assertTrue(r.setField("author", "pekka"));
        assertTrue(r.setField("author", "olli"));
        assertTrue(r.setField("null", null));

        Vector v = r.getField("author");
        assertTrue(v != null);
        assertTrue(v.elementAt(0).equals("pekka"));
        assertTrue(v.elementAt(1).equals("olli"));

        v = r.getField("null");
        assertTrue(v != null);
        assertTrue(v.firstElement() == null);

        assertTrue(r.getField("unset") == null);
        assertTrue(r.getField(null) == null);

        HashSet hs = r.getFieldNames();
        assertTrue(hs.contains("author"));
        assertTrue(hs.contains("null"));
        assertTrue(!hs.contains("unset"));
    }

    public void testRecordEquals() {
        Record r1 = new Record("id");
        Record r2 = new Record("id");
        Record r3 = new Record("differentid");
        Record r4 = new Record(null);
        assertTrue(r4.getID().equals(""));

        assertTrue(r1.equals(r1));
        assertTrue(r1.equals(r2));
        assertTrue(!r1.equals(r3));
        assertTrue(!r1.equals(r4));
        assertTrue(!r1.equals(null));
    }
 }

Testin käynnistys

Etsi ajettava testiluokka (RecordTest.java) Eclipsen Package Explorerista. Valitse context menusta Run As -> JUnit Test. JUnit-ikkuna ilmestyy johonkin päin ruutua. Jos siinä näkyy pääosin vain vihreä palkki, kaikki ajetut testit menivät läpi. Muussa tapauksessa palkki on punainen, ja tekstiruutuun ilmestyy selvitys virheen aiheuttaneista tiedostoista ja koodiriveistä.

Lausekattavuuden mittaus

Testien ajo kattavuuden mittauksen kanssa on jonkin verran hitaampaa kuin ilman. Varsinkin suurilla aineistoilla testien ajaminen ilman kattavuusmittausta voi olla järkevää. Mittauksen voi käynnistää context menusta Run As -> JUnit w/Coverlipse. Jos tätä vaihtoehtoa ei ole valikossa, kokeile käynnistää ajo Coverlipsen "how to use" ohjeen mukaisesti. JUnitin lisäksi pitäisi ilmestyä Coverlipsen ikkuna, jossa ilmoitetaan mitatut luokat ja niiden kattavuudet. Glass-box -testauksessa kaikkien luokkien kattavuuden pitäisi olla 100%, paitsi testaussuunnitelmassa mainituissa poikkeustapauksissa. Varmista että Coverlipsen ikkunan yläreunasta on valittu "block coverage" -nappi. Jos jonkin luokan kohdalla kattavuus on alle 100%, tuplaklikkaa ko. luokkaa ja se avautuu editori-ikkunaan, jossa jokaisen rivin alussa on vihreä tai punainen kattavuuteen sisältyvyyttä ilmaiseva merkki.

Lopputoimet

Testiluokka commitoidaan CVS-repositorioon. Jos Record -luokasta olisi löydetty (pieniä) virheitä, ne olisi voitu korjata itse, ja commitoida myös korjattu luokka repositorioon. Luokan kirjoittajalle ilmoitettaisiin virheestä ja korjauksesta. Yksinkertaiseltakin vaikuttava virhe ja sen korjaus saattavat muuttaa luokan toimintaa tarkoituksenvastaiseksi. Luokan kirjoittajan tulisi varmistaa, ettei tehdyllä korjauksella ole tällaisia vaikutuksia.

Page last modified on November 12, 2005, at 03:07 PM