Automatische Testen van Java Klassen



Vergelijkbare documenten
Testen van Java code met JML

Correspondentie inzake overnemen of reproductie kunt u richten aan:

Software Test Documentation

Practicumhandleiding. (versie 2010)

Kleine cursus PHP5. Auteur: Raymond Moesker

Geen webservice? Geen probleem!

In BlueJ. Doe onderstaande met muis/menu s:

Cover Page. The handle holds various files of this Leiden University dissertation.

Objectgericht programmeren 1.

Datatypes Een datatype is de sort van van een waarde van een variabele, veel gebruikte datatypes zijn: String, int, Bool, char en double.

Derde deeltentamen Imperatief programmeren - versie 1 Vrijdag 6 november 2015, uur

Programmeren in Java 3

Verslag. Projectteam: 107 Datum: 16 oktober 2008 Project leden: Lennard Fonteijn Harish Marhe Nicoletta Saba Turgay Saruhan Robin Tummers

Software Test Plan. Yannick Verschueren

Vakgroep CW KAHO Sint-Lieven

Tentamen Imperatief en Object-georiënteerd programmeren in Java voor CKI

Die inputs worden op een gecontroleerde manier aangeboden door (test) stubs. De test driver zorgt voor de uiteindelijke uitvoering ervan.

Inleiding Software Engineering! Unit Testing, Contracten, Debugger! 13 Februari 2014!

Informatica. Objectgeörienteerd leren programmeren. Van de theorie met BlueJ tot een spelletje met Greenfoot... Bert Van den Abbeele

BEGINNER JAVA Inhoudsopgave

Abstraheren van modellen

Programmeermethoden. Recursie. week 11: november kosterswa/pm/

Tentamen Object Georiënteerd Programmeren TI januari 2013, Afdeling SCT, Faculteit EWI, TU Delft

Vereiste kennis. 1 Java-editor. 2 Het compileren van een programma

Opmerkingen en vragen aan Ultieme vraag: Hoe beïnvloedt dit de winstkansen?

Probleem met dobbelspel. 2IP05: Programmeren Blok A. 5 spelers,2 dobbelstenen. wstomv/edu/2ip05/ Per ronde werpt elke speler 1

Abstracte klassen & Interfaces

NHibernate als ORM oplossing

Ontwerp van Informatiesystemen

Lessen Java: Reeks pag. 1

Programmeren: Visual Basic

Tentamen Object Georiënteerd Programmeren TI oktober 2014, Afdeling SCT, Faculteit EWI, TU Delft

Uitwerking Aanvullend tentamen Imperatief programmeren Woensdag 24 december 2014, uur

Tentamen Objectgeorienteerd Programmeren TI februari Afdeling ST Faculteit EWI TU Delft

Knowledgeable Referenceable Personable Accountable Scalable

icafe Project Joeri Verdeyen Stefaan De Spiegeleer Ben Naim Tanfous

Programmeermethoden. Pointers. Walter Kosters. week 10: november kosterswa/pm/

Algoritme noteren? Algoritmen voor de computer worden vastgelegd met behulp van een programmeertaal.

Opdracht 4: Overzichtelijker en generieker

Noties Informatica. In java fungeren objecten als een model voor de elementen waarin een probleem kan worden opgesplitst

Programmeren in Java 3

Programmeermethoden. Recursie. Walter Kosters. week 11: november kosterswa/pm/

Modelleren en Programmeren

Beginselen van programmeren Practicum 1 (Doolhof) : Oplossing

Overerving & Polymorfisme

Automated Engineering White Paper Bouw & Infra

INFORMATICA 1STE BACHELOR IN DE INGENIEURSWETENSCAPPEN

HOOFDSTUK 3. Imperatief programmeren. 3.1 Stapsgewijs programmeren. 3.2 If Then Else. Module 4 Programmeren

Algoritmisch Denken: Dodo s Bit Parity

IMP Uitwerking week 13

Tentamen Inleiding Programmeren (IN1608WI), duur van de toets 2 uur Technische Universiteit Delft, Faculteit EWI, Afdeling 2.

H9: Klasse Ontwerp. Richtlijnen Specificaties Multiple inheritence

In deze les. Het experiment. Hoe bereid je het voor? Een beetje wetenschapsfilosofie. Literatuuronderzoek (1) Het onderwerp.

Programmeren in Java les 3

Java Les 3 Theorie Herhaal structuren

Uitwerkingen derde deeltentamen Gameprogrammeren Vrijdag 6 november 2015, uur

Eindtoets. Opgaven. 1 Gegeven is het domeinmodel van figuur 1. Domeinmodel voor betalingen. Eindtoets I N T R O D U C T I E.

NAAM: Programmeren 1 Examen 29/08/2012

Verslag Opdracht 4: Magische Vierkanten

Bellen Zonder Zorgen

Derde deeltentamen Imperatief programmeren - versie 1 Vrijdag 7 november 2014, uur

VBA voor doe het Zelvers - deel 10

Scripting 2 TUTORIAL EEN APP ONTWIKKELEN VOOR ANDROID MET PROCESSING. ellipse(screenwidth/2, screenheight/2, 140,140); DOOR THIERRY BRANDERHORST

Voorbeeld. public class BankRekening {

APPLICATIEBOUW 3E COLLEGE: OBJECT GEORIËNTEERD PROGRAMMEREN, METHODEN, PARAMETERS, SCOPE VAN VARIABELEN. Onderdeel van SmartProducts

Voorbeeldtentamen Inleiding programmeren (IN1608WI), Oktober 2003, , Technische Universiteit Delft, Faculteit EWI, Afdeling 2.

Algoritme noteren? Algoritmen voor de computer worden vastgelegd met behulp van een programmeertaal.

Kennismaken Greenfoot

Objectgeorïenteerd werken is gebaseerd op de objecten die door het systeem gemanipuleerd worden.

Objectgeoriënteerd programmeren in Java 1

Practicumopgave 3: SAT-solver

Derde deeltentamen Imperatief programmeren - versie 1 Vrijdag 9 november 2018, uur

Analyse Programmeertalen

Opdracht 7a. Applicatiebouw 2014/2015

Handleiding JCreator. Inhoud. Een Workspace en een eerste project maken

RCL Arduino Workshop 1

Javascript oefenblad 1

Rekenen: Getallen groep 5 en hoger. Rekenen en schattingen ontdekken. Algebra groep 5 en hoger. Patronen en relaties ontdekken.

Software Test Plan. PEN: Paper Exchange Network Software Engineering groep 1 (se1-1415) Academiejaar

Programmeertechnieken Week 7

Open Source Software. Bart van Dijk

Inhoud Inhoud. Over dit boek 7. 1 Eclipse IDE (Integrated Development Environment) 9. 2 Functionele specificatie 13

Een inleiding in de Unified Modeling Language 67

Domein API Handleiding

case: toestandsdiagrammen

Uitwerking tentamen Analyse van Algoritmen, 29 januari

Een unit test is geen integratie test. Niet het hele systeem, maar onderdelen van een systeem worden getest.

Functioneel programmeren

Transcriptie:

Automatische Testen van Java Klassen Wishnu Prasetya (wishnu@cs.uu.nl) Dit artikel bespreekt de problemen van tradioneel handmatig testen en hoe men tegenwoordig Java klassen automatisch kan testen. Niemand wil een software met veel fouten gebruiken. Niet alleen omdat het vervelend is, maar fouten kunnen ook ernstige gevolgen hebben. Het is daarom belangrijk dat ontwikkelaars hun software testen voor ze de software op de markt zetten. Dit is echter geen makkelijke klus. Elke test is uniek: het controleert hoe de software reageert op een unieke reeks van input. Dat betekent dat men heel veel tests nodig heeft om allerlei mogelijke configuraties en interacties van een software te controleren. De tests zijn vaak met de hand geschreven. Het is dus zeer arbeidsintensief, en dus duur. Hier is een eenvoudig voorbeeld. De lezer kent ongetwijfeld het spel Reversi (ook bekend met de naam Othelo). Het een klein strategiespel van twee spelers op een 8 8 bord. Op iedere beurt legt een speler een schijfje van zijn eigen kleur (zwart of wit) op het bord. De schijven van de tegenstander die door de zet omgesloten raken worden omgezet naar de speler s kleur. Het spel is afgelopen als beide spelers geen zet meer kunnen doen. Met nog een paar andere regels is het toch een vrij complex spel, met circa 10 28 mogelijke posities. In Figuur 1 staan twee screenshots van een on-line versie van dit spel. Figuur 1: Reversi spel. Links is de beginconfiguratie van het spel. Rechts is een mogelijke spelconfiguratie na 46 zetten. Bron: www.artifactinteractive.com.au. Stel dat het spel in Java geïmplementeerd is, en in twee lagen gesplitst: de speellogica laag, en de user-interface laag. Deze splitsing maakt het mogelijk om de lagen afzonderlijk te testen. De speellogica-laag is een miniatuur van wat een bedrijfslogica van een bedrijfsapplicatie is. Het is het brein van een applicatie, dus het meest kritische deel

van de applicatie. In Java gebruikt men vaak JUnit om tests te schrijven. JUnit maakt het schrijven tests en het analyseren van het resultaat eenvoudiger, maar het is geen automatische tool: men moet nog steeds de tests zelf schrijven. @Test t1(){ Reversi R = new Reversi() ; R.move(new Square(3,2)) ; assert R.getSquare(3,2) == WHITE assert R.getSquare(3,3) == WHITE @Test t2(){ Reversi R = new Reversi() ; R.move(new Square(4,2)) ; assert R.getSquare(4,2) == EMPTY ; Figuur 2: twee tests, t1 en t2, op de speellogica van Reversi, geschreven in Java/JUnit. In Figuur-2 staan twee voorbelden van JUnit tests van de speellogica van Reversi. De eerste test t1() doet een zet (wit is verondersteld als de eerste aan de beurt) op het vakje (3,2). Dit is het vakje op de vierde kolom van links en derde rij van onder op het bord. Het controleert vervolgens dat er op dat vakje nu inderdaad een witte schijf zit en dat op het vakje (3,3), die door en zwarte schijf bezet was, nu een witte schijf zit. De tweede test t2() probeert een andere zet, namelijk (4,2). Volgens de regels van Reversi is de zet echter illegaal. De test controleert dus dat op dat vakje inderdaad geen schijf gezet is. Deze twee tests zijn erg eenvoudig. Ongetwijfeld heeft men meer nodig; maar hier is een belangrijke, maar ook lastige, vraag: hoeveel tests heeft men eigenlijk nodig? Reversi heeft naar schatting 10 58 mogelijke speelpartijen. Een partij is een volledige reeks van zetten van beide spelers, vanaf een beginconfiguratie tot het spel eindigt. Het is ondoenbaar om ze allemaal uit te testen. Reversi is maar een klein programma, grotere programma s hebben nog grotere aantallen van configuraties en interacties om te testen. Pragmatisch gezien is het doel van testen dus niet het vinden van alle fouten, maar het vinden van de meeste fouten binnen een redelijke tijd. Het percentage van de delen van een programma die door een test uitgevoerd worden heet de dekking van de test. Hogere dekking geeft meer kans om fouten te vinden. Omdat dekking meetbaar is, is het in de praktijk ook de meest gebruikte criteria van testvolledigheid. Er zijn ook tools om testdekking te meten, bijvoorbeeld Emma en Cobertura. Allebei zijn open source. Emma heeft ook mooi plugins voor Eclipse en Netbeans; zie de screenshot in Figuur-3. Men moet echter goed beseffen dat zelfs 100% dekking geen garantie is voor een foutloos software. Men kunt wel kiezen om fijnere aspecten van zijn programma ook te dekken; daar staat tegenover dat hij meer tests moet schrijven.

Figuur 3: JUnit en Emma in Eclipse. JUnit houdt automatisch bij welke tests slagen of falen. De groene balk linksboven betekent dat ze allemaal slagen. Emma houdt de dekking van de tests bij. Het kleurt de broncode (bovenblad): groene regels zijn al gedekt, rode nog niet. Het maakt ook een handige overzichtslijst (onderblad) van het dekkingspercentage van verschillende klassen en methodes. Eigenlijk dekken de twee tests in Figuur-2 al vele delen van Reversi. De verleiding is groot om nu te stoppen. Men moet juist persistent blijven. Bugs loeren vaak in moeilijk te dekken plekken van de code. Dit is ook waar testen lastig wordt. De tests in Figuur-2 dekken bijvoorbeeld de implementatie van de eindspelregel van Reversi niet. Daarvoor zou men een test moeten schrijven met een lange reeks van zetten die het spel helemaal uitspeelt. De meeste partij is van 64 zetten; het kost veel werk om de zetten te bedenken en om ze op te schrijven. Bepaalde situatie, zoals gelijkspel, is ook erg moeilijk om handmatig te reconstrueren. Een andere lastige situatie is waar een speler zijn beurt moet overslaan omdat hij geen zet kan doen. Dat was niet het enige probleem van handmatig testen. Test codes zoals in Figuur-2 zijn ook fragiel. Stel dat men besluit om de spelregels te veranderen. In bedrijven is dit een realistisch scenario. Een bedrijf kan haar beleid aanpassen. Bedrijven kunnen fuseren. De regering kan regels en wetten veranderen. Deze kunnen allemaal leiden tot aanpassingen in een bedrijfsapplicatie. Na de aanpassingen kunnen helaas deels van de tests onbruikbaar worden omdat ze de applicatie op verkeerde manieren aansturen, of omdat ze de reacties van de applicatie tegen verkeerde waarden vergelijken. Men zou deze tests moeten bijwerken; dit is vaak net zo lastig als ze opnieuw schrijven.

Automatisch Testen Uiteraard, men kan veel kosten besparen door tests automatisch te genereren in plaats van ze handmatig te schrijven. Er is veel onderzoek gedaan op dit onderwerp en leidt al tot tools zoals FindBugs (University of Maryland), JCrasher (Georgia Institute of Technology), Korat (MIT), Agitar (commercieel), SpecExplorer (Microsoft Research), en T2. De laatste is van Universiteit Utrecht, met een unieke feature dat het een Java klasse in haar geheel test. Het test een methode dus niet in isolatie zoals in een puur unit testen. Unit testen is het testen van een unit. Een unit is meestal een functie of een methode. Omdat een unit klein is heeft men beter overzicht van hoe het werkt, en is dus ook beter in staat om het te testen. Voor een object georiënteerde taal komt deze definitie van unit echter te kort. Zie het voorbeeld onder: class Caldendar{ private int day = 1 ; public void nextday() { day++ ; public int getweek() { return day/7 ; public void reset() { day=0 ; De waarde die c.getweek() terug geeft hangt van de toestand van het object c af; dit is op zijn beurt afhankelijk van wat men met c hiervoor deed. Als men c.reset() deed net voor de aanroep c.getweek(), gaat de tweede crashen. Deze fout zal men echter nooit vinden als men getweek in isolatie test. T2 genereert daarom testreeksen. Elke reeks begint met het aanmaken van een doelobject; dit is een object uit de klasse die getest moet zijn. Elke stap in de reeks is een methode aanroep op het doelobject, of een update op een van zijn velden. Op deze manier simuleert T2 interacties tussen methodes. Bovendien controleren methodes in dezelfde reeks ook elkaar, en heeft T2 dus een betere kans om fouten te vinden. Per stap controleert T2 dat er geen assertieovertredingen of andere onverwachte excepties zijn. Afhankelijk van de doelklasse kan T2 duizenden testreeksen binnen één seconde genereren. Dit is heleboel tests met slechts één knopdruk! T2 heeft ook geen probleem om lange testreeksen voor Reversi te genereren en de eerder genoemde lastige spelsituaties te dekken. Meer informatie over T2 kan men in deze website vinden: http://code.google.com/p/t2framework Het is open source met een GPL licentie. Men kan het gebruiken als een stand alone of als een bibliotheek vanuit JUnit. Het werkt dus ook in elke IDE die JUnit ondersteunt (zoals Eclipse of Netbeans). Specificatie-gebaseerd Testen. In handmatige testcode zoals in Figuur-2 gebruikt men vaak concrete waarden om de verwachtingen uit te drukken, zoals in: assert R.getSquare(3,2) == WHITE

Men verwacht dat er in het vakje (3,2) een witte schijf zit. Maar deze verwachting is natuurlijk afhankelijk van de specifieke zetten die men in deze test deed. Als de zetten anders moeten zijn, bijvoorbeeld omdat men de spelregels verandert, zou men zijn verwachtingen ook moeten aanpassen. Dit is, zoals eerder opgemerkt, fragiel. Man kan dit probleem oplossen door formele specificaties te schrijven met een specificatietaal ; het is een taal die speciaal voor dat doel is ontworpen. Voorbeelden: Object Constraint Language (OCL, een onderdeel van UML), Java Modelling Language (JML), of Z. Één van de spelregels van Reversi is dat het spel eindigt als beide spelers geen zet meer kunnen zetten. In OCL kan men dit bijvoorbeeld zo uitdrukken: context: Reversi inv: possiblemoves(black).isempty() and possiblemoves(white).isempty() implies status()!= ONGOING Het beschrijft een voorwaarde, een zogenaamde klasse-invariant, voor de klasse Reversi, namelijk dat het waar moet zijn na elke aanroep op de methodes van Reversi. Een specificatie kan men blijven hergebruiken in alle tests. Als men toch de spelregels aanpast hoeft men alleen de specificaties bij te werken in plaats van dat te moeten op alle tests. Specificaties zijn dus veel robuuster. Bedrijven zijn echter vaak huiverig om specificaties te schrijven. Vaardigheid in een specificatietaal is zeldzaam, dus het is moeilijk om de juiste programmeurs te vinden. Bestaande specificatietalen integreren ook niet volledig met populaire programmeertalen. Dit leidt tot onderhoudproblemen. Het is dus goed voor te stellen dat een bedrijf dit allemaal te riskant vindt. Wat men lijkt te vergeten is dat men specificaties ook in een programmeertaal zoals Java kan schrijven. Het heet in-code specificatie. Het bezwaar is echter dat ze lelijk zijn. Maar wacht even, men kan misschien iets leren van de Embedded Domain Specific Language (EDSL) aanpak. Een domeinspecifieke taal is een (kleine) taal specifiek voor het uitdrukken van uitspraken uit een bepaald domein. In plaats van een aparte vertaler voor de taal te schrijven, die heel veel werk kost, kan men vaak een kleine taal in een rijke taal zoals Java simuleren door een bibliotheek van methodes te schrijven, die de basiswoorden van de taal vormen. Zinnen kan men bouwen door deze woorden te combineren. Specificaties van een applicatie vormen eigenlijk ook een eigen domeinspecifieke taal, en kunnen dus ook embedded in Java gecodeerd. Qua syntax ziet het resultaat redelijk netjes uit (het voorbeeld onder). Echt mooi zoals in OCL zullen ze inderdaad nooit worden, maar daar staat tegenover dat men ook geen integratieprobleem meer heeft. Stel dat men de klasse Reversi de methodes cnt(c), possiblemoves(c), en status()uitbreiden. Deze geven, respectievelijk, het aantal van de schijfjes van de speler c, de collectie van mogelijke zetten van c, en de status van het spel (nog aan de gang, zwart win, etc) terug. Men kan dan zeggen dat ze nu de woordenschat zijn waarmee men de specificaties van Reversi gaat schrijven. De spelregel die eerder in OCL was kan men nu ook in Java schrijven; het ziet er bijna net zo abstract als de OCL versie: boolean classinv(){

if (possiblemoves(black).isempty() && posiblemoves(white).isempty()) return status()!= ONGOING ; else return true ; De eerder genoemde tool T2 controleert ook klasse-invarianten. Dus het begrijpt specificaties zoals boven. Hier is nog een: boolean classinv(){ if (status() == BLACKWIN) return (cnt(black)>cnt(white)) ; else if (status() == WHITEWIN) return (cnt(black)<cnt(white)) ; else return (cnt(black)==cnt(white)) ; Deze klasse-invariant codeert de spelregel die de winaar van het spel bepaalt. Op Zoek naar 100% Dekking Zou een automatische tool zoals T2 100% testdekking kunnen leveren? In principe niet. Onderzoekers noemen dit probleem onbeslisbaar. Dat betekent dat geen computer het voor alle gevallen kan oplossen, ongeacht hoe slim men de computer maakt. Een tool kan veel werk besparen, maar het is hoe dan ook geen volledige vervanger van handmatige tests. Wel blijft het zeker moeite waard om een tool slimmer te maken, zodat het voor meer gevallen betere dekking geeft. De basismachine van T2 genereert testreeksen op random basis. Dit is de makkelijkste en snelste manier. Deze levert vaak 70-80% dekking. Voor meer dekking zou men gerichter moeten zoeken. Maar hoe het precies is, is voor de onderzoekers van Universiteit Utrecht nog een open vraag. In een laatst experiment probeert men klassen te instrumenteren om de executie paden van een test bij te houden. Zo kan men een groep van testreeksen selecteren die het dichtste bij een nog ongedekte deel van een methode komen. Met een bepaalde heuristiek worden de parameters van deze testreeksen gevarieerd om nieuwe reeksen te maken. Dit werkte heel goed, maar de algoritme is momenteel nog niet snel genoeg. Overal in de wereld is het dekkingprobleem een hot onderzoekitem. Men probeert van alles: adaptieve random, symbolische executie, genetische algoritmen. Er wordt dus veel gedaan, en hopelijk komt er ook spannende doorbraken.