Unit testen, enkele tips

 
03 oktober 2008

Als je de afgelopen jaren niet ergens in een grot hebt gewoond, weet je nu wel dat bij het ontwikkelen van software testen een belangrijke rol inneemt. Een van die vormen van testen is zelfs een hoeksteen van een hele beweging binnen de software ontwikkeling. Unit testen is een basis beginsel bij agile ontwikkelen.

Ondanks de grote bekendheid van unit testen, kom ik in de praktijk toch nog vaak tegen dat er geen, zinloze of zelfs foute unit testen geschreven zijn. Wat te denken van een test set waar na een aanpassing enkele testen niet meer werkten zoals bedoelt en waarbij de body van de test vervangen is door een Thread.sleep(1000)? Of, iets waar ik me in een duister verleden ook schuldig aan heb gemaakt, testen die onderling van elkaar afhankelijk zijn en daardoor omvallen bij een run waar de volgorde anders is dan verwacht?

Het moge duidelijk zijn dat testen als de voorgaande ondertussen als niet goed beschouwd mogen worden, maar wanneer is een test dan wel goed opgezet? Daarvoor wil ik een aantal tips aan de hand doen die in de wereld van het testen veel gebruikt worden bij het opstellen van allerhande soorten testen.

Allereerst begin je natuurlijk met kijken welke business methodes er in een class aanwezig zijn. (Getters en setters zijn dat over het algemeen niet!) Voor elk van deze business methodes kijk je naar de input en output. Is de input bijvoorbeeld zodanig dat je er bijvoorbeeld leeftijdscategorieen uit kan halen, 0-17, 18-64 en 65+ bijvoorbeeld, dan heb je al 3 testen die je kunt uitvoeren. Voor elk van deze categorieen 1.

    public void testXTotAchttien() {
	assertFalse(p.x(16));
    }
 
    public void testXVanAchttienTotVijfenzestig() {
	assertTrue(p.x(32));
    }
 
    public void testXVanafVijfenzestig() {
	assertFalse(p.x(76));
    }

De oplettende lezer zal verder zien dat er nog een vierde test uit te halen valt. Die waar de input niet valide is: kleiner dan 0. Zo hebben we ook gelijk de 2 hoofd soorten testen te pakken. Die waar de uitkomst het succes scenario doorloopt en die waar er een exceptionele uitkomst verwacht mag worden.

Vaak zul je meer testen hebben die in die laatste categorie vallen, dan testen die het succes scenario doorlopen. Al was het maar omdat je input vaak van een syntactisch algemener soort kan zijn dan wat je semantisch wil toestaan. In het voorbeeld van zonet, zul je misschien een integer waarde accepteren en in de meeste programmeertalen kunnen die ook negatieve waarden bevatten, of onwaarschijnlijke grote waarden zoals de leeftijd van Methusalem.

Testen die in deze context ook erg nuttig kunen zijn zijn die waar de grenswaarden gebruikt worden. Een veelvoorkomende fout is de off-by-one error en die is hiermee makkelijk te voorkomen. Je begint met het bepalen van de grenzen van je input en test ook de eerste waarde die in de reeks daarvoor en die daarna komen. Bijvoorbeeld 64, 65 en 66 als grenswaarden van de categorie 65-plussers van net.

Nu hebben we net de input al aan verschillende testen onderworpen, maar het gaat toch eigenlijk om dat wat we daar mee doen. Niet ongebruikelijk is dat er een serie keuzes gemaakt moeten worden op basis van die input. Om nog even bij de leeftijden te blijven het volgende versimpelde voorbeeld: iedereen van 18 tot en met 64 moet pensioenpremie betalen, tenzij die persoon werkloos is, iedereen tot 18 is vrijgesteld van betalen van pensioenpremie en iedereen van 65 jaar of ouder is dat ook, tenzij die persoon nog steeds werkt.

Zo opgeschreven is dit een hele brij, maar als je de verschillende keuzes op rij zet die gemaakt moeten worden om te bepalen of iemand pensioenpremie moet betalen, dan wordt het al een stuk simpeler. En omdat je nu weet welke keuzes er gemaakt kunnen worden, weet je ook welke paden in je code je moet testen. Elk pad doorloop je een keer. Zowel voor de positieve als negatieve uitkomsten.

Als je bovenstaande allemaal toepast, zou je denken dat je een heleboel testen moet schrijven. Toch valt dit in praktijk best wel mee. Veel van de technieken zoals er enkele hierboven beschreven zijn, kunnen prima tot gecombineerde testen leiden. Denk bijvoorbeeld aan een boundary check en een partitie check voor de leeftijdscategorie. Test met de waardes 63, 64 en 65 en je hebt al voor beide een (deel-)test.
Om dezelfde reden is ook het expliciet testen van getters en setters niet nodig. Indirect worden ze vaak al getest doordat je ze bij business methodes aanroept. Kom je er achter dat een getter of setter niet aangeroepen wordt, kun je altijd nog besluiten die apart te testen; als je na een dergelijke constatering nog een bestaansrecht voor die methode ziet tenminste.

Wie nog meer van dergelijke handvaten wil toepassen, moet eens zoeken naar de termen Right-BICEPS en CORRECT. Of pak een van de edities van Pragmatic Unit Testing ter hand; er is er een voor JUnit en een voor NUnit, maar 90+ procent van de inhoud is voor beiden gelijk.


Werken met ?
Kijk dan bij onze mogelijkheden voor starters en/of ervaren IT'ers.


Categorieën: Development

Tags: ,


Reacties (2)

  • Thomas Zeeman schreef:

    The reason I, and all others on this company blog, are writing in Dutch is that we are targeting The Netherlands and we want to provide quality information in Dutch.

    There is plenty of English language sites and books with the same or similar information. Besides the already mentioned Pragmatic Unit Testing books, you could try to find the ISEB Test Foundations for a broader perspective on software testing as well as more general applicable techniques.
    Some sites that could be used as a starting point are: http://www.opensourcetesting.org http://junit.org and http://www.softwaretestinghelp.com/

    Geplaatst op 21 oktober 2008 om 17:31 Permalink

  • John schreef:

    It’s probably a cool article, but why do you write in Dutch?

    Geplaatst op 06 oktober 2008 om 16:07 Permalink