Mutationstestning av Java-mjukvara Emil Aura 33417 Kandidatavhandling Handledare: Dragos Truscan & Jerker Björkqvist Datateknik Institutionen för informationsteknik Åbo Akademi 1 april 2012 Sammandrag Dagens samhälle har höga krav på fungerande mjukvara, vilket medför att testning utgör en mycket viktig del av utvecklingsprocessen. För att säkerställa att denna mjukvara fungerar enligt givna direktiv är tester högkvalitativa tester ett måste. Denna avhandling kommer att behandla mutationstestning, vilket är ett sätt att utvärdera testernas kvalitet. Första delen koncentrerar sig på teorin bakom mutationstestning samt allmänna principer, medan den andra delen tar upp mutationstestning av Java-mjukvara. Slutligen görs en jämförelse av diverse verktyg som finns tillgängliga för att underlätta mutationstestning av Javamjukvara Den teoretiska delen inleds med en presentation av grundläggande begrepp inom ramen för mutationstestning, så som mutationsoperatorer, ekvivalenta mutanter och mutationsvärde, för att sedan gå vidare till själva mutationsprocessen samt Javaspecifik teori. Teorin beskrivs av kodexempel skrivna i Java. Nyckelord: Mutationstestning, mutationsoperatorer, Java, MuJava, Javalance, Judy II Innehåll Sammandrag II Innehåll III Figurer IV Tabeller V 1 Introduktion 1 2 Bakgrund 2 2.1 2.2 Testning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 2.1.1 White-box-testning . . . . . . . . . . . . . . . . . . . . . . 2 2.1.2 Testningsnivåer . . . . . . . . . . . . . . . . . . . . . . . . 3 Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2.2.1 5 JUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Mutationstestning 7 3.1 Mutationsoperatorer . . . . . . . . . . . . . . . . . . . . . . . . . 7 3.2 Ekvivalenta mutanter . . . . . . . . . . . . . . . . . . . . . . . . . 8 3.3 Mutationsvärde . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 3.4 Processen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 4 Mutationstestning i Java 11 4.1 Mutationsoperatorer . . . . . . . . . . . . . . . . . . . . . . . . . 12 4.2 Verktyg för mutationstestning . . . . . . . . . . . . . . . . . . . . 12 4.2.1 MuJava och MuClipse . . . . . . . . . . . . . . . . . . . . 13 4.2.2 Javalanche . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 4.2.3 Judy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 5 Avslutning 18 Litteratur 19 A Bilaga 1 21 III Figurer 2.1 V modellen för testning . . . . . . . . . . . . . . . . . . . . . . . . 4 3.1 Mutationsprocessen . . . . . . . . . . . . . . . . . . . . . . . . . . 10 4.1 Indelning av publikationer angående mutationstestning utgående från programmeringspåk de behandlat. . . . . . . . . . . . . . . . 11 A.1 MuClipse GUI för generering av mutanter . . . . . . . . . . . . . 21 A.2 Lista över genererade mutanter i MuClipse GUI . . . . . . . . . . 22 A.3 Jämförelse av mutant och original i MuClipse . . . . . . . . . . . 22 A.4 MuClipse GUI exekvering av mutanterna mot testfallen . . . . . . 23 IV Tabeller 2.1 Enhetstest i JUnit . . . . . . . . . . . . . . . . . . . . . . . . . . 6 3.1 Mutationsoperatorer på metodnivå . . . . . . . . . . . . . . . . . 8 3.2 Sex olika mutanter . . . . . . . . . . . . . . . . . . . . . . . . . . 9 4.1 Mutationsoperatorer på klassnivå . . . . . . . . . . . . . . . . . . 13 V 1 Introduktion På Software QA and Testing Resource Centers hemsida [1] listas några färska exempel på hur en liten mjukvarubugg kunnat få stora mjukvarusystem att fungera inkorrekt. På listan hittas bl.a betygssystem som gett felaktiga vitsord åt två miljoner studerande och massor av system som gett ut hemlig information om privatpersoner. Några av dessa buggar skulle antagligen ha varit möjliga att upptäcka i ett tidigare skede och på så viss undvika katastrofer, om man testat dem bättre. Utvecklingen går visserligen mot det bättre. I takt med att mjukvarusystemen blir allt större och mer komplexa, tvingas även mjukvaruprocesserna att satsa mer på testning och kvalitetssäkring. Ett av de stora problemen är att bestämma hur mycket testning som krävs. Vanligtvis mäts mängden testning i täckningsgrad, det vill säga hur stor del av koden som körs av testerna. Utvecklarna bestämmer sedan ett tröskelvärde och slutar testa när detta värde är uppnått. Mutationstestning bidrar med ett koncept som skiljer sig helt från det vanliga sättet att mäta täckningsgraden. Genom att införa fel i programkoden av programmet som skall testas kan testernas förmåga att märka av felaktigt beteende testas. Tanken är att testerna skall försöka upptäcka så många av dessa mutanter av originalkoden som möjligt och utgående från detta antal beräkna en täckningsgrad. Målet med denna avhandling är inte att utveckla något system för mutationstestning utan att ge läsaren en inblick i vad mutationstestning är och hur det kan tillämpas för att framställa högkvalitativa tester samt att presentera några av de verktyg som redan finns till förfogande för att utföra mutationstestning. 1 2 Bakgrund 2.1 Testning Dagens samhälle är beroende av programvara, allt ifrån små system i konsumentelektronik till stora system som bygger upp samhället så som finanssystem, bokningssystem och trafiknät. Ju större ansvar dessa system har desto viktigare är det att de fungerar felfritt. I och med att dessa system blir mer avancerade ökar betydelsen av programvarutestning och testning är därför en mycket viktig del i utvecklingen av programvara. För att underlätta testning finns olika metoder för hur det utförs. Testen kan delas upp på olika nivåer och ha olika ansvarsområden.[2] Det är omöjligt att genom testning försäkra sig om att ett program är helt felfritt. Antalet möjliga indata samt utdata är mycket stort, och så är även antalet möjliga vägar genom programmet. Att försöka testa dem alla vore extrem resurskrävande och priset för testning ökar därav exponentiellt mot mängden testning, samtidigt som antalet fixade buggar minskar. En gyllene medelväg bör därför hittas.[3] 2.1.1 White-box-testning White-box-testning har som mål att testa programmets interna struktur genom att t.ex försöka få varje påstående i programmet att köras minst en gång. Detta är känt som uttömmande testning av vägar och kan liknas vid uttömmande testning av indata vid black-box-testning, som försöker gå igenom all potentiell indata och verifierar utdata. 2 Att gå igenom alla möjliga vägar genom programmet kan dessutom inte ses som ett tillräckligt krav för att programmet skall vara fullständigt testat. För det första kan antalet möjliga vägar vara nästintill oändligt många och för det andra kan ett program som alla vägar testats i fortfarande vara fullt av fel. Till detta finns tre förklaringar. Programmet måste inte möta sina specifikationer, det kan med andra ord vara ett fullkomligt felfritt program, men som dessvärre utför fel uppgift. Vidare kan vägar genom programmet helt saknas eftersom white-boxtestning endast testar befintliga vägar men söker inte efter obefintliga. Slutligen så kan programmet ha datakänsliga fel som uttömmande testning av vägar inte upptäcker, exempelvis följande java program.[4] 1 i f ( a−b < c ) 2 System . out . p r i n t l n ( " a−b < c " ) ; Programmet skall jämföra två värden och se ifall skillnaden mellan dessa är mindre än ett förut bestämt värde. För att detta skall göras korrekt borde givetvis det absoluta värdet av de två talens differens jämföras. Detta är ett problem som uttömmande testning av vägar kanske inte observerar.[4] 2.1.2 Testningsnivåer Testningsnivåer kan även delas upp enligt mjukvaruutvecklings olika aktiviteter: krav och specifikationer, design eller själva källkoden. Denna uppdelning kan ses ur figur 2.1. Det rekommenderas att skriva tester för respektive aktivitet i aktivitetens utvecklingsskede, även om programmet inte kommer att vara exekverbart förrän i implementationsskedet. Detta görs för att fel som annars skulle gå obemärkta kan uppmärksammas under planeringen av testerna. [2] Enhets- och modultestning Enhetstestning är den lägsta nivån av testning och den har som uppgift att testa de minsta enheterna av implementeringen, i Java betyder detta klasserna. Användaren verifierar funktionaliteten av dessa enheterna utan att beakta resten av systemet.[6, 2] Med hjälp av verktyg såsom JUnit kan testerna packas med med 3 Figur 2.1: V modellen visar vilka testningsnivåer som motsvarar vilken aktivitet. [5] källkoden och enhetstestning sköts därför oftast av samma person som skrivit koden.[2] Integrationstestning Integrationstestning baserar sig på enhetstestning och därför antas att enhetstestning är gjord när integrationstestningen tar vid. Den fokuserar på att testa interaktioner mellan enheterna testade av enhetstestningen och görs oftast i samband med implementering av koden i programmerarens utvecklingsmiljö.[7] Systemtestning Systemtestning utförs efter att integrationstestningen slutförts. Testerna skrivs dock oftast redan i ett tidigt skede av utvecklingsprocessen, eftersom de baserar sig på systemets kravspecifikationer, vilka oftast är tillgängliga i ett tidigt skede. Systemtestning skiljer sig från integrationstestning i det att det ser på funktionaliteten av hela systemet, till skillnad från integrationstestning som testar strukturen.[7] 4 Acceptanstestning Acceptanstestning undersöker ifall systemet inte motsvarar klientens krav och planeringen av acceptanstest utförs helst av eller i samarbete med slutanvändaren. Testerna är mycket användarfokuserade och utförs gärna i en miljö liknande distributionsmiljön. [7] 2.2 Java Javas designmål att vara: enkelt, objektorienterat, distribuerat, robust, dynamiskt, portabelt, effektivt, säkert, stöda multitrådning och vara arkitektur neutralt [8] har gjort det till ett av de mest använda programmeringsspråken nuförtiden. Därför kommer denna avhandling att koncentrera sig på Java, även om testning och mutationstestning likaväl kan tillämpas på andra programmeringsspråk. 2.2.1 JUnit Ett av de mest använda ramverken för testning av Javaapplikationer är JUnit som är ett ramverk med öppen källkod gjort för att automatisera upprepad testning. JUnit är skrivet av Erich Gamma and Kent Beck, vilka även var med i Gang of Four som skrev boken “Design Patterns”. Junit används i stor utsträckning inom industrin och kan användas direkt från kommandotolken eller via en integrerad utvecklingsmiljö så som Eclipse.[9] JUnit fungerar med hjälp av testmetoder vars uppgift är att evaluera villkor (assertions) vars resultat sedan presenteras för användaren. Tabell 2.1 visar ett exempel på ett enhetstest för koden i tabell 3.2. Raden “assertEquals (2, new Calc().Min(2,3));” fungerar som testmetod och dess uppgift är att verifiera utdata från funktionen Min() mot det väntade resultatet, i detta fall 2. Ifall dessa två är lika är testet godkänt och programmet fungerar enligt specifikationen. [9] 5 1 2 3 4 5 6 7 8 9 Test Klass 1 import o r g . j u n i t . Test 2 import s t a t i c o r g . j u n i t . A s s e r t . ∗ ; 3 public c l a s s C a l c T e s t { 4 @Test 5 public void t e s t M i n ( ) { 6 assertEquals (2 , 7 new Calc ( ) . Min ( 2 , 3 ) ) ; 8 } 9 } 10 public c l a s s Calc { s t a t i c public int Min ( int a , int b ) { int minVal ; minVal=a ; i f ( b<a ) { minVal=b ; } return minVal } } Tabell 2.1: Enhetstest i JUnit. 6 3 Mutationstestning Principen för mutationstestning är att genom att införa fel i programkoden producera mutanter av denna. Dessa mutanter körs sedan mot testsviten, vars förmåga att urskilja förändringarna i koden då testas. Principen nämndes för första gången år 1971 i Richard Liptons’ vetenskapliga publikation “Fault Diagnosis of Computer Programs”.Det dröjde dock ända till slutet av 1970 talet förrän det börjades forskas mer inom området och DeMillo, Lipton och Saywards rapport[10] brukar ses som den första inflytelserika publikationen. Mutationstestning har sedan dess tillämpats på både källkod (Programmutation) samt mjukvaruspecifikationer (Specifikationsmutation). Programmutation hör till white-box-testning medan specifikationsmutation hör till black-box-testning.[11] Denna avhandling kommer att koncentrera sig på programmutation. 3.1 Mutationsoperatorer Mutationsoperatorer är regler som beskriver syntaktiska förändringar i koden och som har som avsikt att imitera ofta förekommande fel och missar från programmeraren. Dessa förändringar åstadkoms genom insättnings-, borttagningsoch bytes-operatorer. Ett effektivt test skall märka av dessa små förändringar eftersom de kan innebär radikala förändringar i programmets funktionalitet. Figur 3.2 visar ett java program med sex muterade rader där varje muterad rad motsvarar ett nytt program. Mutanterna 1, 3 och 5 byter ut en variabelreferens mot en annan medan mutant 2 byter ut en relationsoperator. Mutanterna 4 och 6 är specialoperatorer som inte kommer att beaktas i denna avhandling.[12, 11] 7 Mutationsoperatorerna måste designas noga för att vara effektiva. Väldefinierade mutationsoperatorer kan därmed resultera i mycket effektiv testning, medan dåliga endast resulterar i ineffektiva tester. De derivat av det ursprungliga programmet som uppstår efter att mutationsoperatorerna har tillämpats kallas för mutanter av det ursprungliga programmet. Två problem som måste beaktas vid design av mutationsoperatorer är: ifall fler än en operator kan tillämpas per mutant och ifall varje möjlighet att tillämpa operatorn skall tas? Alltså skall en mutant innehålla endast en förändring eller skall flera tillåtas? Forskning har visat att det effektivaste sättet är att använda endast en operator per mutant[2] Tabell 3.1 listar några av de mutationsoperatorer som mutationssystemet muJava använder. Dessa är grundläggande mutationsoperatorerna som används både inom objektorienterad och procedurell mutationstestning. Mutationsoperator AOR AOD AOI ROR COR COD COI SOR LOR LOD LOI ASR Beskrivning Ersättning av aritmetisk operator Radering av aritmetisk operator Insättning av aritmetisk operator Ersättning av relationsoperator Ersättning av villkorsoperator Radering av villkorsoperator Insättning av villkorsoperator Ersättning av skiftoperator Ersättning av logisk operator Radering av logisk operator Insättning av logisk operator Ersättning av tilldelningsoperator Tabell 3.1: Metodnivå-specifika mutationsoperatorer. [12] 3.2 Ekvivalenta mutanter Ett av de största problemen med mutationstestning är mutanter som är funktionellt ekvivalenta med ursprungsprogrammet. Detta innebär att mutanten i alla scenarion kommer att ge samma utdata som det ursprungliga programmet. Mutant nummer 3 i Figur 3.2 är ett exempel på en ekvivalent mutant. Intuitivt har A och minVal samma värde på mutationsplatsen så ett byte gör ingen skillnad. Att försöka bevisa att en mutant är ekvivalent är oftast ett oavgörbart problem även om vissa ekvivalenta mutanter kan bevisas med hjälp av mjukvara.[2] 8 Ursprunglig metod int Min (int A, int B) { int minVal; minVal = A: if(B<A) { minVal = B; } return (minBal); } //end Min Metoden med mutanter int Min (int A, int B) { int minVal; minVal = A; ∆ 1 minVal = B; if(B<A) ∆ 2 if (B>A) ∆ 3 if (B < minVal) { minVal = B; ∆4 Bomb(); ∆5 minVal =A; ∆6 minVal = failOnZero (B); } return (minVal); } // end Min Tabell 3.2: Sex olika mutanter [2] 3.3 Mutationsvärde För att kunna ge ett mätbart resultat över kvaliteten på testerna räknas ett mutationsvärde ut. Detta värde baserar sig på antalet döda mutanter dividerat med alla mutanter förutom de som visar sig vara ekvivalenta, formell3.1.[13] Med döda mutanter avses mutanter vars utdata skiljer sig från ursprungsprogrammet.[2, 14, 12] mutationsvärde = |döda mutanter| |totala antalet mutanter| − |ekvivalenta mutanter| (3.1) Ett mutationsvärde på 1,0 innebär att testerna lyckats döda alla mutanter och uppsättningen av tester anses vara adekvat. Att sträva efter ett mutationsvärde på 1,0 är dock inte rimligt, eftersom testerna mycket sällan lyckas döda alla mutanter. Därför bör ett tröskelvärde, som fungerar som det minsta godkända värdet, definieras.[2] 3.4 Processen Mutationsprocessen mäter kvaliteten på testerna, själva testningen är en sidoeffekt. I praktiken kommer programmet ändå att testas grundligt. Ifall programmet innehåller ett fel, finns det oftast några mutanter som endast kan dödas av samma test som uppmärksammar detta fel. [2, 14] Figur 3.1 illustrerar mutationsprocessen. De heldragna rektanglarna represente- 9 rar steg som sköts av mutationssystemet och är därmed automatiserade, medan de streckade rektanglarna fodrar mänsklig aktivitet och är därmed manuella. Systemet tar som indata koden för ursprungsprogrammet och genererar en mutant per mutationsoperator. Till följande tas testsviten som indata och varenda testfall körs mot originalkoden varefter utvecklaren verifierar att utdata är korrekt. Ifall utdatan visar sig vara felaktig har en bugg hittats. Utvecklaren måste då förbättra koden och sedan köra om testfallen tills utdata är korrekt. När så är fallet kan processen fortgå genom att exekvera varenda testfall mot mutanterna. De mutanter vars utdata avviker från ursprungsprogrammet markeras som döda. De döda mutanterna kommer inte att exekveras mot senare testfall. Vi detta skede räknas mutationsvärdet ut och ifall det av utvecklaren definierade tröskelvärdet uppfylles kan processen avslutas. I annat fall måste de fortfarande levande mutanterna analyseras och eventuella ekvivalenta mutanter avlägsnas. Utvecklaren kan nu även förbättra testerna varefter processen börjar om igen med att de nya testerna körs mot originalprogrammet och dess utdata verifieras. [2, 14] Figur 3.1: Processen för mutationstestning. Heldragna boxar representerar automatiserade steg och streckade manuella. [14] 10 4 Mutationstestning i Java Ur figur 4.1 ses att största delen av den forskning som gjorts angående mutationstestning har gjorts i området för programmutation och cirka 50 procent i Java, C och Fortran. Java har numera störst andel eftersom Fortrans popularitet avtagit avsevärt. Eftersom effektiviteten hos mutationstestning baserar sig på de Figur 4.1: Indelning av publikationer angående mutationstestning utgående från de programmeringspåk de behandlar. [11] fel som mutationsoperatorerna representerar, är mutationsoperatorernas kvalitet mycket viktigt vid mutationstestningen. Vanligtvis tillämpas mutationstestning på procedurella program, vilka skiljer sig mycket från de objektorienterade. Metodernas kroppar är oftast betydligt kortare vid objektorienterade program vilket leder till ökad interaktion sinsemellan. Objektorienterad programmering använder sig dessutom, till skillnad från procedurella program, av inkapsling, arv, och polymorfism. [12, 11] 11 4.1 Mutationsoperatorer På grund av Javas annorlunda struktur räcker inte de mutationsoperatorer till, som designats för procedurella språk. Därför har nya operatorer skapats för att kunna utnyttja Javas funktioner till fullo.[12] Dessa operatorer kan läsas ur tabell 4.1. Från tabellen ses även att dessa delas in i fyra olika grupper: inkapsing, arv, polymorfism, och Java-specifika beroende på vilken språklig funktion de behandlar. Åtkomstnivåerna för variabler och metoder ignoreras ofta vid designtillfället och kan därmed leda till problem i implementationsskedet. Denna ignorans kan förklaras med dålig kunskap om semantiken för olika åtkomstnivåer. Felaktig användning av inkapsling behöver inte direkt leda till problem, men kan göra så när klassen integreras med andra klasser. MuJava testar detta genom att ändra åtkomsttypen för variabler, för att styra utvecklaren att skriva korrekt kod.[15] Arv är en mycket kraftfull abstraktionsmekanism, men felaktig användning kan leda till många fel. MuJava behandlar flera olika aspekter av arv: variabelskuggning, metod-överskuggning, användningen an nyckelordet super och definition av konstruktorer. Detta görs bland annat genom att lägga till eller radera super.[15] Polymorfism medför att ett objekts typ kan vara variera. Dess typ kan vara alla de typer som dess subklasser är. Därför måste alla möjliga typer testas och detta görs genom att ändra på typkonverteringar mellan över och underordnade klasser.[15] De Javaspecifika mutationsoperatorerna försöker härma fel som programmerare ofta gör och som är typiska för java. Exempelvis fel användning av nyckelorden static och this.[15] 4.2 Verktyg för mutationstestning Det finns ett flertal verktyg som förenklar mutationsverktyg i java, varav de flesta är små universitetsprojekt. Denna avhandling har behandlat tre av dessa: MuJava, Javalanche och Judy. 12 Funktion Inkapsling Mutationsoperator Beskrivning AMC Ändring av åtkomsttyp IHD Radering av gömd variabel IHI Insättning av gömd variabel IOD Radering av överskuggande metod IOP Flytning av överskuggande metodanrop Arv IOR Namnbyte på överskuggande metod ISI Insättning av nyckelordet super ISD Radering av nyckelordet super IPC Radering av explicit anrop av överordnad konstruktor new metodanrop med underordnad klasstyp PNC PMD Underordnad variabeldeklaration med överordnad klasstyp PPD Parameter-variabeldeklaration med underordnad klasstyp PCI Insättning av typkonvertering PCC Ändring av typkonvertering Polymorfism PCD Radering av typkonvertering PRV Referenstilldelning med annan kompatibel typ OMR Ändring av innehåll i överladdad metod OMD Radering av överladdad metod OAC Ändring av ordningen på argument JTI Insättning av nyckelordet this JTD Radering av nyckelordet this JSI Insättning av static JSD Radering av static JID Radering av initiering av medlemsvariabel Java-specifika JDC Skapande av javas standardkonstruktor EOA Ändring av referens och tilldelningsinnehåll EOC Byte av referens och innehålls jämförelse EAM Ändring av accessmetod EMM Ändring av modifieringsmetod Tabell 4.1: Mutationsoperatorer på klassnivå som används av MuJava De två viktigaste aspekterna att beakta vid jämförelse av mutationsverktyg är vilka mutationsoperatorer då stöder, samt vilken metod de använder vid mutation. Operatorerna är viktiga dels för att de avgör hur omsorgsfullt testerna utvärderas men även för att deras antal kan ha en avgörande inverkan på exekveringstiden. [16] 4.2.1 MuJava och MuClipse MuJava MuJava, (Mutation System for Java) stöder hela processen för mutationstestning, vilket innefattar generering av mutanter, analys av mutanter och exekvering av mutanter mot testfall skrivna av utvecklaren. Projektet är ett samarbete mellan två universitet: Korea Advanced Institute of Science and Technology (KAIST) 13 i Sydkorea och George Mason University i USA. Den första versionen av MuJava släpptes är 2003 och den senaste versionen 2008. Programmet kan laddas ner från respektive universitets hemsida. [17] Mujava kan delas upp i tre komponenter: mutantgeneratorn, mutantpresenteraren samt mutantexekveraren. Generatorn ger användaren möjlighet att själv välja vilka filer som skall muteras samt vilka mutationsoperatorer som skall användas. Mujava stöder både klass-specifika samt metod-specifika operatorer. Mutantpresenteraren visar sedan hur många mutanter av respektive typ som har genererats. Det är också möjligt för användaren att se vilka ändringar som gjorts i koden, vilket kan underlätta vid analys av ekvivalenta mutanter samt vid utveckling av tester för att döda svår-dödade mutanter. Mutantexekveraren ansvarar för att köra alla de av generatorn genererade mutanterna mot de av användaren specificerade testfallen. Resultaten av körningen, det vill säga mutationsvärdet, skrivs sedan ut. Mujava gör muteringarna direkt i java-bytekoden för att undvika dyr omkompilering. [12] Nackdelarna med MuJava är de facto att det inte kan köras via kommandotolken, vilket förhindrar satsvis bearbetning av en stor mängd data. Dessutom saknar det integration med JUnit samt Ant eller Maven, vilket visserligen delvis kan åtgärdas tack vare MuClipse.[16] MuClipse MuClipse är ett insticksprogram för Eclipse som fungerar som en bro mellan utvecklingsmiljön Eclipse och MuJava. Det har utvecklats av Benjamin Hatfield Smith, men den aktiva utvecklingen lades dessvärre ned 29.9.2011. Utvecklingen av MuClipse baserar sig på MuJava och dess uppgift är att förse användaren med ett grafiskt användargränssnitt via Eclipse samt möjliggöra användning am Mujava med JUnit. MuClipse har samma funktioner som MuJava och kan därför långt delas upp i samma tre komponenter. Figur A.1 visar hur användaren, i körningsalternativen i Eclipse, kan välja vilken fil som skall muteras och vilka mutationsoperatorer som skall användas. Med “Traditional mutants” menas här 14 det som tidigare i avhandlingen gått under namnet “mutanter på metodnivå”. Resultaten av körningen sparas sedan i en mapp “result” och användaren kan se de genererade mutanterna som en lista i en skild vy “Mutants and Results” i Eclipse figur A.2. Användaren kan klicka på mutanterna i denna lista för att öppna vyn “Compare Mutants” figur A.3 var man kan se skillnaderna mellan originalkoden och mutanten. De genererade mutanterna exekveras mot testfallen med hjälp av ytterligare ett nytt körningsalternativ i Eclipse figure A.4. Här kan användaren välja vilken testfil som skall köra och vilken fils mutanter den skall testa. Det finns även möjlighet att välja en över tidsgräns för hur länge en mutant skall köras innan den anses ha fastnat i en oändlig loop. Resultaten av körningen skrivs sedan till en fil i mappen “result” och listan i figur A.2 uppdateras med status “killed” eller “alive”.[18] Även om MuClipse underlättare användandet av MuJava genom integration med Eclipse, uppstår fortfarande problem vid användning på större projekt eftersom MuClipse kräver en viss namngivningskonvention som inte alltid behöver vara den samma som använts vid utvecklingen av systemet. Vidare kan endast en klass i gången testas och verktyget saknar integration med ett byggverktyg så som Ant eller Maven.[16] Muclipse finns att ladda ned gratis från http://muclipse.sourceforge.net/ eller direkt från Eclipse genom att lägga till deras sida till Eclipse uppdateringssidor. 4.2.2 Javalanche Javalance är ett mutationsverktyg för projekt som använder sig av Junit 3 eller 4. Programmet är terminalbaserat, dvs. det har inget grafiskt användargränssnitt.[19] Ett tilläggsprogram till Eclipse har enligt [20] existerat men är för tillfället inte under utveckling. Javalanche presenterades första gången 24-28 Augusti 2009 under European Software Engineering Conference (ESEC) och ACM SIGSOFT Symposium on the Foundations of Software Engineering (FSE)[21]. Tyngdpunkten vid utvecklingen av Javalanche är effektivitet och att försöka möj- 15 liggöra mutationstestning av stora projekt med mycket källkod. Detta åstadkoms m.h.a. att värdera inverkan av enskilda mutanter. Med inverkan avses här skillnaden mellan körningen av en mutant och ursprungskoden. På så sätt kan man undvika ekvivalent mutanter och använda färre mutationsoperatorer, vilket leder till lägre exekveringstider tack vare färre mutanter att exekvera. Vidare kommer varje testfall inte att exekvera varje mutant, eftersom programmet samlar täckningsinformation för varje test och exekverar endast de tester som täcks av just det testfallet. Javalanche muterar dessutom liksom MuJava, Java-bytekoden direkt. Slutligen stöder det parallellexekvering och kan således användas på parallella och distribuerade system. Javalanche är fullt automatiserat och kräver endast namnet på testsviten, namnet på projektets paket och klasserna som behövs för att exekverar testsviten.[20] Javalanche körs via kommandotolken och alla inställningar görs i en XML-fil vid namn javalanche.xml. Resultaten sparas i en HSQL-databas och kan avläsas från en HTML-fil.[19] 4.2.3 Judy Judy är ett relativt nytt mutationsverktyg. Dess första version släpptes 7.7.2011 och det utvecklas av professor Lech Madeyski vid Wroclaw University of Technology.[22] FAMTA Light FAMTA Light (Fast aspect-oriented mutation testing algorithm) baserar sig på den från aspektorienterad programmering (AOP) bekanta mekanismen “aspekt och punktsnitt”. Denna mekanism försöker eliminera upprepad kod med samma funktionalitet som vid t.ex loggning eller tidtagning av metoder. För att åstadkomma detta definieras kodpunkter i programmet, där den upprepade koden skall köras. Punktsnitten samlar sedan ihop dessa punkter och ger dem namn. Genom att sedan ange kod-direktiv som skall köras vid dessa punkter kan alla dessa 16 samlas i en enda aspekt. Detta kan lättare ses ur 4.1[23] aspekt = punktsnitt(kodpunkter) + direktiv (4.1) Denna mekanism tillämpas genom FAMTA Light på mutationstestning. Varje direktiv ansvarar för exekveringen av en metod i programmet. I direktivet görs sedan valet ifall originalet eller en mutant skall exekveras. Mutanterna sparas som mutantmetoder i samma klass som originalmetoden. Detta medför att alla mutanter kan kompileras på samma gång så kompileringen behöver inte upprepas för varje mutant, vilket medför en märkbart mindre exekveringstid. Dessvärre resulterar detta tillvägagångssätt i mycket långa klasser, med uppemot 10000 rader kod, vilket leder till bl.a OutOfMemoryError undantag. Detta löstes genom att göra processen iterativ och köra mindre iterationer av: genererings-, kompileringsoch testnings-faser för att minska antalet mutanter som kan tillämpas på samma gång. [16] Judy Även Judy koncentrerar sig på att göra mutationstestning av stora system möjligt genom minska antalet mutanter och onödiga kompileringar samt körningar. Med hjälp av FAMTA Light, fullt automatiserad mutationstestnings-process och stöd för de senaste Java-versionerna vill Judy överkomma de problem som gjort att mutationstestning fortfarande inte används i stor utsträckning inom industrin för programvaruproduktion. I en jämförelse mellan MuJava och Judy visar sig Judy, med ett medeltal av 52,05 mutanter/min mot MuJava 4,15 mutanter/min, vara överlägset snabbare än MuJava. Eftersom Judy muterar på källkodsnivå och FAMTA Light endast gör mutanter av metoder, klara Judy för tillfället endast av metodspecifika-mutationsoperatorer. Judy är dock under aktiv utveckling och målet är att snart även kunna stöda klasspecifika-mutationsoperatorer, genom att kombinera FAMTA Light med exemplevis bytekodsmutering.[16] 17 5 Avslutning Mutationstestning är som tidigare nämndes ett av de mest kraftfulla verktygen för att producera högkvalitativa test och därmed även högkvalitativ mjukvara. Dessvärre har dess användning länge varit begränsad till mindre system p.g.a den enorma beräkningskapacitet som skulle behövas för tillämpning på större system[14]. Lyckligtvis går utvecklingen framåt och tack vare verktyg som Javalanche och Judy som prioriterar användbarhet och skalbarhet har möjligheten till mutationstestning av större system blivit betydligt större. Forskningen har dessutom gått från att koncentrera sig på simpla operatorer till mer detaljerad mutation. Detta innebär att fokus gått från syntaktiska förändringar till de semantiska effekterna av mutation. Denna förändring har medfört ökat intresse för mutation på högre nivå som ger upphov till detaljerad mutation , vilket förhoppningsvis ger resulterar i mer realistiska mutanter.[11] Framtiden ser lovande ut för mutationstestning. Antalet verktyg har ökat under de senaste åren och även storleken av de program som testas har vuxit till avsevärt.[11] Forskare tror att inom en snar framtid kan det vara möjligt att endast ge en mjukvarumodul som indata till testningsverktyget och efter en rimlig tid få färdigt skrivna test, som garanterat ger effektiv testning, som utdata. [14] 18 Litteratur [1] Software QA and Testing Resource Center, “Software QA and Testing Frequently-Asked-Questions, Part 1.” http://www.softwareqatest.com/ qatfaq1.html. Hämtad 30.3.2012. [2] P. Ammann och J. Offutt, Introduction to Software Testing. New York, NY, USA: Cambridge University Press, 1 ed., 2008. [3] R. Patton, Software Testing. Indianapolis, IN, USA: Sams, 2 ed., 2005. [4] G. J. Myers och C. Sandler, The Art of Software Testing. John Wiley & Sons, 2004. [5] I. Zahid, “What is v model in testing?.” http://www.blogs.zahidiqbal. info/post/What-is-V-model-in-testing.aspx, Augusti 2011. Hämtad: 16.2.2012. [6] J. A. Whittaker, “What is software testing? and why is it so hard?,” IEEE Softw., vol. 17, sid. 70–79, Januari 2000. [7] P. Oladimeji, “Levels of testing,” December 2007. [8] J. Gosling och H. McGilton, The Java language environment: a white paper. Sun Microsystems Computer Co., 1995. [9] P. Ammann och J. Offut, “Junit, automated software testing framework.” http://cs.gmu.edu/~offutt/classes/637/slides/Ch1-junit. pdf. Hämtad 24.3.2012. [10] R. A. DeMillo, R. J. Lipton, och F. G. Sayward, “Hints on test data selection: Help for the practicing programmer,” IEEE Computer, vol. 11, nr. 4, sid. 34– 41, 1978. [11] Y. Jia och M. Harman, “An analysis and survey of the development of mutation testing,” IEEE Transactions on Software Engineering, vol. 37, sid. 649– 678, 2011. [12] Y.-S. Ma, J. Offutt, och Y.-R. Kwon, “Mujava: a mutation system for java,” i Proceedings of the 28th international conference on Software engineering, ICSE ’06, (New York, NY, USA), sid. 827–830, ACM, 2006. [13] J. Z. Gao, J. Tsao, Y. Wu, och T. H.-S. Jacob, Testing and Quality Assurance for Component-Based Software. Norwood, MA, USA: Artech House, Inc., 2003. [14] A. J. Offutt och R. H. Untch, “Mutation 2000: Uniting the orthogonal,” i Proceedings of the 1st Workshop on Mutation Analysis (MUTATION’00), (San Jose, Kalifornien), sid. 34–44, 6-7 Oktober 2001. 19 [15] Y.-S. Ma, Y.-R. Kwon, och J. Offutt, “Inter-class mutation operators for java,” i Proceedings of the 13th International Symposium on Software Reliability Engineering, ISSRE ’02, (Washington, DC, USA), sid. 352–, IEEE Computer Society, 2002. [16] L. Madeyski och N. Radyk, “Judy - a mutation testing tool for java.,” IET Software, vol. 4, nr. 1, sid. 32–42, 2010. [17] Y.-S. Ma, J. Offutt, och Y. R. Kwon, “Mujava: an automated class mutation system: Research articles,” Softw. Test. Verif. Reliab., vol. 15, sid. 97–133, Juni 2005. [18] B. H. Smith och L. Williams, “On guiding the augmentation of an automated test suite via mutation analysis,” Empirical Softw. Engg., vol. 14, sid. 341– 369, Juni 2009. [19] “Javalanche wiki.” https://github.com/david-schuler/javalanche/ wiki/Documentation. Hämtad: 17.3.2012. [20] D. Schuler och A. Zeller, “Javalanche: Efficient mutation testing for java,” i ESEC/FSE ’09: Proceedings of the 7th joint meeting of the European Software Engineering Conference and the ACM SIGSOFT International Symposium on Foundations of Software Engineering, sid. 297–298, Augusti 2009. [21] “Javalanche.” https://www.javalanche.org. Hämtad: 17.3.2012. [22] “Judy documentation.” http://java.mu/. Hämtad: 18.3.2012. [23] B. Granvik, “Ytterligare en aspekt på din kod.” http://blog. jayway.com/2004/01/20/ytterligare-en-aspekt-pa-din-kod/. Hämtad: 18.3.2012. 20 A Bilaga 1 Skärmdumpar från Muclipse. Figur A.1: MuClipse GUI för generering av mutanter 21 Figur A.2: Lista över genererade mutanter i MuClipse GUI Figur A.3: Jämförelse av mutant och original i MuClipse 22 Figur A.4: MuClipse GUI exekvering av mutanterna mot testfallen 23
© Copyright 2024