Technology Radar

Mutation Testing

Technieken
Trial
#software engineering

Wat is het?

Code coverage vertelt je welke regels code worden uitgevoerd door je tests, maar niet of je tests ook daadwerkelijk fouten detecteren. Een test zonder asserts haalt immers ook 100% coverage. Mutation testing vult dit gat: het meet de kwaliteit van je testsuite in plaats van alleen de dekking.

Het principe is simpel. Een mutation-testingtool introduceert automatisch kleine wijzigingen ("mutaties") in je productiecode: een > wordt een >=, een + wordt een -, een boolean wordt omgedraaid, een return value wordt vervangen. Vervolgens draait de tool je testsuite tegen elke gemuteerde versie. Faalt er minstens één test, dan is de mutant "gekilled" — je tests hebben de fout gevangen. Slaagt de hele suite, dan heeft de mutant het "overleefd" en heb je een blinde vlek gevonden. Het resultaat is een mutation score: het percentage gekillde mutanten.

Tooling

Voor de meeste ecosystemen bestaat een volwassen tool: Stryker voor JavaScript/TypeScript, .NET (Stryker.NET) en Scala, PIT (Pitest) voor de JVM, mutmut en cosmic-ray voor Python en cargo-mutants voor Rust. Stryker en PIT zijn het meest volwassen, met goede CI-integratie, incrementele runs en heldere HTML-rapportages.

Voordelen

  • Meet testeffectiviteit in plaats van testdekking : Je ontdekt tests die code wel uitvoeren maar niets verifiëren, ontbrekende asserts en ongeteste randgevallen
  • Concreet en actiegericht : Elke overlevende mutant wijst naar een specifieke plek in de code met een specifieke zwakte — veel bruikbaarder dan een abstract coveragepercentage.
  • Challenged Code Coverage : Coverage-targets zijn makkelijk te halen; een mutation score niet. Het stimuleert betekenisvolle tests in plaats van tests die alleen het getal opkrikken.
  • Sterke aanvulling op refactoring : Een hoge mutation score geeft vertrouwen dat je vangnet echt werkt voordat je gaat verbouwen.

Nadelen

  • Rekenintensief : De testsuite draait honderden tot duizenden keren. Op grote codebases kan een volledige run uren duren, al beperken moderne tools dit met incrementele analyse en het draaien van alleen relevante tests per mutant.
  • Equivalent mutants : Sommige mutaties veranderen het gedrag van het programma niet en kunnen dus nooit gekilled worden. Deze false positives vergen handmatige beoordeling.
  • Vereist een volwassen testsuite : Op een codebase met lage coverage of trage, flaky tests levert mutation testing vooral ruis op. Het is een verfijningsinstrument, geen startpunt.
  • Leercurve in interpretatie : Niet elke overlevende mutant is het fixen waard; teams moeten leren prioriteren.

Conclusie

Wij adviseren mutation testing gericht in te zetten, niet vlakdekkend:

  • Gebruik het op bedrijfskritieke modules met complexe logica — denk aan prijsberekeningen, validatieregels, financiële logica of security-gevoelige code — waar het vertrouwen in de testsuite echt waterdicht moet zijn.
  • Gebruik het als periodieke audit van de testkwaliteit, bijvoorbeeld voor een grote refactoring of bij het overnemen van een bestaande codebase.
  • Gebruik het in CI op incrementele basis: muteer alleen gewijzigde code in een pull request. Zo blijft de runtime beheersbaar en krijgen ontwikkelaars directe feedback.
  • Gebruik het niet als verplichte kwaliteitspoort over de hele codebase, op projecten zonder degelijke testsuite of op code met weinig logica (DTO's, configuratie, glue code). De kosten wegen daar niet op tegen de baten.

Mutation testing beantwoordt een vraag die coverage niet kan beantwoorden: _testen mijn tests eigenlijk wel iets?