Mutation Testing ist eine Methode, um die Qualität einer Test-Suite zu prüfen: Ein Tool verändert automatisch den Produktionscode durch sogenannte Mutanten, etwa indem es ein größer-als durch größer-gleich ersetzt, und prüft dann, ob mindestens ein Test fehlschlägt. Schlägt kein Test an, überlebt der Mutant, was auf eine Lücke in den Tests hinweist.
Das Wichtigste in Kürze
- Mutation Testing prüft nicht den Produktionscode, sondern die Qualität der Test-Suite: Ein absichtlich eingebauter Fehler im Code gilt als getötet, sobald mindestens ein Test fehlschlägt.
- Das Java-Framework PIT optimiert Laufzeiten durch inkrementelle Analyse: Es vergleicht Hashcodes von Code und Tests und führt nur Mutationen für tatsächlich geänderte Stellen erneut aus.
- Mutation Testing sollte nicht auf die gesamte Codebasis angewendet werden, sondern gezielt auf die Kern-Business-Logik, weil UI- oder Datenbankzugriffe den Aufwand unnötig in die Höhe treiben.
- Wer Mutation Testing regelmäßig einsetzt, schreibt mit der Zeit bessere Tests und besseren Code, weil das Wissen über typische Mutanten schon beim Schreiben des Codes mitfließt.
Was ist Mutation Testing?
Mutation Testing ist ein Verfahren, mit dem du deine Tests testest. Statt nur zu prüfen, ob der Produktionscode funktioniert, prüft die Methode, ob deine Test-Suite überhaupt in der Lage ist, Fehler zu finden.
Das Prinzip ist über 50 Jahre alt. Richard Lipton beschrieb es 1971 in einem Paper. Die Grundidee hat sich seitdem kaum verändert.
Der Ablauf ist simpel. Du hast eine Test-Suite, die grün ist und bei der du davon ausgehst, dass alles in Ordnung ist. Jetzt baust du absichtlich einen Fehler in deinen Code ein und schaust, ob die Test-Suite ihn findet. Findet sie ihn, ist sie zumindest für diese Stelle gut geeignet. Findet sie ihn nicht, hast du eine Lücke.
Diese eingebauten Änderungen heißen nicht Bugs, sondern Mutanten. Daher der Name Mutation Testing. Ein Mutant ist eine gezielte Veränderung am Produktionscode.
Welche Mutanten kommen zum Einsatz?
Die Art der Mutanten hängt teilweise von der Programmiersprache ab. In der Java-Welt lassen sich mehrere Kategorien unterscheiden, von denen einige sprachunabhängig funktionieren.
Eine häufige Kategorie sind Conditional Boundaries. Aus einem größer wird ein größer gleich, aus einem kleiner ein kleiner gleich. Die Grenzen einer Abfrage verschieben sich. Andere Mutatoren negieren ganze Bedingungen, etwa indem aus gleich gleich ein ungleich wird.
Increment-Mutatoren tauschen plus plus gegen minus minus. Arithmetische Mutatoren ersetzen Addition durch Subtraktion oder Multiplikation durch Division.
Auch das Verhalten ganzer Methoden lässt sich verändern. Eine void-Methode, die etwas tut, aber nichts zurückgibt, wird vom Mutator einfach nicht aufgerufen. Bei Methoden mit Rückgabewert wird statt eines Objekts null zurückgegeben, bei primitiven Typen eine Null oder ein leerer String.
Der Fantasie sind kaum Grenzen gesetzt. Wer alle denkbaren Mutatoren von Hand auf eine ganze Codebasis anwenden wollte, wäre lange beschäftigt. Genau deshalb übernehmen Frameworks diese Arbeit.
Wie funktioniert ein Mutation-Test-Tool?
Ein Tool für Mutation Testing bildet einen Mutanten, lässt alle Tests laufen und prüft, ob mindestens ein Test fehlschlägt. Schlägt ein Test fehl, ist das ein gutes Zeichen: Die Test-Suite hat den Mutanten gekillt.
Die Sprache rund um Mutation Testing ist martialisch. Ein Mutant überlebt oder wird getötet. Den Mutanten zu töten ist in diesem Kontext das gewünschte Ergebnis.
In der Java-Welt ist PIT ein verbreitetes Framework. Es lässt sich in den Buildprozess einbinden und wendet die Mutatoren auf den Source-Code an. Anschließend protokolliert es, wie sich der Code verhält. Dieses Protokoll wertest du danach aus.
PIT bringt eine Vorauswahl an Mutatoren in verschiedenen Ausbaustufen mit, von einem Basic-Set bis zu einer Stufe, die alle mitgelieferten Mutatoren anwendet. Du kannst einzelne Mutatoren gezielt an- und abschalten, weil nicht jeder für deinen Code sinnvoll ist und mehr Mutatoren längere Laufzeiten bedeuten.
Mutation-Tests dauern lang, wenn du sie nicht eingrenzt
Mutation-Tests können sehr lange laufen. Für jeden gebildeten Mutanten wird die gesamte relevante Test-Suite ausgeführt. Wer das unkontrolliert auf die komplette Codebasis anwendet, blockiert seinen Buildprozess.
PIT ist an dieser Stelle stark optimiert. Es schreibt eine History mit Hashcodes von Code und Tests. Bei einer erneuten Ausführung prüft das Tool, welche Tests von einer Änderung betroffen sind, und führt nur diese aus. Wo sich nichts geändert hat, ändert sich auch das Ergebnis nicht.
Diese inkrementelle Arbeitsweise macht den Unterschied für die Integration. Würde jeder Lauf Stunden dauern, ließe sich Mutation Testing kaum in einen Daily Build einbauen. Läuft es nur inkrementell, hast du mehr Möglichkeiten, es in laufende Builds zu integrieren.
Im Buildprozess steht Mutation Testing immer hinter dem Kompilieren und dem normalen Testlauf. Erst wenn alle Tests grün sind, folgt die Mutation-Analyse.
Fang klein an, nicht bei der ganzen Codebasis
Wer Mutation Testing einführt, sollte es nicht sofort auf den gesamten Source-Code loslassen. Beginne mit der Kern-Business-Logik.
UI-Code oder Datenbankzugriffe per Mutation Testing zu prüfen, ist anfangs Overkill. Frameworks wie PIT erlauben es, die Analyse auf bestimmte Packages oder einzelne Klassen einzugrenzen. So entscheidest du, wo Mutation Testing tatsächlich Wert liefert, und vermeidest übermäßig lange Laufzeiten.
Auch bei der Anzahl der Mutatoren lohnt ein vorsichtiger Start. Schalte zunächst nur wenige ein und arbeite dich vor, während du die Ergebnisse genau analysierst und Tests oder Code anpasst.
Was die Ergebnisse dir verraten
Mutation Testing deckt drei Arten von Schwachstellen auf. Es zeigt, ob deine Testdaten gut sind und etwa Grenzbereiche abdecken. Es zeigt, was du noch gar nicht getestet hast. Und es bringt Logikprobleme ans Licht.
Wenn eine Mutation am Code keinen Test zum Fehlschlagen bringt, prüfst du beide Seiten: die Tests und den Code. Manchmal stellt sich dabei heraus, dass die Tests zwar grün sind, aber eine falsche Logik prüfen oder am eigentlichen Zweck vorbeigehen.
Genau hier entsteht der Mehrwert. Du verbesserst nicht nur deine Tests, sondern auch deinen Code.
Äquivalente Mutanten und ihre Tücken
Ein bekanntes Problem von Mutation Testing sind äquivalente Mutanten. Das sind Mutanten, die die Logik des Codes nicht wirklich verändern. Der Code verhält sich nach der Mutation genauso wie vorher, und entsprechend schlägt kein Test fehl.
PIT versucht, äquivalente Mutanten im Vorhinein zu erkennen und zu vermeiden. Vollständig verhindern lassen sie sich nicht. Diese Stellen musst du selbst herausfiltern.
Erkennbar werden sie über das Grundprinzip: Ein Mutant wurde gebildet, aber alle Tests bleiben grün. Bleibt ein Mutant am Leben, besteht immer die Möglichkeit, dass es ein äquivalenter Mutant ist.
Häufig sind äquivalente Mutanten je nach Code aber selten. Manchmal lässt sich der Code so umschreiben, dass das Problem verschwindet. Oft sind sie auch ein Hinweis darauf, dass mit dem Code selbst etwas nicht stimmt: Wenn ein anderes Konstrukt dasselbe Ergebnis liefert, ist bei der Implementierung möglicherweise etwas schiefgelaufen.
Das Tool wird zum Trainer für besseren Code
Mit zunehmender Nutzung verschiebt sich der Effekt von Mutation Testing. Anfangs liefert es vor allem neue Testideen. Je öfter du es einsetzt, desto stärker wirkt der Lerneffekt.
Du kennst nach einiger Zeit die Mutanten, die angewendet werden, und reagierst schon beim Schreiben der Tests darauf. Birgit Kratz beschreibt, wie schwer es ihr fiel, für einen Vortrag bewusst Beispielcode zu erstellen, bei dem ein Mutant überlebt. Wer das Tool gut kennt, schreibt fast automatisch Tests und Code, die kaum noch durchrutschen lassen.
Im Team kann dieser Lerneffekt deutlich größer ausfallen. Eine gemeinsame Session, in der die Findings der Mutation-Tests durchgegangen werden, bringt nach Birgits Einschätzung viel.
Es ist nicht nur gut für bessere Tests, sondern auch für besseren Code. Birgit Kratz
In der Praxis stößt dieser Teamansatz allerdings auf Widerstand. Viele reagieren auf die Idee, die eigenen Tests zu testen, mit der Frage, wo das aufhören soll. Häufig bleibt es deshalb bei der Nutzung durch einzelne Entwickler.
Die zwei Voraussetzungen für Mutation Testing
Bevor du Mutation Testing einsetzen kannst, brauchst du zwei Dinge: Tests und grüne Tests.
Das klingt selbstverständlich, ist es aber nicht. An genau diesen beiden Faktoren scheitert es in der Praxis oft. Entweder gibt es überhaupt zu wenige Tests, oder die vorhandenen Tests sind nicht grün.
Erst wenn beide Bedingungen erfüllt sind, lässt sich Mutation Testing sinnvoll anwenden, sei es lokal oder als Stufe in der Build-Pipeline hinter dem normalen Testlauf.


