Mutationstestning innebär att du gör en liten, avsiktligt felaktig ändring i produktionskoden och kör testsviten. Om ett test faller är mutanten dödad. Om allt förblir grönt har mutanten överlevt — ett konkret tecken på att testsviten inte skiljer rätt beteende från just den defekten.
Grönt visar bara att koden och testet är överens
AI-assistenter är skickliga på att producera många tester som ser rimliga ut. Men samma modell har ofta sett implementationen, härlett dess nuvarande beteende och skrivit kontroller som speglar just det. Resultatet kan bli ett slutet resonemang: koden säger vad som händer och testet bekräftar att det händer.
Kodtäckning löser inte problemet. Den visar vilka rader som kördes, inte om testet skulle märka att en jämförelse, returkonstant eller behörighetsregel blev fel. Den breda guiden till testning av AI-genererad kod rekommenderar därför att du förstör beteendet med flit. Mutationstestning gör den kontrollen systematisk.
Frågan är inte längre ”kör testerna den här raden?” utan ”vilka fel på den här raden kan testerna faktiskt upptäcka?”. Det är en mycket hårdare kvalitetskontroll.
Börja manuellt med en viktig regel
Du behöver inget nytt verktyg för att förstå metoden. Välj en liten affärsregel där ett fel skulle märkas av en användare. Här är en fraktfunktion i JavaScript:
export function shippingCost(total, isMember) {
if (isMember || total >= 500) return 0;
return 49;
}
En testsvit som bara provar ett stort köp ser kanske ut så här:
import test from 'node:test';
import assert from 'node:assert/strict';
import { shippingCost } from './shipping.js';
test('fri frakt för stora köp', () => {
assert.equal(shippingCost(800, false), 0);
});
Kör först testerna utan ändring. Baslinjen måste vara grön. Byt sedan >= 500 mot > 500 och kör samma test igen. Testet fortsätter att vara grönt: mutanten överlever, eftersom testsviten aldrig provar själva gränsen.
Lägg till testet som beskriver kontraktet:
test('fri frakt börjar vid exakt 500 kronor', () => {
assert.equal(shippingCost(500, false), 0);
});
Kör med den felaktiga jämförelsen kvar. Nu ska det nya testet falla. Återställ produktionskoden till >= och kör igen. När sviten blir grön har du både dödat mutanten och lagt till ett test som förklarar varför gränsen finns.
En mutation är en tillfällig defekt, inte en fix. Gör försöket i en ren arbetskopia, håll varje mutation till en enda beteendeförändring och kontrollera diffen innan du committar. Annars riskerar själva kvalitetstestet att bli produktionsbuggen.
Välj mutanter som representerar riktiga fel
Börja inte med att ändra allt. Välj mutationer som motsvarar misstag en människa eller modell faktiskt kan göra och som påverkar ett observerbart kontrakt.
Fyra användbara mutationer i exemplet
- Flytta gränsen: byt
>=mot>. Det avslöjar om exakt 500 kronor testas. - Ändra logiken: byt
||mot&&. Det avslöjar om medlemskap och ordersumma provas var för sig. - Ändra resultatet: byt fraktkostnaden
49mot0. Det avslöjar om det betalande normalfallet kontrolleras. - Ta bort undantaget: låt alla betala frakt. Det avslöjar om medlemsregeln över huvud taget skyddas.
Prioritera behörighet, pengar, datakonvertering och permanenta sidoeffekter. En överlevande mutant i felmeddelandets interpunktion är sällan lika viktig som en överlevande mutant som gör en nekad användare behörig. Samma riskordning bör styra dina stoppregler före merge.
En överlevare ger en fråga, inte automatiskt ett nytt test
När en mutant överlever finns tre vanliga förklaringar. Testet kan saknas. Produktionskoden kan vara onödigt komplicerad. Eller så är mutanten ekvivalent: den ändrar syntaxen men inte något beteende som går att observera i systemet.
Anta att systemet redan validerar att totalsumman alltid är ett heltal. En mutation som bara förändrar beteendet för decimalen 499,5 kanske då inte går att nå genom den publika funktionen. Att pressa fram ett test för ett omöjligt tillstånd skapar mer testkod men inte mer trygghet. Dokumentera varför mutanten är irrelevant eller förenkla koden så att den inte genereras igen.
Låt AI föreslå angrepp, inte utfärda facit
AI är användbar för att lista möjliga mutationspunkter: gränsvärden, inverterade villkor, borttagna kontroller och ändrade returvärden. Be modellen förklara vilket användarbeteende varje mutation skulle förändra. Om den inte kan beskriva effekten är mutanten sannolikt dåligt vald.
Låt däremot inte samma svar både välja mutation, skriva testet och bedöma att resultatet är bra utan att du kör koden. Den viktiga signalen är exekveringen: grön baslinje, röd med mutant, grön efter återställning. Det är samma oberoende återkoppling som gör testdrivet AI-byggande värdefullt.
En användbar prompt är:
Föreslå fem små mutationer i den här funktionen.
För varje mutation: ange exakt kodändring, vilket publikt
beteende som ska ändras och vilket befintligt test som borde falla.
Skriv ingen ny implementation och anta inte att testet passerar.
Kör sedan förslagen ett i taget. Blanda aldrig flera mutationer i samma körning; då vet du inte vilken defekt som ett fallande test reagerade på.
Mät utan att jaga en kosmetisk procentsiffra
Ett enkelt mutationsresultat kan räknas som dödade mutanter dividerat med dödade plus överlevande. Ogiltiga och ekvivalenta mutanter ska inte smygas in i nämnaren bara för att få ett tal. Men även en korrekt procentsats saknar sammanhang: tio obetydliga mutanter kan ge bättre poäng än två kritiska behörighetsregler.
Spara därför både resultatet och risknivån. En överlevare i betalning eller åtkomstkontroll ska kunna stoppa en merge även om totalpoängen är hög. En lågprioriterad överlevare kan bli en tydligt ägd uppgift. Rapporten ska hjälpa någon fatta beslut, inte bara dekorera bygget.
Inför en CI-grind i två steg
När det manuella försöket ger värde kan ett mutationsverktyg automatisera samma loop: skapa en kodändring, kör relevanta tester, återställ och rapportera utfallet. Inför det gradvis. En full körning över hela en äldre kodbas kan vara långsam och ge en stor lista som ingen äger.
- Börja med ändrad riskkod. Kör mutationer på moduler som berör pengar, behörighet eller dataförlust när de ändras.
- Stoppa försämring. Spara en granskad baslinje och låt inte nya ändringar introducera nya, oförklarade överlevare.
Separera gärna den snabba testsviten från en tyngre mutationskörning, men gör resultatet synligt i samma kodgranskning. Om verktyget hittar en överlevare ska rapporten visa fil, mutation och berörd regel. En ensam procentsiffra är för svår att agera på.
För varje kritisk mutant ska ett relevant test falla av rätt anledning. Mutanten ska vara återställd, testet ska vara grönt mot korrekt kod och en eventuell överlevare ska vara antingen åtgärdad eller dokumenterad med ägare och motivering.
En arbetsordning för nästa AI-genererade testsvit
Om ett nytt test oväntat avslöjar att felet infördes långt tidigare kan du därefter använda Git Bisect för att hitta den första trasiga commiten. Mutationstestning visar att skyddet saknas; bisect visar när beteendet började avvika.