Hvad er mutationstest? (Eksempel)

⚡ Smart opsummering

Mutationstestning introducerer bevidst små fejl i kildekoden og kører derefter den eksisterende testsuite mod hver fejlbehæftet version for at måle, om disse tests er stærke nok til at registrere ændringen.

  • 🔘 Definition: En mutant er det program, der bærer én bevidst syntaktisk ændring, og at afslutte den beviser, at en test har opdaget denne ændring.
  • ☑️ Proces: Generer mutanter, kør pakken mod originalen og mutanten, sammenlign output, og styrk derefter de tests, der overså fejl.
  • ✅ Operators: Operand-erstatning, ekspressionsmodifikation og sætningsmodifikation producerer de tre hovedfamilier af mutanter.
  • 🧪 score: Mutationsscore er procentdelen af ​​dræbte mutanter, og den måler assertionstyrke snarere end blot linjeudførelse.
  • 🛠️ Værktøj: Stryker-dæksler Javamanuskript, TypeScript, C# og Scala, mens PIT muterer JVM bytekode inde i Maven og Gradle bygger.
  • ⚠️ Omkostninger: Hver mutant kører hele pakken igen, så mutationstestning er langsom, dyr og upraktisk uden automatisering.

Mutationstest

Hvad er mutationstestning?

Mutationstest er en type softwaretest, hvor bestemte udsagn i kildekoden ændres eller muteres for at kontrollere, om testcasene er i stand til at finde fejl i kildekoden. Målet med mutationstestning er at sikre testcasenes kvalitet med hensyn til robusthed, så de fejler mod den muterede kildekode.

Ændringen i et mutantprogram skal holdes ekstremt lille, så den ikke påvirker programmets overordnede mål. Mutationstestning kaldes også en fejlbaseret teststrategi, fordi den involverer bevidst at skabe en fejl i programmet. Det er en form for Hvid Box Test som primært anvendes under Enhedstest.

Mutationstestning blev foreslået i 1971 i en studenterartikel af Richard Lipton og formaliseret i artiklen "Hints on Test Data Selection" fra 1978 af DeMillo, Lipton og Sayward. Den mistede momentum på grund af datidens computeromkostninger og har siden genvundet terræn for sprog som Java, C#, Python, JavaScript og XML.

Hvordan udfører man mutationstest?

Følgende er trinnene til at udføre mutationstestning, også kendt som mutationsanalyse:

Trin 1: Fejl introduceres i programmets kildekode ved at oprette mange versioner kaldet mutanter. Hver mutant skal indeholde en enkelt fejl, og målet er at få mutantversionen til at fejle, hvilket demonstrerer testtilfældenes effektivitet.

Trin 2: Testtilfælde anvendes på det oprindelige program og også på mutantprogrammet. Test sag bør være tilstrækkelig, og den er tilpasset til at opdage fejl i et program.

Trin 3: Sammenlign resultaterne af det oprindelige og mutantprogrammet.

Trin 4: Hvis det originale program og mutantprogrammet genererer forskelligt output, dræbes mutanten af ​​testcasen. Derfor er testcasen god nok til at detektere ændringen mellem det originale program og mutantprogrammet.

Trin 5: Hvis det originale program og mutantprogrammet genererer det samme output, holdes mutanten i live. I sådanne tilfælde skal der oprettes mere effektive testtilfælde, der dræber alle mutanterne.

Diagrammet nedenfor tracer de samme fem trin, fra det oprindelige program gennem mutantgenerering til den dræbte eller overlevende dom.

Mutationstestningsworkflow, der viser det oprindelige program, genererede mutanter, testudførelse og den dræbte eller levende mutants dom.

Hvordan opretter man mutantprogrammer?

En mutation er intet andet end en enkelt syntaktisk ændring, der foretages i en programsætning. Hvert mutantprogram skal afvige fra det oprindelige program med præcis én mutation.

Originalt program Mutant program
Hvis (x>y)
Udskriv "Hej"
Else
Udskriv "Hej"
Hvis (x
Udskriv "Hej"
Else
Udskriv "Hej"

I parret ovenfor er kun sammenligningsoperatoren ændret, men et testtilfælde, hvor x er større end y, udskriver nu "Hej" i stedet for "Hej". Illustrationen viser den ene syntaktiske redigering.

Én syntaktisk ændring anvendt på en programsætning for at producere en enkelt mutant

Hvad skal man ændre i et mutantprogram?

Der findes adskillige teknikker, der kan bruges til at generere mutantprogrammer. De tre familier nedenfor dækker de fleste af de mutationsoperatorer, som værktøjer leveres med.

Operaog erstatningsoperatører Udtryksmodifikationsoperatorer Operatorer for ændring af erklæringer
Erstat operanden med en anden operand (x med y, eller y med x) eller med en konstant værdi. Erstat en operator, eller indsæt en ny operator, i en programsætning. Programmatiske udsagn modificeres for at skabe mutantprogrammer.
Eksempel:
If(x>y) erstatte x- og y-værdier
If(5>y) erstatte x med konstant 5
Eksempel:
Hvis(x==y)
Vi kan erstatte == med >= og få mutantprogrammet som
If(x>=y) og indsætte ++ i sætningen
Hvis(x==++y)
Eksempel:
Slet den anden del i en if-else-sætning
Slet hele if-else-sætningen for at kontrollere, hvordan programmet opfører sig

Nogle eksempler på mutationsoperatorer:

  • GOTO etiket udskiftning
  • Udskiftning af returerklæring
  • Sletning af erklæring
  • Indsættelse af unær operator (såsom – og ++)
  • Udskiftning af logisk stik
  • Sammenlignelig udskiftning af arraynavn
  • Fjernelse af else-delen af ​​en if-else-sætning
  • Tilføjelse eller udskiftning af operatorer
  • Erstatning af erklæring ved at ændre dataene
  • Dataændring for variablerne
  • Ændring af datatyper i programmet

OperaTorer, der berører en randbetingelse, overlever oftest, så mutationsresultater peger ofte tilbage på huller i grænseværdianalyse.

Typer af mutationstestning

In Software EngineeringMutationstestning er grundlæggende kategoriseret i tre typer - udsagnsmutation, værdimutation og beslutningsmutation.

  • Statement Mutation – en sætning klippes, indsættes eller slettes, så resultatet kan være, at nogle linjer kode fjernes.
  • Værdimutation – værdierne af primære parametre og konstanter ændres, for eksempel ændring af en løkkegrænse eller en tærskel.
  • Beslutningsmutation – kontrolsætninger ændres, for eksempel flipping en relationel operator eller negation af en betingelse.

Værktøjer grupperer deres operatorer under disse tre overskrifter, så familien, der producerede en overlevende mutant, fortæller testeren, hvilken type påstand der mangler. En overlevende beslutningsmutant markerer normalt en utestet gren, som overlapper med looptestning.

Automatisering af mutationstestning

Mutationstestning er ekstremt tidskrævende og kompliceret at udføre manuelt, så det anbefales at bruge automatiseringsværktøjer, som også reducerer omkostningerne. Et mutationsværktøj samler mutanterne, planlægger kørslerne, registrerer hvilken mutant hver enkelt fejlende test dræbte, og rapporterer scoren.

Liste over tilgængelige værktøjer:

  • Stryker — et open source-rammeværk til mutationstestning med udgaver til JavaManuskript og TypeScript (StrykerJS), C# og .NET (Stryker.NET) og Scala (Stryker4s).
  • PIT, også skrevet PITest — et mutationstestsystem til Java og JVM'en, der muterer den kompilerede bytekode og tilsluttes Maven og Gradle bygger ved siden af JUnit.

Begge kører som et byggetrin, så de hører hjemme i det samme kontinuerlig integration rørledningen som resten af automatiseringstest på.

Mutationsscore

Mutationsscoren er defineret som procentdelen af ​​dræbte mutanter ud af det samlede antal mutanter.

Mutationsscore = (dræbte mutanter/samlet antal mutanter) * 100

Formlen er vist nedenfor i den form, som de fleste værktøjer rapporterer den.

Mutationsscoreformel dividerer dræbte mutanter med det samlede antal mutanter og ganger med hundrede

Testtilfælde beskrives som mutationsfyldestgørende, når scoren når 100 procent. I praksis skal nævneren udelukke ækvivalente mutanter — mutanter hvis ændrede syntaks opfører sig præcis som originalen, så ingen test kan dræbe dem. Værktøjer rapporterer derfor dræbte mutanter divideret med dræbte plus overlevende ikke-ækvivalente mutanter og lader testeren markere ækvivalenter.

Eksperimentelle resultater har vist, at mutationstestning er en effektiv måde at måle tilstrækkeligheden af ​​testcases. Den største ulempe er omkostningerne ved at generere mutanterne og udføre hver testcase mod hver enkelt.

Mutationstest vs. Code Dækning

Høj test dækning beviser ikke stærke tests. Linje- og branch coverage registrerer hvilke udsagn der blev kørt, ikke om noget blev verificeret bagefter, så en test, der kalder en metode og ikke hævder noget, tæller stadig som dækket. Mutationstest lukker dette hul, fordi en mutant kun dør, når en assertion rent faktisk fejler.

Aspect Code dækning Mutationsscore
Hvad det måler Hvilke linjer eller grene testene blev udført Hvilke injicerede fejl testene detekterede
Følsom over for påstande Nej — en test med nul påstande tilføjer stadig dækning Ja — en mutant overlever, når ingen påstand fejler
Prisen for en løbetur Én instrumenteret testkørsel Én testkørsel pr. overlevende mutant, indtil videre langsommere
Typisk brug En hurtig gate på hver commit En grundigere periodisk kontrol af kritiske moduler
Fejl tilstand 100 procent dækning uden reel verifikation Ækvivalente mutanter, der aldrig kan dræbes

De to målinger komplementerer hinanden. Dækningsgraden angiver den kode, der aldrig blev nået; mutationsscoren angiver den nåede kode, der aldrig blev kontrolleret. Begge giver den samme information. proces til håndtering af fejl, sammen med foranstaltninger som f.eks. defektdensitet.

Fordele ved mutationstestning

Følgende er fordelene ved mutationstest:

  • Det er en effektiv metode til at opnå høj dækning af kildeprogrammet.
  • Den tester selve testsuiten, som ingen andre softwaretestteknik gør direkte.
  • Mutationstestning giver softwareudvikleren et godt niveau af fejldetektion.
  • Metoden afdækker tvetydigheder i kildekoden og har kapacitet til at afsløre fejl, som almindelige kørsler aldrig når.
  • Overlevende mutanter kan gribes an: hver enkelt navngiver en specifik linje og en specifik ændring, som suiten ikke bemærkede.
  • Kunderne drager fordel af denne testning ved at modtage et mere pålideligt og stabilt system.

Ulemper ved mutationstestning

På den anden side er der følgende ulemper ved mutationstestning:

  • Mutationstestning er ekstremt dyrt og tidskrævende, fordi et stort antal mutantprogrammer skal genereres og kompileres.
  • Da det er tidskrævende, er det rimeligt at sige, at denne testning ikke kan udføres uden et automatiseringsværktøj.
  • Hver mutant udføres med det samme antal testcases som det oprindelige program, så en stor mutantpopulation skal køres mod hele testsuiten.
  • Tilsvarende mutanter kan ikke dræbes af nogen test, og adskillelse af dem fra ægte overlevende kræver normalt manuel gennemgang.
  • Da metoden ændrer kildekoden, er den ikke anvendelig på Sort Box Test.

Hvornår skal man bruge mutationstestning

Ovenstående omkostningsprofil betyder, at mutationstestning sjældent køres på tværs af en hel kodebase ved hver commit. Det betaler sig selv, hvor en uopdaget fejl er dyr, og den kode, der testes, er lille nok til at mutere hurtigt.

  • Sikkerhedskritisk eller økonomisk logik — betalingsberegning, skatteregler og godkendelsestjek, hvor et tavst forkert svar er værre end et nedbrud.
  • Suiter med mistænkeligt høj dækning — når dækningen er tæt på 100 procent, men der stadig undslipper fejl.
  • Ældre kode refaktoreres — mutationsresultater afslører, om de eksisterende tests ville fange en regression.
  • Biblioteker og delte komponenter — en fejl i en genbrugt komponent multipliceres på tværs af hver opkalder.
  • Hold der øver testdrevet udvikling — scoren kontrollerer, at de først skrevet prøver udfører et rigtigt arbejde.

Det er normalt ikke værd at køre på engangsprototyper, på tynd lim eller genereret kode uden forgreningslogik, eller på pakker domineret af langsomme integrationstests som allerede tager timer for en enkelt gennemkørsel.

De fleste teams dækker derfor kørslen til ændrede filer, sætter en tærskel for de moduler, der er vigtige, og lader den bredere regressionstest suiten bærer resten af livscyklus for softwaretest.

Ofte Stillede Spørgsmål

En ækvivalent mutant er en ændring, der ændrer syntaksen, men ikke adfærden, såsom at erstatte en løkkegrænse, der aldrig nås. Ingen test kan dræbe den, så den skal markeres og ekskluderes, før scoren kan stoles på.

Der er intet universelt tal. Teams sætter ofte en høj tærskel for kritiske moduler såsom betaling eller sikkerhedslogik og en lavere tærskel andre steder. At jagte en procentdel er mindre nyttigt end at gennemgå hver overlevende mutant i højrisikokode.

Det er de to antagelser, som teknikken hviler på. Den første siger, at programmører skriver næsten korrekt kode, så reelle fejl er små. Den anden siger, at tests, der fanger små fejl, også fanger de komplekse fejl, der er bygget ud fra dem.

Begræns mutation til filer ændret i den aktuelle gren, genbrug dækningsdata, så kun tests, der berører en mutant, udføres, kør mutanter parallelt, og fejl build'et på et scorefald i stedet for et absolut tal.

Maskinlæringsmodeller forudsiger, hvilke mutanter der sandsynligvis vil overleve, så kørslen kan trimmes, klassificerer sandsynlige ækvivalente mutanter til gennemgang og genererer mutanter, der ligner fejl set i projekthistorikken i stedet for ensartede operatorbytter.

Ja, for den mekaniske del. Givet en overlevende mutant og den metode, der testes, udarbejder Copilot den manglende påstand eller edge-case-test. En anmelder skal stadig bekræfte, at den forventede værdi er korrekt og ikke blot kopieret fra den nuværende adfærd.

Den reviderer resultatet. Testdrevet udvikling producerer tests før kode, men intet garanterer, at disse tests er tilstrækkeligt effektive. En periodisk mutationskørsel på de samme moduler viser, om den rød-grønne cyklus producerede tests, der reelt fejler på et forkert svar.

Nej. Fejlinjektion ødelægger runtime-miljøet og fuzz-testning føder forkert udformet input, der begge bedømmer applikationen. Mutationstest ændrer kildekoden og bedømmer testsuiten, så objektet under evaluering er anderledes.

Opsummer dette indlæg med: