Mutation Testing, zweiter Anlauf

Dunkler Serverraum-Korridor mit einer Flucht cyanfarben leuchtender Prüf-Tore; eine einzelne amber glühende Codekapsel mit feinem Riss schwebt unbehelligt hindurch, kein Alarm schlägt an
Abbildung 1. Der Mutant passiert jedes Tor, und kein Alarm schlägt an: Genau dieses Loch soll Mutation Testing sichtbar machen

2020 habe ich Infection auf ein gewachsenes Shopsystem losgelassen. Infection baut absichtlich kleine Fehler in PHP-Code ein und prüft, ob die Tests sie bemerken. Aus einem >= wird ein >, aus einem Plus ein Minus, aus einer Bedingung ihr Gegenteil. Jede dieser Varianten heißt Mutant. Stirbt der Mutant, weil ein Test rot wird, hat die Suite ihren Job gemacht. Überlebt er, hat die Suite ein Loch.

Flussdiagramm des Mutation-Testing-Prinzips: Aus dem Originalcode entsteht ein Mutant mit einer kleinen Änderung, die Testsuite läuft dagegen; wird mindestens ein Test rot, stirbt der Mutant und die Suite prüft wirklich, sonst überlebt der Mutant und die Suite hat ein Loch
Abbildung 2. Das Prinzip: Der Mutant stirbt, oder die Suite hat ein Loch

Das Werkzeug hat geliefert. Es fand Löcher, von denen die Coverage nichts ahnte. Abgeschaltet habe ich es trotzdem.

Woran es 2020 wirklich scheiterte

Nicht am Werkzeug. An der Rechnung dahinter. Jeder überlebende Mutant will von einem Menschen angesehen werden. Ist es ein echtes Loch? Dann fehlt ein Test, und den muss jemand schreiben. Oder ist es eine äquivalente Mutante, eine Änderung, die das Verhalten gar nicht ändert und die kein Test der Welt fangen kann? Dann war die Analyse umsonst.

Beides kostet Entwicklerzeit. Bei jedem Lauf, nicht einmalig. Ein guter Test kostete damals ohnehin schon mehr als der Code, den er prüft. Mutation Testing setzte noch eine Schicht Handarbeit obendrauf: die Löcher finden, die Löcher bewerten, die Löcher stopfen. Das Budget dafür gab es nicht. Also flog die Prüfung raus, und die Coverage-Zahl blieb als einzige Wahrheit übrig.

Was sich seit damals gedreht hat

Heute schreibt in meinem Nebenprojekt Calculoria eine KI den Großteil der Tests. Rund 5.000 Stück sind so entstanden. Die Abdeckung ist hoch, und genau deshalb traue ich ihr weniger denn je. Ein Sprachmodell erzeugt mühelos Tests, die jede Zeile ausführen und kaum eine prüfen. Smoke-Tests auf Steroiden. Die Coverage sieht tadellos aus und misst doch nur, dass der Code gelaufen ist. Nicht, dass ihn jemand festgenagelt hat.

Damit ist Mutation Testing vom Luxus zur fehlenden Kontrolle geworden. Es ist das einzige mir bekannte Verfahren, das die Testsuite selbst befragt: Würdet ihr es überhaupt merken, wenn der Code kaputt wäre?

Und die Rechnung von 2020 hat sich umgedreht. Die teure Hälfte, das Bewerten und Stopfen der Löcher, ist heute delegierbar. Ein überlebender Mutant ist eine präzise Arbeitsanweisung an einen Agenten: Hier ist die Codezeile, hier die Änderung, die niemand bemerkt hat. Schreib den Test, der sie bemerkt. Das kostet Tokens, keine Entwicklerwochen.

Gegenüberstellung der Kostenverteilung: 2020 fand das Werkzeug Infection die Löcher, aber ein Mensch musste jeden Mutanten bewerten und fehlende Tests schreiben; heute übernehmen Agenten das Bewerten und Nachrüsten gegen Tokens, beim Menschen bleibt die Stichprobe als Kontrolle der Kontrolle
Abbildung 3. Dieselben drei Schritte, andere Kostenträger: 2020 zahlte ein Mensch bei jedem Lauf, heute übernehmen Agenten die teure Mitte

Der Versuchsaufbau

Genau das probiere ich jetzt in Calculoria aus. Der Plan:

  1. Baseline messen: Mutation Score auf den Kernmodulen, einmal komplett. Die Zahl sagt, wie viel von der schönen Abdeckung echt ist.

  2. Überlebende Mutanten als Backlog: Jeder Mutant wird ein Arbeitsauftrag für einen Agenten. Test schreiben, Mutant töten, Gate grün.

  3. Nachmessen: Score danach, Token-Kosten pro getötetem Mutanten, Zeitaufwand, und der Anteil äquivalenter Mutanten, der übrig bleibt.

Ablaufdiagramm des Experiments: Nach der Baseline-Messung des Mutation Scores wird jeder überlebende Mutant zum Arbeitsauftrag für einen Agenten, ein Gate prüft ob die Suite grün bleibt und der Mutant stirbt; danach werden Score, Token-Kosten und Zeit nachgemessen, äquivalente Mutanten und die Handstichprobe gegen Score-Gaming bleiben Handarbeit
Abbildung 4. Der Versuchsaufbau: Überlebende Mutanten werden zum Backlog für Agenten, die Stichprobe bleibt beim Menschen

Drei Hypothesen gehen mit ins Rennen:

H1: Der Mutation Score liegt deutlich unter dem, was die Coverage verspricht. Wenn nicht, war mein Misstrauen unbegründet. Auch das wäre ein Ergebnis.

H2: Das Stopfen der Löcher rechnet sich diesmal. Tokens pro Mutant statt Entwicklerstunden pro Mutant. 2020 war das der Killer, heute sollte es der billigste Teil sein.

H3: Ein harter Rest bleibt Handarbeit. Äquivalente Mutanten kann kein Agent töten, er kann sie höchstens erkennen und begründen. Wie groß dieser Rest ist, entscheidet über die Alltagstauglichkeit.

Was schiefgehen kann

Es ist ein Experiment, und ich rahme es ehrlich als eines. Die Laufzeit auf einer Suite dieser Größe kann unangenehm werden, denn Mutation Testing führt die Tests für jeden Mutanten erneut aus. Ein anderes Risiko macht mir mehr Sorgen: Ein Agent, der auf den Mutation Score optimiert, kann Tests schreiben, die Mutanten töten und trotzdem nichts Sinnvolles prüfen. Auch ein Mutation Score ist nur eine Metrik, und Metriken kann man bedienen. Die Stichprobe von Hand bleibt deshalb im Aufbau. Als Kontrolle der Kontrolle.

Die Ergebnisse schreibe ich hier auf, mit echten Zahlen, auch wenn sie gegen mich sprechen. 2020 hat das Experiment gegen mein Budget verloren. Mal sehen, gegen wen es diesmal verliert.


Klingt nach deinem Thema? Schreib mir. Im Erstgespräch klären wir unverbindlich, ob und wie ich helfen kann.