Wat is mutatietesten? (Voorbeeld)

⚡ Slimme samenvatting

Mutatietesten introduceren opzettelijk kleine fouten in de broncode en voeren vervolgens de bestaande testsuite uit op elke foutieve versie, waarbij wordt gemeten of die tests sterk genoeg zijn om de wijziging te detecteren.

  • 🔘 Definitie: Een mutant is een programma dat één opzettelijke syntactische wijziging bevat, en het uitschakelen ervan bewijst dat een test die wijziging heeft gedetecteerd.
  • ☑️ Werkwijze: Genereer mutanten, voer de testsuite uit op het origineel en de mutant, vergelijk de resultaten en verbeter vervolgens de tests die fouten over het hoofd hebben gezien.
  • OperaTorens: OperaVervanging van genen, modificatie van genexpressie en modificatie van genuitdrukkingen leiden tot de drie belangrijkste families van mutanten.
  • 🧪 score: De mutatiescore is het percentage gedode mutanten en meet de assertiviteitssterkte in plaats van louter de uitvoering van een aanvalslijn.
  • tooling: Stryker-covers Javascript, TypeScript, C#, en Scala, terwijl PIT de JVM-bytecode binnen Maven muteert en Gradle bouwt.
  • ⚠️ Kosten: Elke mutatie doorloopt de hele testsuite opnieuw, waardoor mutatietesten traag, duur en onpraktisch zijn zonder automatisering.

Mutatie testen

Wat is mutatietesten?

Mutatie testen Mutatietesten is een vorm van softwaretesten waarbij bepaalde onderdelen van de broncode worden gewijzigd, of gemuteerd, om te controleren of de testgevallen fouten in de broncode kunnen vinden. Het doel van mutatietesten is om de robuustheid van de testgevallen te waarborgen, zodat ze falen bij de gemuteerde broncode.

De wijziging die in een gemuteerd programma wordt aangebracht, moet extreem klein zijn, zodat deze het algemene doel van het programma niet beïnvloedt. Mutatietesten wordt ook wel een foutgebaseerde teststrategie genoemd, omdat er opzettelijk een fout in het programma wordt gecreëerd. Het is een vorm van Wit Box Testen dat voornamelijk wordt toegepast tijdens Testen van een eenheid.

Mutatietesten werden in 1971 voorgesteld in een studentenpaper van Richard Lipton en geformaliseerd in het artikel "Hints on Test Data Selection" uit 1978 van DeMillo, Lipton en Sayward. Het verloor aan populariteit vanwege de hoge rekenkosten van die tijd, maar heeft sindsdien weer terrein gewonnen voor talen zoals Java, C#, Python, JavaScript en XML.

Hoe voer je een Mutatietest uit?

Hieronder volgen de stappen voor het uitvoeren van mutatietesten, ook wel mutatieanalyse genoemd:

Stap 1: Fouten worden in de broncode van het programma geïntroduceerd door vele versies te creëren, mutanten genaamd. Elke mutant moet één fout bevatten, en het doel is om de mutantversie te laten falen, wat de effectiviteit van de testgevallen aantoont.

Stap 2: Testgevallen worden toegepast op het oorspronkelijke programma en ook op het gemuteerde programma. Testgeval zou voldoende moeten zijn, en het is aangepast om fouten in een programma te detecteren.

Stap 3: Vergelijk de resultaten van het oorspronkelijke en het gemuteerde programma.

Stap 4: Als het oorspronkelijke programma en het gemuteerde programma een verschillende uitvoer genereren, wordt het gemuteerde programma door de testcase uitgeschakeld. De testcase is dus voldoende om de verschillen tussen het oorspronkelijke en het gemuteerde programma te detecteren.

Stap 5: Als het oorspronkelijke programma en het gemuteerde programma dezelfde uitvoer genereren, blijft de mutant bestaan. In dergelijke gevallen moeten effectievere testgevallen worden ontwikkeld die alle mutanten uitschakelen.

Het onderstaande diagram tracHet doorloopt dezelfde vijf stappen, van het oorspronkelijke programma via de generatie van mutanten tot het oordeel of de mutant gedood of overleefd is.

Werkstroom voor mutatietesten, met het originele programma, de gegenereerde mutanten, de testuitvoering en het oordeel of de mutant gedood of levend is.

Hoe Mutant-programma's maken?

Een mutatie is niets anders dan een enkele syntactische wijziging die wordt aangebracht in een programmaregel. Elk gemuteerd programma moet precies één mutatie verschillen van het originele programma.

Origineel programma Mutantenprogramma
Als (x>y)
Afdrukken “Hallo”
Anders
Druk “Hallo” af
Als (x
Afdrukken “Hallo”
Anders
Druk “Hallo” af

In het bovenstaande paar is alleen de vergelijkingsoperator veranderd, maar een testgeval waarbij x groter is dan y print nu "Hi" in plaats van "Hello". De illustratie laat die ene syntactische aanpassing zien.

Eén syntactische wijziging toegepast op een programmaregel om een ​​enkele mutant te produceren.

Wat moet er veranderd worden in een Mutant Programma?

Er zijn verschillende technieken die gebruikt kunnen worden om gemuteerde programma's te genereren. De drie onderstaande categorieën omvatten de meeste mutatieoperatoren die standaard in de tools aanwezig zijn.

Operand vervangende operatoren Operators voor het wijzigen van expressies Operators voor het wijzigen van verklaringen
Vervang de operand door een andere operand (x door y, of y door x) of door een constante waarde. Vervang een operator in een programmaregel of voeg een nieuwe operator in. Programmatische instructies worden aangepast om mutante programma's te maken.
Voorbeeld:
If(x>y) vervang x- en y-waarden
Als(5>y) vervang x door constante 5
Voorbeeld:
Als(x==y)
We kunnen == vervangen door >= en het gemuteerde programma als volgt verkrijgen:
If(x>=y) en het invoegen van ++ in de instructie
Als(x==++y)
Voorbeeld:
Verwijder het else-gedeelte in een if-else-instructie
Verwijder de volledige if-else-instructie om te controleren hoe het programma zich gedraagt.

Enkele voorbeelden van mutatieoperatoren:

  • GOTO-labelvervanging
  • Vervanging retourverklaring
  • Verklaring verwijderen
  • Invoeging van unaire operatoren (zoals – en ++)
  • Vervanging van logische connectoren
  • Vergelijkbare vervanging van de arraynaam
  • Het verwijderen van het 'else'-gedeelte van een if-else-instructie
  • Operators toevoegen of vervangen
  • Vervanging van verklaringen door de gegevens te wijzigen
  • Gegevensaanpassing voor de variabelen
  • Wijziging van gegevenstypen in het programma

OperaTors die een randvoorwaarde raken, overleven het vaakst, dus mutatieresultaten wijzen vaak terug op hiaten in grenswaarde analyse.

Soorten mutatietesten

In Software EngineeringMutatietesten worden in principe onderverdeeld in drie typen: statementmutatie, waardemutatie en beslissingsmutatie.

  • Verklaring Mutatie – een instructie wordt geknipt, geplakt of verwijderd, waardoor mogelijk enkele regels code worden verwijderd.
  • Waardemutatie – De waarden van primaire parameters en constanten worden gewijzigd, bijvoorbeeld door een lusgrens of een drempelwaarde aan te passen.
  • Beslissingsmutatie – besturingsinstructies worden gewijzigd, bijvoorbeeld flippenping een relationele operator of het ontkennen van een voorwaarde.

De tools groeperen hun operators onder deze drie rubrieken, zodat de familie die een overlevende mutant heeft geproduceerd de tester vertelt welk type bewering ontbreekt. Een overlevende beslissingsmutant markeert meestal een ongeteste tak, die overlapt met lus testen.

Automatisering van mutatietests

Mutatietesten zijn extreem tijdrovend en complex om handmatig uit te voeren. Daarom is het raadzaam om automatiseringstools te gebruiken, wat ook de kosten verlaagt. Een mutatietool compileert de mutanten, plant de uitvoeringen in, registreert welke mutant bij elke mislukte test wordt verwijderd en rapporteert de score.

Lijst met beschikbare tools:

  • Stryker — een open-source framework voor mutatietesten met edities voor JavaScriptie en TypeScript (StrykerJS), C# en .NET (Stryker.NET), en Scala (Stryker4s).
  • PIT, ook wel geschreven als PITest — een mutatietestsysteem voor Java en de JVM die gecompileerde bytecode muteert en aansluit op Maven en Gradle bouwt ernaast JUnit.

Beide worden uitgevoerd als een build-stap, dus ze horen in hetzelfde pakket thuis. continue integratie pijpleiding, net als de rest van de automatisering testen op.

Mutatiescore

De mutatiescore wordt gedefinieerd als het percentage gedode mutanten ten opzichte van het totale aantal mutanten.

Mutatiescore = (Gedode Mutanten / Totaal aantal Mutanten) * 100

De formule wordt hieronder weergegeven in de vorm zoals de meeste tools deze presenteren.

De formule voor de mutatiescore wordt berekend door het aantal gedode mutanten te delen door het totale aantal mutanten en te vermenigvuldigen met honderd.

Testgevallen worden als mutatie-adequaat beschouwd wanneer de score 100 procent bereikt. In de praktijk moet de noemer het volgende uitsluiten: equivalente mutanten — mutanten waarvan de gewijzigde syntaxis zich exact hetzelfde gedraagt ​​als het origineel, waardoor geen enkele test ze kan uitschakelen. Tools rapporteren daarom het aantal uitgeschakelde mutanten gedeeld door het aantal uitgeschakelde plus het aantal overgebleven niet-equivalente mutanten, en laten de tester de equivalenten markeren.

Experimentele resultaten hebben aangetoond dat mutatietesten een effectieve manier zijn om de geschiktheid van testgevallen te meten. Het belangrijkste nadeel is de kosten van het genereren van de mutanten en het uitvoeren van elk testgeval tegen elke mutant.

Mutatietesten versus Code Dekking

Hoge test dekking Dit bewijst niet dat de tests sterk zijn. Regel- en vertakkingsdekking registreert welke instructies zijn uitgevoerd, niet of er daarna iets is geverifieerd. Een test die een methode aanroept en niets controleert, telt dus nog steeds als gedekt. ​​Mutatietesten dichten die kloof, omdat een mutant alleen sterft wanneer een bewering daadwerkelijk mislukt.

Aspect Code dekking Mutatiescore
Wat het meet Welke regels of vertakkingen werden er getest? Welke fouten werden er geïnjecteerd tijdens de tests?
Gevoelig voor beweringen Nee, een test zonder beweringen voegt nog steeds dekking toe. Ja, een mutant overleeft wanneer geen enkele bewering faalt.
Kosten van een run Eén instrumentele testrun Eén testrun per overlevende mutant, tot nu toe veel langzamer.
Typisch gebruik Een snelle controle bij elke commit. Een grondigere periodieke controle van kritieke modules
Faal modus 100 procent dekking zonder echte verificatie Gelijkwaardige mutanten die nooit gedood kunnen worden

De twee meetwaarden vullen elkaar aan. De code coverage geeft aan welke code nooit is bereikt; de mutatiescore geeft aan welke code wel is bereikt, maar nooit is gecontroleerd. Beide leveren dezelfde resultaten op. defectbeheerproces, naast maatregelen zoals defectdichtheid.

Voordelen van mutatietesten

Hieronder volgen de voordelen van mutatietesten:

  • Het is een krachtige methode om een ​​hoge dekking van het bronprogramma te bereiken.
  • Het test de testsuite zelf, wat geen andere software testtechniek doet dat direct.
  • Mutatietesten bieden softwareontwikkelaars een goede mate van foutdetectie.
  • De methode legt onduidelijkheden in de broncode bloot en kan fouten aan het licht brengen die bij normale uitvoeringen nooit worden ontdekt.
  • De overgebleven mutanten zijn aanspreekbaar: elk van hen noemt een specifieke regel en een specifieke wijziging die de software niet heeft opgemerkt.
  • Klanten profiteren van deze tests doordat ze een betrouwbaarder en stabieler systeem ontvangen.

Nadelen van mutatietesten

Aan de andere kant zijn de volgende nadelen verbonden aan mutatietesten:

  • Mutatietesten zijn extreem kostbaar en tijdrovend, omdat er een groot aantal gemuteerde programma's gegenereerd en gecompileerd moet worden.
  • Omdat het tijdrovend is, kun je gerust stellen dat deze test niet zonder een automatiseringstool kan worden uitgevoerd.
  • Elke mutant wordt getest met hetzelfde aantal testgevallen als het oorspronkelijke programma, dus een grote populatie mutanten moet worden getest met de volledige testsuite.
  • Gelijkwaardige mutanten kunnen door geen enkele test worden gedood, en het onderscheiden ervan van echte overlevenden vereist meestal handmatige beoordeling.
  • Omdat de methode de broncode wijzigt, is deze niet toepasbaar op Zwart Box Testen.

Wanneer moet je mutatietesten gebruiken?

Het bovenstaande kostenprofiel betekent dat mutatietesten zelden bij elke commit op de volledige codebase worden uitgevoerd. Het betaalt zichzelf terug wanneer een onopgemerkte fout kostbaar is en de te testen code klein genoeg is om snel te muteren.

  • Veiligheidskritische of financiële logica — betalingsberekening, belastingregels en autorisatiecontroles, waarbij een stilzwijgend fout antwoord erger is dan een ongeluk.
  • Suites met verdacht hoge dekking — wanneer de dekking bijna 100 procent bedraagt, maar er toch nog defecten door de mazen van het net glippen.
  • Verouderde code wordt herzien. — De resultaten van de mutatietests laten zien of de bestaande tests een regressie zouden detecteren.
  • Bibliotheken en gedeelde componenten — een fout in een hergebruikt bestanddeel vermenigvuldigd met elke beller.
  • Teams die oefenen test gedreven ontwikkeling — De score controleert of de eerst afgenomen tests daadwerkelijk effectief zijn.

Het is meestal niet de moeite waard om te werken met wegwerpprototypes, met dunne verbindingsstukken of gegenereerde code zonder vertakkingslogica, of met testsuites die gedomineerd worden door trage code. integratietests die nu al uren in beslag nemen voor één enkele doorgang.

De meeste teams beperken de run daarom tot gewijzigde bestanden, stellen een drempelwaarde in voor de modules die ertoe doen, en laten de rest over aan de rest. regressietesten suite draagt ​​de rest van de levenscyclus van softwaretests.

Veelgestelde vragen

Een equivalente mutant is een wijziging die de syntaxis verandert, maar niet het gedrag, zoals het vervangen van een lusgrens die nooit wordt bereikt. Geen enkele test kan deze mutant uitschakelen, dus moet deze worden gemarkeerd en uitgesloten voordat de score betrouwbaar is.

Er bestaat geen universeel getal. Teams hanteren doorgaans een hoge drempel voor kritieke modules zoals betalings- of beveiligingslogica, en een lagere drempel voor andere onderdelen. Het nastreven van een percentage is minder nuttig dan het analyseren van elke overgebleven mutatie in risicovolle code.

Dit zijn de twee aannames waarop de techniek is gebaseerd. De eerste is dat programmeurs bijna correcte code schrijven, waardoor echte fouten klein zijn. De tweede is dat tests die kleine fouten opsporen, ook de complexe fouten detecteren die daaruit voortvloeien.

Beperk mutaties tot bestanden die in de huidige branch zijn gewijzigd, hergebruik dekkingsgegevens zodat alleen tests die een mutant raken worden uitgevoerd, voer mutanten parallel uit en laat de build mislukken bij een scoredaling in plaats van een absoluut getal.

Machine learning-modellen voorspellen welke mutanten waarschijnlijk zullen overleven, zodat de run kan worden ingekort, classificeren waarschijnlijke equivalente mutanten voor beoordeling en genereren mutanten die lijken op fouten die in de projectgeschiedenis zijn waargenomen, in plaats van uniforme operatorwisselingen.

Ja, wat het mechanische gedeelte betreft. Gegeven een overlevende mutant en de te testen methode, stelt Copilot de ontbrekende bewering of randgevaltest op. Een reviewer moet nog steeds bevestigen dat de verwachte waarde correct is en niet simpelweg is overgenomen uit het huidige gedrag.

Het controleert het resultaat. Testgestuurde ontwikkeling genereert tests vóór de code, maar niets garandeert dat die tests voldoende betrouwbaar zijn. Een periodieke mutatietest op dezelfde modules laat zien of de rood-groen-cyclus tests heeft opgeleverd die daadwerkelijk falen bij een onjuist antwoord.

Nee. Foutinjectie verstoort de runtime-omgeving. fuzz-testen Bij mutatietesten wordt onjuiste invoer gebruikt, waarbij de applicatie wordt beoordeeld. Mutatietesten wijzigen de broncode en beoordelen de testsuite, waardoor het te evalueren object anders is.

Vat dit bericht samen met: