Automatiserad GUI-testning för Java EDA270 - Djupstudie J. Assarsson D08, Lunds Tekniska Högskola dt08ja2@student.lth.se 26 februari 2012
Abstract Den här rapporten är en del av en djupstudie i kursen Coaching av programvaruteam på LTH (Lunds tekniska högskola). Syftet med rapporten är att komma fram till om det nns något lämpligt verktyg för att skapa automatiska GUI-test (graska användargränssnitt) för kurser med programmeringsprojekt. Rapporten behandlar olika typer av testningstekniker för GUI. Fyra olika verktyg studeras närmare och utvärderas. Avslutningsvis testas verktyget UISpec4J i ett utvecklingsteam från en kurs med programmeringsprojekt.
Innehåll 1 Inledning 2 2 Bakgrund 2 3 Tekniker 3 3.1 Capture & replay.................................. 3 3.2 Modellbaserad................................... 3 3.3 Event-driven.................................... 3 4 Verktyg 4 4.1 jfcunit........................................ 4 4.2 FEST-Swing.................................... 5 4.3 UISpec4J...................................... 6 4.4 SwingUnit...................................... 7 4.5 Körtider....................................... 7 5 GUI-testning i PVG-kursen 8 5.1 Val av verktyg................................... 8 5.2 Tillämpning..................................... 9 6 Slutsats 9 A Exempel 11 A.1 Exempel-GUI.................................... 11 A.1.1 RegistrationGui.java............................ 11 A.2 jfcunit test..................................... 12 A.3 FEST-Swing test.................................. 14 A.4 UISpec4J test.................................... 17 A.5 SwingUnit test................................... 19 A.5.1 TestRegistrationGui.java.......................... 19 A.5.2 TestRegistrationGui.xml.......................... 21 B UISpec4J patch 22 1
1 Inledning Precis som övriga delar av ett program måste testas, måste så även GUI (Graphical User Interface, på svenska: graskt användargränssnitt). I takt med att GUI har fått en allt mer central roll inom mjukvaruutveckling, har behovet av att kunna testa dem på ett eektivt sätt ökat. Inom agila utvecklingsmetoder är automatiserade enhetstest väldigt viktigt under utvecklingen, detta har bidragit till att intresset för automatiserad GUI-testning också ökat där [3] [5]. Denna djupstudie kommer behandla olika metoder för att skapa automatiska GUI-test. Den kommer även ta upp några olika tekniker som används för att utföra testen. Fokus kommer främst att ligga på olika verktyg för automatiserad GUI-testning, på deras för- och nackdelar. Jag kommer först att undersöka vilka verktyg som nns tillgängliga för GUI-testning för Java. Jag kommer även själv att försöka testa så många av verktygen som möjligt, för att kunna göra en bättre bedömning av dem. Min förhoppning är att jag ska hitta minst ett testverktyg som på ett enkelt och bra sätt gör det möjligt att skapa automatiserade GUItest, som skulle kunna användas i kurser med programmeringsprojekt. Slutligen kommer jag att presentera mina slutsatser av denna djupstudie. 2 Bakgrund Anledningen till denna djupstudie är att jag vill undersöka om det nns något lämpligt verktyg för att skapa automatiska GUI-test i PVG-projekten (programvaruutveckling i grupp). PVG är en projektkurs på LTH. Den är obligatorisk för alla studenter som går andra året på datateknik. Utvecklingsteam bildas av 8-10 stycken andra-års studenter (utvecklare) och coachas av 2 stycken studenter från kursen Coaching av programvaruteam. Syftet med kursen är att ge praktiska erfarenheter av hur man arbetar i ett agilt mjukvaruteam [12]. Projektet utförs utifrån XP (extremeprogramming) metoden. Det består av totalt 6 iterationer, där en iteration är en heldagslabb (8-17). Projektet går ut på att utveckla ett system för tidtagning för olika typer av endurotävlingar (en form av motorsport). Målet med kursen är inte att teamet ska klara av att implementera så mycket funktionalitet som möjligt, utan är att tillämpa de olika XP-praktikerna och att producera kod med hög kvalitet. Inom XP används automatiska enhetstest för att man på ett snabbt och smidigt sätt ska kunna försäkra sig om att den nya koden som läggs till inte förstör någon som tidigare implementerats [9]. Problemet är att GUI:t i många fall inte täcks av denna typ av test, istället testas det manuellt [10]. I ett större projekt är det orimligt att utföra manuell testning varje gång något nytt har implementerats, eftersom det är väldigt tidskrävande. Därför hade det varit önskvärt att kunna använda automatiserade test även för GUI:t. I PVG-projekten ska utvecklarna tillämpa TDD (test-driven-development), för alla delar förutom för GUI:t. Utvecklarna behöver inte använda automatiska test för att testa GUI:t, istället är det tillåtet att utföra testningen manuellt. Förmodligen är anledningen till detta att kursledningen anser att det är för tidskrävande och komplicerat för utvecklarna att skapa automatiska GUI-test. Oftast blir GUI:t inte så stort i projekten, så det går snabbt att testa det manuellt. Jag tycker dock att det kan vara lämpligt att utvecklarna även får lära sig att tillämpa TDD för GUI:t, förutsatt att de har tillgång till ett bra testverktyg. 2
3 Tekniker GUI-testning är ett relativt ungt forskningsområde. Att testa GUI automatiskt är mycket svårare än att testa vanlig kod automatiskt. Problemet är främst att interaktionen med GUI:t bygger på event (händelser) som användaren genererar, t.ex. att användaren klickar på ett visst ställe eller att användaren trycker på en viss tangent. Detta gör det svårare att skapa testen [5] [6]. Det nns era olika typer av tekniker och teorier om hur GUI kan testas. Jag har försökt göra en indelning av tre vanliga tekniker som används. När jag läst olika artiklar om ämnet har det ibland varit svårt att kategorisera en viss teknik, oftast är det kombinationer av olika tekniker. 3.1 Capture & replay Capture and replay [1] är som man hör på namnet att man spelar in (muspekarens rörelser, var man klickar samt knapptryckningar från tangentbordet) när man använder GUI:t och vid ett senare tillfälle kan man spela upp det igen. Detta kan man använda för att testa att GUI:t fortfarande beter sig på samma sätt som det gjorde när man spelade in. Tekniken kallas även bland annat Capture and playback [3] och Record/Playback [4] [5]. Det nns mängder av olika program för den här typen av testning. Oftast är verktygen oberoende av vad för typ av GUI som testat. Vissa av verktygen använder sparade bilder till att veriera att GUI:t beter sig som tänkt. Fördelen med denna typ av verktyg är att det går både snabbt och lätt att skapa testen. Nackdelen är att testprogrammet inte har någon uppfattning om GUI:t, den använder bara koordinater för att hitta rätt. Om GUI:ts utseende förändras eller om GUI:t ritas upp annorlunda på en annan dator måste testen skapas på nytt [2] [5]. 3.2 Modellbaserad Modellbaserad GUI-testning använder abstrakta modeller av GUI:t för att modellera GUI:ts beteende. För att skapa modellen studerar man olika event-öden som kan uppstå [11]. Modellen är en riktad graf där noderna representerar de olika tillstånden som GUI:t kan benna sig i, bågarna representerar olika event. Grafen kan ses som en tillståndsmaskin som ändrar tillstånd beroende på event. Modellen kan sen användas till att generera automatiska testscenarier [8]. Det optimala hade varit att kunna skapa modellerna automatiskt utifrån ett givet GUI. I dagsläget är detta inte möjligt, utan modellen måste skapas förhand. Det bedrivs en hel del forskning på området, vilket kan leda till att det i framtiden utvecklas bättre verktyg som använder denna typ av teknik [2] [8] [11]. 3.3 Event-driven I event-driven GUI-testning drar man nytta av att man känner till hur GUI komponenterna är uppbyggda och fungerar. Denna typ av testning kallas även Programmatic GUI testing [5]. För att komma åt komponenterna används en testrobot som kan identiera de olika komponenterna i ett GUI. Komponenter av samma typ behöver unika namn, för att roboten ska kunna skilja dem åt. När en komponent har hittats, kan roboten simulera användar-event på komponenten [6]. När testet skrivs bestämmer man vilken komponent som 3
ett visst event ska verka på. På så sätt blir denna typ av test mer exibelt och kan anpassas efterhand som GUI:t utvecklas, till skillnad mot t.ex. capture and replay tekniken [5]. Eftersom testet inte agerar blint utan vet vilken komponent som ska användas, kommer testet kunna hantera att GUI:t förändras eller att det inte ritas upp på exakt samma sätt på olika datorer. 4 Verktyg Verktygen är den centrala delen av den här rapporten. I detta kapitlet kommer jag presentera fyra olika testverktyg som jag provat. Följande egenskaper anser jag vara viktiga för att ett verktyg ska vara lämpligt att använda i PVG-projekten: Kompatibelt med Java-swing Kunna köras med JUnit Inte kräver allt för många steg för att få det att fungera Kunna användas med TDD Jag sökte efter olika verktyg och valde ut de fyra som jag trodde var mest lämpade för att användas i PVG-projekten. Gemensamt för alla de fyra utvalda verktygen är att de är event-drivna, de är Java-baserade, de kan användas för att testa Swing GUI och att de kan köras med JUnit. Jag valde att inte testa några Capture and replay verktyg eftersom det då inte är möjligt att tillämpa TDD, samt att jag anser att dessa inte är lämpade för projekt där GUI:t förändras ofta. Jag försökte även hitta lämpliga modellbaserade testverktyg, men hade svårt för att hitta några. Ett av dem jag hittade var GUITAR [17]. Det saknade dock två av de eftersökta egenskaperna. Att det inte kunde användas med JUnit, samt att det krävdes en hel del jobb både för att få det att fungera och för att skapa test. För att kunna göra en bedömning testade jag de utvalda verktygen. I slutet av detta kapitlet har jag gjort en sammanställning av körtiderna för de olika verktygen när de används för att testa ett exempel-gui. 4.1 jfcunit jfcunit är ett Java-baserat ramverk som utökar JUnit [13]. För att kunna använda det krävs att man använder JUnit 3.7 eller högre samt Jakarta RegExp 1.2 eller högre. För att installera jfcunit laddar man ner den senaste versionen av jfcunit.jar samt den senaste versionen av JakartaRegExp.jar och inkluderar dessa i sitt projekt. Alternativt laddar man ner jfcunit som en plugin till Eclipse. För vidare instruktioner se jfcunits hemsida [13]. Testens struktur är ungefär den samma som för vanliga JUnit-test. JUnit 4.x notationer som t.ex. @Test och @Before, ignoreras, istället anger man att en metod är ett test genom att börja metodnamnet med test, för att skapa en before-metod måste metoden heta setup. Detta är samma struktur som används i JUnit 3.x. Annars är det inga större skillnader mellan hur testen skrivs i jfcunit jämfört med JUnit. Utvecklarna kommer känna igen strukturen och bör därför snabbt förstå hur testen är uppbyggda. 4
För att komma åt de olika komponenterna i GUI:t används en NamedCompnentFinder, som hittar namngivna JCompnoent-objekt i GUI:t. När man hittat de komponenter man vill använda, använder man en Helper för att skicka event till komponenten [13]. Därför krävs det att komponenterna man testar har unika namn, samt att man vet vad för typ av JComponent det är. Om man tillämpar TDD, medför detta att man måste bestämma vilken typ av komponenter som ska användas när man skriver testet. Detta kan vara en nackdel eftersom implementation blir på så sätt begränsad av testet. När testen körs visas GUI:t som vanligt, fast all interaktion styrs automatiskt. Det har både sina för- och nackdelar. Fördelarna är att det är lätt att se vad som händer när testet körs. Nackdelarna är att utvecklaren kan påverka testets utfall om hon använder datorn samtidigt som testet körs. Muspekaren styrs dock inte, till skillnad från t.ex. FEST-Swing och SwingUnit. Detta göra att testet inte påverkas av att man yttar muspekaren samtidigt som testet körs. Att köra testen tar en denitivt märkbar tid (se kapitel 4.5). Fördelar JUnit baserat Testen liknar vanliga JUnit-test Man ser när testen körs Nackdelar Möjligheten att påverka testen när de körs Långsamt 4.2 FEST-Swing FEST (Fixtures for Easy Software Testing) är ett Java-baserat testramverk som är kompatibelt med TestNG och JUnit [14]. FEST består av era moduler. En av dessa moduler är FEST-Swing, som är gjord för att testa Swing baserad GUI. För att kunna använda FEST-Swing krävs Java 1.5 eller senare. För att installera FEST-Swing laddar man ner den senaste versionen av fest-swing-x.x.zip, packar upp fest-swing-x.x.jar, fest-assert-x.x.jar, fest-reflection-x.x.jar, fest-util-x.x.jar samt jcip-annotations-x.x.jar och inkluderar dessa i sitt projekt (x.x byts ut mot versionsnumret). Strukturen för testen är samma som för vanliga JUnit-test. Det är notationen som avgör ifall testet ska köras som ett JUnit-test eller som ett TestNG-test. För att testen ska köras som ett JUnit-test använder man samma notationer som för vanliga JUnit-test, vill man däremot använda TestNG används andra notationer [14]. Utvecklarna kommer därför snabbt känna igen sig och förstå hur testen är uppbyggda. För att testen ska fungera, krävs det att komponenter av samma typ har unika namn. Komponenterna som man får ut är av speciella Fixture-typer [6]. Detta medför att implementationen inte är låst till att använda en specik JComponent, vilket t.ex. jfcunit kräver. När testen körs så visas GUI:t som vanligt. All interaktion styrs automatiskt, även muspekaren styrs automatiskt. Det är dock möjligt att röra muspekaren samtidigt som test körs, då kan testet börja interagera med annat än GUI:t som man testar. Som jag skrev tidigare är det både positivt och negativt att GUI:t visas när testet körs. Fördelen är att man ser vad som händer när testet körs. Nackdelen är att man inte kan använda datorn samtidigt. Att köra igenom ett test tar en del tid (se kapitel 4.5). 5
Fördelar JUnit kompatiblet Testen liknar vanliga JUnit-test Man ser när testen körs Nackdelar Möjligheten att påverka testen när de körs Testen kan påverka annat än enbart GUI:t som ska testas Långsamt Uppdelat i fem olika jar-ler 4.3 UISpec4J UISpec4J är kompatibelt med JUnit och TestNG [15]. UISpec4J är väldigt lätt att installera, det enda man behöver göra är att ladda ner den senaste versionen av UISpec4J.jar och inkluderar len till sitt projekt. Du behöver inte själv ange ifall du använder JUnit eller TestNG, detta upptäcker UISpec4J automatiskt. Strukturen för testen är väldigt lik vanliga JUnit-test. JUnit 4.x notationer som t.ex. @Test och @Before, ignoreras. För att ange att en metod är ett test måste metodnamnet börja med test, för att skapa en before-metod måste metoden heta setup. Detta är samma struktur som används i JUnit 3.x. Därför bör utvecklarna snabbt förstår hur testen är uppbyggda. För att kunna skilja på komponenterna namnger man dem [15]. När man hämtar ut en komponent från GUI:t så blir den inkapslad i en speciell UISpec4J-typ, detta påminner väldigt mycket om hur Fixtures i FEST-Swing fungerar. Detta gör att implementationen inte blir lika låst till att använda en specik JComponent, till skillnad från t.ex. jfcunit. Detta är det enda verktyget av de jag testat som inte visade GUI:t när testen kördes. Detta gör att användaren inte kan påverka testet. En annan fördel med detta är att testen körs väldigt snabbt, likt vanliga JUnit-test. Det nns dock nackdelar med att GUI:t inte visas när testen körs. Det kan i många fall vara en fördel att kunna se vad testet gör, framförallt när man håller på att skriva testen eller om man är osäker på att testet beter sig som man tänkt. Ett annat problem som också uppstår på grund av att GUI:t inte visas är att det inte är möjligt att testa om rätt komponent har fokus. Fördelar JUnit kompatibelt Testen liknar vanliga JUnit-test Snabbt Nackdelar Måste patchas för att kunna användas med OpenJDK (se bilaga B) GUI:t visas inte när testen körs 6
4.4 SwingUnit SwingUnit installeras genom att man laddar ner den senaste versionen av swingunit.jar och inkluderar len till sitt projekt. För att kunna köra testen måste man även ladda ner eller skapa en speciell l, TestSetting.properties, och lägga den i projektets rotkatalog. TestSetting.properties innehåller inställningar för hur testverktyget ska fungera t.ex. väntetiden från att ett nytt fönster skapats tills testet ska börja interagera med fönstret. Testen skrivs i XML. I XML-len anges test som scenarier, varje scenario består av en sekvens av interaktioner på GUI:t. I det riktiga testet (testet som kan startas av JUnit) skapar man ett EventPlayer-objekt, till det anger man vilket scenario man vill spela upp [16]. När ett scenario spelas upp, körs scenariots sekvens av interaktioner på GUI:t. Dokumentationen för projektet och framför allt för hur man skriver test har jag inte lyckats hitta på annat sätt än i SwingUnits repositorium på http://java.net. För övrigt verkar det inte nnas någon form av aktivitet kring projektet längre, varken i form av utveckling eller forum. Jag tror att utvecklarna i PVG-kursen skulle tycka att det var väldigt ovant att skriva test i XML-format. Detta i kombination med att det är svårt att hitta dokumentation mm, gör att jag tycker att SwingUnit är direkt olämpligt att använda i PVG-kursen. GUI:t visas när testen körs. Muspekaren och textinmatning styrs automatiskt. Om utvecklaren yttar musen samtidigt som testet körs, kan det leda till att testet börjar interagera med annat än GUI:t som ska testas. På samma sätt sker textinmatningen där muspekaren klickar, vilket kan leda till oönskade situationer. Fördelar JUnit baserart Man ser när testen körs Nackdelar Testen skrivs i XML Möjligheten att påverka testen när de körs Testen kan påverka annat än enbart GUI:t som ska testas Långsamt Dålig dokumentation 4.5 Körtider Eftersom det var stor skillnad mellan körtiderna för de olika testverktygen, tyckte jag att det kunde vara intressant att titta närmare på detta. Jag skapade ett exempel-gui. GUI:t är ett exempel på hur ett tidigt GUI i PVG-projektet kan se ut. Sen försökte jag skapa så likvärdiga tester som möjligt för de fyra olika verktygen. Exempel-GUI:t med tillhörande tester nns samlade i bilaga A. Observera att exemplen enbart är till för demonstrationssyfte och bör inte användas som exempel för hur man skriver bra GUI-tester eller GUI. Jag körde igenom varje test 20 gånger och beräknade en genomsnittstid för en körning. Alla test kördes på samma dator med samma program och inställningar. Syftet med resultatet är inte att ge några exakta siror för hur lång tid det tar att köra ett test, det är mer till för att ge en liten förståelse för tidsskillnaderna mellan de olika testverktygen. 7
Eftersom antalet test i mitt exempel är lågt, kan resultatet vara något missvisande. För att göra testsekvenserna mer verklighetstrogna samt för att kunna jämföra de tidigare resultaten mot något, fyrdubblade jag antalet test. Observera att testen inte körs fyra gånger i rad, utan den första uppsättningen test kopierades och sen klistrades tre kopior av den in i samma testl. På samma sätt som tidigare körde jag testen 20 gånger och beräknade genomsnittstiden. Resultatet nner du i tabellen nedan. Omgång jfcunit FEST-Swing UISpec4J SwingUnit Normal 5.294s 3.456s 0.145s 7.329s Fyrdubbelt 20.405s 13.187s 0.204s 28.308s Från tabellen kan man se att när antalet test fyrdubblades, ökades körtiderna ungefär med en faktor 4, för alla testverktyg förutom UISpec4J. För UISpec4J ökade körtiden ungefär med en faktor 1.4. Skillnaden var att testet fortfarande kördes väldigt snabbt (på under 1s). Anledningen att körtiden inte ökade linjärt, tror jag beror på att den tiden som ett nytt testfall medför är förhållandevis liten jämfört mot en konstant tid för att initiera testet. Framförallt i större projekt kan det vara viktigt att ha körtiden i åtanke då man väljer ett testverktyg. 5 GUI-testning i PVG-kursen 5.1 Val av verktyg Jag använde resultatet från förra kapitlet som underlag för att göra en bedömning av om något av verktygen är lämpade för att användas i PVG-projekten. Jag anser att SwingUnit har era egenskaper som gör det mindre lämpligt att använda i PVG-projekten. Framförallt tycker jag att det är för stor skillnad mellan att skriva testen i XML istället för att skriva dem som vanliga JUnit-test. Dokumentationen och dess åtkomst kan också orsaka problem. Något som jag tycker är direkt negativt med SwingUnit är att det är möjligt att påverka utfallet av ett test, när det körs och att testet till och med kan påverka annat än GUI:t som man testar. FEST-Swing är väl dokumenterat, men kräver att man lägger till inte mindre än fem stycken jar-ler. När man väl har inkluderat alla jar-lerna är det bara att börja skriva test. Det går både snabbt och lätt att skriva testen. Det nns dock en nackdel, nämligen att muspekaren styrs av testen. Detta kan leda till testet börjar interagera med annat än GUI:t som ska testas. FEST-Swing skulle mycket väl kunna vara lämpligt att använda för GUI-testning i PVG-projekten. jfcunit fungerar väldigt bra. Det går snabbt att installera och man kan komma igång med att skriva testen direkt. Den enda direkta nackdelen som jag kan komma på är att man måste specicera vilken typ av JComponent en komponent är. Jag tror att jfcunit kan vara lämpligt att använda för GUI-testning i PVG-projekten. UISpec4J fungerar också väldigt bra. Det går snabbt att installera och det är väldigt lätt att förstå hur testen är uppbyggda. Det är väldokumenterat och det går att hitta många exempel på hur verktyget kan användas. På samma sätt som i FEST-Swing behöver man inte ange vilken JComponent-typ som en viss komponent ska ha. Det enda negativa jag kan se med UISpec4J är att GUI:t inte visas när testet körs. Detta kan göra det svårt att få 8
exakt koll på vad som händer när testet körs. Jag tror att UISpec4J kan vara lämpligt att använda för GUI-testning i PVG-projekten. Sammanfattningsvis är det bara SwingUnit som jag direkt kan utesluta som lämpligt att använda i PVG-projekten. De andra tre har alla sina för- och nackdelar. Helst av allt hade jag velat att samma utvecklingsteam skulle testa alla tre verktygen, för att sen ge deras åsikt om dem. Eftersom det skulle ta för mycket fokus från utvecklarna att sitta och testa alla tre verktygen ck jag nöja mig med att välja ut ett av verktygen som de skulle testa. Sen tidigare har jag erfarenheter av jfcunit. Det största missnöjet med jfcunit har varit att det tar väldigt lång tid att köra testen (i förhållande till vanliga JUnit-test). Eftersom PVG-kursen går ut på att utvecklarna ska tillämpa testdriven utveckling, där de ska köra enhetstesten ofta, blir det jobbigt att vänta upp emot en minut (eller mer) på att testen ska köra klart. Detta kan leda till att utvecklarna inte kör GUI-testen eller än värre att de inte köra några test överhuvudtaget. Kombinationen med att ha GUI-test som tar lång tid att köra och att utvecklarna i PVGprojekten i många fall har problem med att sköta testdisciplinen, tror jag kan leda till ännu sämre testdisciplin. Därför valde jag att teamet som jag vara coach för skulle testa att använda UISpec4J. 5.2 Tillämpning Som jag skrev i det föregående kapitlet valde jag att det teamet som jag var coach i skulle prova att använda UISpec4J i deras projekt. Det var en utvecklare i teamet som ck GUI-testning som spike (hemuppgift). Uppgiften var att undersöka hur man kan använda UISpec4J och skriva några test för det bentliga GUI:t. Jag gav honom inga hjälpmedel förutom en länk till hemsidan för UISpec4J. Utvecklaren gjorde några test inför iterationen. Han tyckte varken det var svårt att installera eller att skriva test för UISpec4J. Det uppstod dock problem när testen skulle köras på skolans studentdatorer (Ubuntu 11.04, OpenJDK 1.10.2 och Eclipse 3.6.1). Problemet är att OpenJDK använder ett annat awt-toolkit. Inställningen är hårdkodad i UISpec4J, därför måste man ändra i källkoden för UISpec4J (se bilaga B). Jag räknar inte med att problemet är något som många PVGstudenter skulle lösa snabbt och lätt. Får de däremot en tydlig beskrivning på hur man löser problemet, tror jag inte att det är några svårigheter för dem att åtgärda det. Alternativt kan utvecklarna förses med en färdigpatchad jar-l med lösningen. Utvecklarna har skrivit några GUI-test under iterationerna. De som skrivit testen har inte stött på några problem. Dock har inte alla utvecklarna i teamet provat på att skriva GUItest. 6 Slutsats Sammanfattningsvis kan jag konstatera att testa GUI är denitivt mer komplicerat än att testa vanlig kod. Det nns dock en del bra verktyg för ändamålet, men de har alla sina för- och nackdelar. Jag hade gärna sett att det fanns något testverktyg där det var möjligt att välja ifall GUI:t skulle visas när det testades eller inte. Jag tror att UISpec4J skulle kunna fungera väldigt bra att använda i PVG-projekten. Även för testdriven utveckling av GUI:t. Jag hade dock behövt mer tid och er team som testat verktyget för att kunna komma fram till ett säkrare resultat. 9
Referenser [1] Andrea Adamoli, Dmitrijs Zaparanuks, Milan Jovic, and Matthias Hauswirth. Automated gui performance testing. Software Quality Journal, 19(4):801839, 2011. [2] Pekka Aho, Nadja Menz, Tomi Räty, and Ina Schieferdecker. Automated java gui modeling for model-based testing purposes. In ITNG, pages 268273, 2011. [3] Malte Finsterwalder. Conference, Automating acceptance tests for gui applications in an extreme programming environment, 2001. [4] Atif Memon. Gui testing: Pitfalls and process. IEEE Computer, 35(8):8788, 2002. [5] Alex Ruiz and Yvonne Wang Price. Test-driven gui development with testng and abbot. IEEE Software, 11(3):5157, 2007. [6] Alex Ruiz and Yvonne Wang Price. Gui testing made easy. IEEE Software, 24:99103, 2008. [7] 29th International Conference on Software Engineering (ICSE 2007), Minneapolis, MN, USA, May 20-26, 2007. IEEE Computer Society, 2007. [8] Xun Yuan and Atif M. Memon. Using gui run-time state as feedback to generate test cases. In ICSE [7], pages 396405. [9] Chromatic. Book, Extreme programming pocket guide. O'Reilly, pages 25-29, 2003. [10] A. Memon, I. Banerjee, and A. Nagarajan. Conference, GUI Ripping: Reverse Engineering of Graphical User Interfaces for Testing. 10th Working Conference on Reverse Engineering (WCRE 2003), Victoria, Canada, November 13-16, 2003. IEEE Computer Society, pages 260269, 2003. [11] Fevzi Belli, Mutlu Beyazt, and Nevin Güler. Event-Based GUI Testing and Reliability Assessment Techniques - An Experimental Insight and Preliminary Results. Conferance 2011 Fourth International Conference on Software Testing, Verication and Validation Workshops, 2011 [12] Kursplaner 2011/2012 EDA260, (2012-02-02), [Online] http://www.ka.lth.se/kursplaner/arets/eda260.html [13] jfcunit User Documentation, (2012-02-02), [Online] http://jfcunit.sourceforge.net/ [14] fest - Fixtures for Easy Software Testing, (2012-02-02), [Online] http://code.google.com/p/fest/ [15] UISpec4J Java/Swing GUI Testing Made Simple!, (2012-02-02), [Online] http://www.uispec4j.org/ [16] Swingunit, (2012-02-02), [Online] http://java.net/projects/swingunit [17] GUITAR Home Page, (2012-02-22), [Online] http://sourceforge.net/apps/mediawiki/guitar/ 10
A Exempel A.1 Exempel-GUI Figur 1: Bild på exempel GUI:t A.1.1 RegistrationGui.java import java.awt.borderlayout; import java.awt.event.*; import java.text.simpledateformat; import java.util.date; import javax.swing.*; public class RegistrationGui extends JFrame { private JTextField startnumbertextfield; private JTextArea latestregistrationstextarea; private JButton registrationbutton; public RegistrationGui() { super(); initcomponents(); this.pack(); this.setvisible(true); private void initcomponents() { JPanel toppanel = new JPanel(); toppanel.setlayout(new BorderLayout()); registrationbutton = new JButton("Registrera"); registrationbutton.setname("registrationbutton"); registrationbutton.addactionlistener(new ActionListener() { @Override public void actionperformed(actionevent arg0) { register(); ); startnumbertextfield = new JTextField(5); 11
startnumbertextfield.setname("startnumbertextfield"); toppanel.add(startnumbertextfield, BorderLayout.CENTER); toppanel.add(registrationbutton, BorderLayout.EAST); latestregistrationstextarea = new JTextArea(); latestregistrationstextarea.setname("latestregistrationstextarea"); latestregistrationstextarea.setrows(5); latestregistrationstextarea.seteditable(false); this.add(toppanel, BorderLayout.NORTH); this.add(latestregistrationstextarea, BorderLayout.CENTER); private void register() { if (!startnumbertextfield.gettext().equals("")) { try { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); latestregistrationstextarea.settext(integer.parseint(startnumbertextfield.gettext()) + "; " + sdf.format(new Date()) + "\n" + latestregistrationstextarea.gettext()); catch (NumberFormatException e) { startnumbertextfield.settext(""); A.2 jfcunit test import java.awt.event.keyevent; import javax.swing.*; import junit.extensions.jfcunit.*; import junit.extensions.jfcunit.eventdata.*; import junit.extensions.jfcunit.finder.namedcomponentfinder; public class TestRegistrationGui extends JFCTestCase { private NamedComponentFinder namefinder; private RegistrationGui gui; private JTextField startnumbertextfield; private JTextArea latestregistrationstextarea; private JButton registrationbutton; public TestRegistrationGui(String name) { super(name); public void setup() { sethelper(new JFCTestHelper()); gui = new RegistrationGui(); namefinder = new NamedComponentFinder(JComponent.class, ""); 12
flushawt(); namefinder.setname("startnumbertextfield"); startnumbertextfield = (JTextField) namefinder.find(gui, 0); namefinder.setname("latestregistrationstextarea"); latestregistrationstextarea = (JTextArea) namefinder.find(gui, 0); namefinder.setname("registrationbutton"); registrationbutton = (JButton) namefinder.find(gui, 0); public void testsingleregistration() { "The latestregistrationtextarea should be empty", "", latestregistrationstextarea.gettext()); gethelper().sendkeyaction( new KeyEventData(this, startnumbertextfield, KeyEvent.VK_1)); gethelper().enterclickandleave( new MouseEventData(this, registrationbutton)); "A registration should be added to the latestregistrationtextarea", "1", latestregistrationstextarea.gettext().split(";")[0]); "The startnumbertextfield should be empty after a registration", "", startnumbertextfield.gettext()); public void testemptyregistration() { "The latestregistrationtextarea should be empty", "", latestregistrationstextarea.gettext()); "The startnumbertextfield should be empty", "", startnumbertextfield.gettext()); gethelper().enterclickandleave( new MouseEventData(this, registrationbutton)); "The latestregistrationtextarea should still be empty", "", latestregistrationstextarea.gettext()); public void testinvalidregistration() { "The latestregistrationtextarea should be empty", "", latestregistrationstextarea.gettext()); gethelper().sendkeyaction( new KeyEventData(this, startnumbertextfield, KeyEvent.VK_A)); gethelper().sendkeyaction( new KeyEventData(this, startnumbertextfield, KeyEvent.VK_B)); gethelper().sendkeyaction( new KeyEventData(this, startnumbertextfield, KeyEvent.VK_C)); "The startnumbertextfield should contain the typed string", "abc", startnumbertextfield.gettext()); 13
gethelper().enterclickandleave( new MouseEventData(this, registrationbutton)); "The latestregistrationtextarea should still be empty", "", latestregistrationstextarea.gettext()); "The startnumbertextfield should be cleared", "", latestregistrationstextarea.gettext()); public void testmultipleregistrations() { "The latestregistrationtextarea should be empty", "", latestregistrationstextarea.gettext()); gethelper().sendkeyaction( new KeyEventData(this, startnumbertextfield, KeyEvent.VK_1)); gethelper().enterclickandleave( new MouseEventData(this, registrationbutton)); "A registration should be added to the top off the latestregistrationtextarea", "1", latestregistrationstextarea.gettext().split(";")[0]); gethelper().sendkeyaction( new KeyEventData(this, startnumbertextfield, KeyEvent.VK_2)); gethelper().enterclickandleave( new MouseEventData(this, registrationbutton)); "A registration should be added to the top off the latestregistrationtextarea", "2", latestregistrationstextarea.gettext().split(";")[0]); gethelper().sendkeyaction( new KeyEventData(this, startnumbertextfield, KeyEvent.VK_3)); gethelper().enterclickandleave( new MouseEventData(this, registrationbutton)); String[] split = latestregistrationstextarea.gettext().split("; \n"); "The top row off the startnumbertextfield should contain the latest registration", "3", split[0]); "The middle row off the startnumbertextfield should contain the second registration", "2", split[2]); "The bottom row off the startnumbertextfield should contain the first registration", "1", split[4]); A.3 FEST-Swing test import static org.junit.assert.*; import java.awt.event.keyevent; import org.fest.swing.core.keypressinfo; import org.fest.swing.edt.*; import org.fest.swing.fixture.*; 14
import org.junit.*; public class TestRegistrationGui { private FrameFixture window; private JTextComponentFixture startnumbertextfield; private JTextComponentFixture latestregistrationstextarea; private JButtonFixture registrationbutton; @BeforeClass public static void setuponce() { FailOnThreadViolationRepaintManager.install(); @Before public void setup() { RegistrationGui frame = GuiActionRunner.execute(new GuiQuery<RegistrationGui>() { protected RegistrationGui executeinedt() { return new RegistrationGui(); ); window = new FrameFixture(frame); window.show(); startnumbertextfield = window.textbox("startnumbertextfield"); latestregistrationstextarea = window.textbox("latestregistrationstextarea"); registrationbutton = window.button("registrationbutton"); @Test public void testsingleregistration() { "The latestregistrationtextarea should be empty", "", latestregistrationstextarea.text()); startnumbertextfield.pressandreleasekey(keypressinfo.keycode(keyevent.vk_1)); registrationbutton.click(); "A registration should be added to the latestregistrationtextarea", "1", latestregistrationstextarea.text().split(";")[0]); "The startnumbertextfield should be empty after a registration", "", startnumbertextfield.text()); @Test public void testemptyregistration() { "The latestregistrationtextarea should be empty", "", latestregistrationstextarea.text()); "The startnumbertextfield should be empty", "", startnumbertextfield.text()); registrationbutton.click(); 15
"The latestregistrationtextarea should still be empty", "", latestregistrationstextarea.text()); @Test public void testinvalidregistration() { "The latestregistrationtextarea should be empty", "", latestregistrationstextarea.text()); startnumbertextfield.pressandreleasekey(keypressinfo.keycode(keyevent.vk_a)); startnumbertextfield.pressandreleasekey(keypressinfo.keycode(keyevent.vk_b)); startnumbertextfield.pressandreleasekey(keypressinfo.keycode(keyevent.vk_c)); "The startnumbertextfield should contain the typed string", "abc", startnumbertextfield.text()); registrationbutton.click(); "The latestregistrationtextarea should still be empty", "", latestregistrationstextarea.text()); "The startnumbertextfield should be cleared", "", latestregistrationstextarea.text()); @Test public void testmultipleregistrations() { "The latestregistrationtextarea should be empty", "", latestregistrationstextarea.text()); startnumbertextfield.pressandreleasekey(keypressinfo.keycode(keyevent.vk_1)); registrationbutton.click(); "A registration should be added to the top off the latestregistrationtextarea", "1", latestregistrationstextarea.text().split(";")[0]); startnumbertextfield.pressandreleasekey(keypressinfo.keycode(keyevent.vk_2)); registrationbutton.click(); "A registration should be added to the top off the latestregistrationtextarea", "2", latestregistrationstextarea.text().split(";")[0]); startnumbertextfield.pressandreleasekey(keypressinfo.keycode(keyevent.vk_3)); registrationbutton.click(); String[] split = latestregistrationstextarea.text().split("; \n"); "The top row off the startnumbertextfield should contain the latest registration", "3", split[0]); 16
"The middle row off the startnumbertextfield should contain the second registration", "2", split[2]); "The bottom row off the startnumbertextfield should contain the first registration", "1", split[4]); @After public void teardown() { window.cleanup(); A.4 UISpec4J test import org.uispec4j.*; import org.uispec4j.interception.windowinterceptor; public class TestRegistrationGui extends UISpecTestCase { static { UISpec4J.init(); private Window window; private TextBox startnumbertextfield; private TextBox latestregistrationstextarea; private Button registrationbutton; public void setup() throws Exception { window = WindowInterceptor.run(new Trigger() { public void run() { new RegistrationGui(); ); startnumbertextfield = window.gettextbox("startnumbertextfield"); latestregistrationstextarea = window.gettextbox("latestregistrationstextarea"); registrationbutton = window.getbutton("registrationbutton"); public void testsingleregistration() { "The latestregistrationtextarea should be empty", "", latestregistrationstextarea.gettext()); startnumbertextfield.typekey(key.d1); registrationbutton.click(); "A registration should be added to the latestregistrationtextarea", "1", latestregistrationstextarea.gettext().split(";")[0]); "The startnumbertextfield should be empty after a registration", "", startnumbertextfield.gettext()); 17
public void testemptyregistration() { "The latestregistrationtextarea should be empty", "", latestregistrationstextarea.gettext()); "The startnumbertextfield should be empty", "", startnumbertextfield.gettext()); registrationbutton.click(); "The latestregistrationtextarea should still be empty", "", latestregistrationstextarea.gettext()); public void testinvalidregistration() { "The latestregistrationtextarea should be empty", "", latestregistrationstextarea.gettext()); startnumbertextfield.typekey(key.a); startnumbertextfield.typekey(key.b); startnumbertextfield.typekey(key.c); "The startnumbertextfield should contain the typed string", "abc", startnumbertextfield.gettext()); registrationbutton.click(); "The latestregistrationtextarea should still be empty", "", latestregistrationstextarea.gettext()); "The startnumbertextfield should be cleared", "", latestregistrationstextarea.gettext()); public void testmultipleregistrations() { "The latestregistrationtextarea should be empty", "", latestregistrationstextarea.gettext()); startnumbertextfield.typekey(key.d1); registrationbutton.click(); "A registration should be added to the top off the latestregistrationtextarea", "1", latestregistrationstextarea.gettext().split(";")[0]); startnumbertextfield.typekey(key.d2); registrationbutton.click(); "A registration should be added to the top off the latestregistrationtextarea", "2", latestregistrationstextarea.gettext().split(";")[0]); startnumbertextfield.typekey(key.d3); registrationbutton.click(); String[] split = latestregistrationstextarea.gettext().split("; \n"); "The top row off the startnumbertextfield should contain the latest registration", "3", split[0]); 18
"The middle row off the startnumbertextfield should contain the second registration", "2", split[2]); "The bottom row off the startnumbertextfield should contain the first registration", "1", split[4]); A.5 SwingUnit test A.5.1 TestRegistrationGui.java Observera att man måste ändra skyddsnivån för attributet latestregistrationstextarea i RegistrationGui, för att testetklassen ska komma åt det. import java.awt.*; import javax.swing.*; import swingunit.extensions.extendedroboteventfactory; import swingunit.framework.*; import junit.framework.testcase; public class TestRegistrationGui extends TestCase { private Scenario scenario; private RobotEventFactory roboteventfactory = new ExtendedRobotEventFactory(); private FinderMethodSet methodset = new FinderMethodSet(); private Robot robot; private RegistrationGui gui; protected void setup() throws Exception { Runnable r = new Runnable() { public void run() { gui = new RegistrationGui(); ; SwingUtilities.invokeAndWait(r); robot = new Robot(); TestUtility.waitForCalm(); String filepath = "TestRegistrationGui.xml"; scenario = new Scenario(robotEventFactory, methodset); scenario.read(filepath); /* * Note: I had to change the protection of the latestregistrationstextarea * attribute in RegistrationGui to be able to do the assertions. */ public void testsingleregistration() throws ExecuteException { EventPlayer player = new EventPlayer(scenario); player.run(robot, "testsingleregistration"); 19
"A registration should be added to the latestregistrationtextarea", "1", gui.latestregistrationstextarea.gettext().split(";")[0]); public void testemptyregistration() throws IllegalStateException, IllegalArgumentException, ExecuteException { EventPlayer player = new EventPlayer(scenario); player.run(robot, "testemptyregistration"); public void testinvalidregistration() throws IllegalStateException, IllegalArgumentException, ExecuteException { EventPlayer player = new EventPlayer(scenario); player.run(robot, "testinvalidregistration"); /* * Note: I had to change the protection of the latestregistrationstextarea * attribute in RegistrationGui to be able to do the assertions. */ public void testmultipleregistrations() throws IllegalStateException, IllegalArgumentException, ExecuteException { EventPlayer player = new EventPlayer(scenario); player.run(robot, "testmultipleregistrations"); String[] split = gui.latestregistrationstextarea.gettext().split("; \n"); "The top row off the startnumbertextfield should contain the latest registration", "3", split[0]); "The middle row off the startnumbertextfield should contain the second registration", "2", split[2]); "The bottom row off the startnumbertextfield should contain the first registration", "1", split[4]); protected void teardown() throws Exception { // Terminate application. Runnable r = new Runnable() { public void run() { if (gui!= null) { gui.setvisible(false); ; SwingUtilities.invokeAndWait(r); gui = null; scenario = null; robot = null; 20
A.5.2 TestRegistrationGui.xml <?xml version="1.0" encoding="utf-8"?> <document componentfinder="a_couple_of_class_and_name_is_equal" windowfinder="class_is_equal_strictly" windowclass="registrationgui"> <!-- *** Unit test scenario for RegistrationGui *** --> <scenario name="testsingleregistration"> <Verify property="text" expected="" name="latestregistrationstextarea" componentclass="jtextarea" windowclass="registrationgui"/> <Click name="startnumbertextfield" componentclass="jtextfield" windowclass="registrationgui"/> <TypeText text="1"/> <Click componentclass="jbutton" name="registrationbutton" windowclass="registrationgui"/> <Verify property="text" expected="" name="startnumbertextfield" componentclass="jtextfield" windowclass="registrationgui"/> </scenario> <scenario name="testemptyregistration"> <Verify property="text" expected="" name="latestregistrationstextarea" componentclass="jtextarea" windowclass="registrationgui"/> <Verify property="text" expected="" name="startnumbertextfield" componentclass="jtextfield" windowclass="registrationgui"/> <Click componentclass="jbutton" name="registrationbutton" windowclass="registrationgui"/> <Verify property="text" expected="" name="latestregistrationstextarea" componentclass="jtextarea" windowclass="registrationgui"/> </scenario> <scenario name="testinvalidregistration"> <Verify property="text" expected="" name="latestregistrationstextarea" componentclass="jtextarea" windowclass="registrationgui"/> <Click name="startnumbertextfield" componentclass="jtextfield" windowclass="registrationgui"/> <TypeText text="abc"/> <Verify property="text" expected="abc" name="startnumbertextfield" componentclass="jtextfield" windowclass="registrationgui"/> <Click componentclass="jbutton" name="registrationbutton" windowclass="registrationgui"/> <Verify property="text" expected="" name="latestregistrationstextarea" componentclass="jtextarea" windowclass="registrationgui"/> <Verify property="text" expected="" name="startnumbertextfield" componentclass="jtextfield" windowclass="registrationgui"/> </scenario> <scenario name="testmultipleregistrations"> 21
<Verify property="text" expected="" name="latestregistrationstextarea" componentclass="jtextarea" windowclass="registrationgui"/> <Click name="startnumbertextfield" componentclass="jtextfield" windowclass="registrationgui"/> <TypeText text="1"/> <Click componentclass="jbutton" name="registrationbutton" windowclass="registrationgui"/> <Click name="startnumbertextfield" componentclass="jtextfield" windowclass="registrationgui"/> <TypeText text="2"/> <Click componentclass="jbutton" name="registrationbutton" windowclass="registrationgui"/> <Click name="startnumbertextfield" componentclass="jtextfield" windowclass="registrationgui"/> <TypeText text="3"/> <Click componentclass="jbutton" name="registrationbutton" windowclass="registrationgui"/> </scenario> </document> B UISpec4J patch Följande ändring måste göras i len org.uispec4j.uispec4j.java för att UISpec4J ska fungera tillsammans med OpenJDK: if ("Linux".equalsIgnoreCase(System.getProperty("os.name"))) { System.setProperty("awt.toolkit", "sun.awt.motif.mtoolkit"); ska ändras till if ("Linux".equalsIgnoreCase(System.getProperty("os.name"))) { System.setProperty("awt.toolkit", "sun.awt.x11.xtoolkit"); 22