Dieses Bild zeigt die Nahaufnahme eines hellen Schneidermaßbands, das sich vor einem warmen, unscharfen Hintergrund in lockeren Schlaufen und Windungen auftürmt. Die Zahlen und Skalenstriche sind nur stellenweise scharf. Die Bildsprache kommuniziert den Kern von Goodhart's Law: Ein Messinstrument, das sich in sich selbst verheddert, misst nichts mehr. Wenn die Metrik zum Ziel wird, verliert die Messung ihre Orientierung und ihren Zweck.

Vor Kurzem habe ich den Vortrag „Können wir Entwickler:innen-Produktivität messen?“ von Eberhard Wolff gesehen, dessen Arbeit ich seit vielen Jahren sehr schätze. Eine Passage hat mich dabei besonders zum Nachdenken gebracht: seine Anwendung von Goodhart's Law auf Code Coverage. Daraus ist dieser Artikel entstanden.

Goodhart's Law

Der britische Ökonom Charles Goodhart formulierte 1975 eine Beobachtung über Geldpolitik, die längst weit über die Wirtschaftswissenschaften hinaus Bedeutung erlangt hat. Die heute geläufige Formulierung stammt von der Anthropologin Marilyn Strathern:

When a measure becomes a target, it ceases to be a good measure.

Wenn eine Metrik zum Ziel wird, hört sie auf, eine gute Metrik zu sein.

Solange eine Kennzahl lediglich beobachtet wird, kann sie ein nützlicher Indikator sein. Sobald jedoch Belohnung oder Bestrafung an ihr hängen, beginnen Menschen, die Kennzahl selbst zu optimieren und nicht mehr das, was sie eigentlich messen sollte. Die Metrik verliert dadurch genau die Aussagekraft, derentwegen sie zum Ziel erklärt wurde.

Code Coverage als Ziel

Die Code Coverage misst den Anteil des Quellcodes, der während der Testausführung tatsächlich durchlaufen wird. Als Indikator ist das nützlich: Code, der von keinem Test ausgeführt wird, kann Fehler enthalten, die garantiert kein Test findet. Eine niedrige Code Coverage ist daher ein klares Warnsignal.

Sobald ein Team 80 %, 90 % oder gar 100 % Code Coverage erreichen muss, weil eine Vorgabe, ein Dashboard oder sogar ein Bonus daran hängt, wird die Metrik manipuliert. Genau davor warnt Goodhart's Law. Das muss nicht einmal in böser Absicht geschehen. Das Team testet bevorzugt die trivialen Teile des Codes, weil sie einfach abzudecken sind, während die komplexen Teile, in denen die Fehler wahrscheinlich stecken, ungetestet bleiben. Tests rufen Methoden auf, ohne deren Ergebnisse zu überprüfen. Oder sie stellen lediglich sicher, dass keine Exception ausgelöst wird.

Die Code Coverage steigt in allen diesen Fällen. Die Tests finden trotzdem keine Probleme. Eine eigentlich gute Metrik hat aufgehört, eine gute Metrik zu sein, weil sie zum Ziel erklärt wurde.

Riskante Tests

Einen Teil dieser Manipulationen kann PHPUnit erkennen. Ein Test, der keine Zusicherung (Assertion) oder Erwartung (Expectation) ausführt, prüft nichts. Er führt Code aus, aber er stellt keine Behauptung über dessen Verhalten auf. Deshalb markiert PHPUnit einen solchen Test standardmäßig als riskant und weist im Testergebnis darauf hin.

Für Goodhart's Law ist dabei ein Detail wichtig: Riskante Tests tragen nicht zur Code Coverage bei. PHPUnit verwirft die Code Coverage-Daten, die während der Ausführung eines riskanten Tests gesammelt wurden. Ein Test, der Code nur ausführt, ohne irgendetwas zu prüfen, erhöht deine Code Coverage also nicht.

In seltenen Fällen ist ein Test ohne Zusicherung beabsichtigt, etwa wenn allein die erfolgreiche Ausführung ohne Exception die relevante Aussage ist. Ein solcher Test kann mit dem Attribut #[DoesNotPerformAssertions] gekennzeichnet werden. Diese Kennzeichnung ist explizit, steht im Code und ist damit im Code Review für alle sichtbar.

Mutation Testing

Ein Test kann Zusicherungen enthalten und trotzdem wertlos sein, beispielsweise wenn er das Falsche prüft oder zu schwach formuliert ist. Solche Tests erkennt PHPUnit nicht als riskant. Hier hilft Mutation Testing: Ein Werkzeug wie Infection nimmt kontrollierte Änderungen am Code vor, die typische Programmierfehler simulieren, und prüft, ob die Tests diese Änderungen bemerken. Tests, die keine einzige Mutante „töten“, testen offensichtlich nichts, das für das Verhalten des Codes relevant ist.

In meinem Artikel „Path Coverage oder Mutation Testing?“ habe ich ausführlich beschrieben, wie Mutation Testing funktioniert und warum ich es in meiner täglichen Arbeit einsetze. Mutation Testing ist eine weitere technische Lösung, um sicherzustellen, dass ein Test tatsächlich etwas testet.

Ein menschliches Problem

So wertvoll diese Werkzeuge sind, das Problem, das Goodhart's Law beschreibt, kann keine technische Maßnahme lösen. Jede Metrik, die zum Ziel erklärt wird, lädt zur Manipulation ein. Das gilt für die Code Coverage genauso wie für den Mutation Score. Wer einen vorgegebenen Mutation Score erreichen muss, wird Wege finden, ihn zu erreichen, ohne dass die Tests dadurch besser werden.

Goodhart's Law beschreibt ein menschliches Problem, genauer: ein zwischenmenschliches. Es geht um Anreize, um Vertrauen und um die Frage, ob ein Team eine Metrik als Werkzeug zur eigenen Verbesserung nutzt oder ob sie ihm als Ziel vorgegeben wird. Eine Metrik, die ein Team selbst beobachtet, um besser zu werden, bleibt ein guter Indikator. Dieselbe Metrik als Vorgabe von außen wird manipuliert werden.

Werkzeuge wie PHPUnit und Infection können die plumpesten Formen der Manipulation erkennen und ihnen die Wirkung nehmen. Die Entscheidung, Metriken als Indikatoren zu behandeln und nicht als Ziele, können sie uns nicht abnehmen. Die müssen wir Menschen schon selbst treffen.