Dieses Bild zeigt eine stilisierte Darstellung einer DNA-Doppelhelix, wobei ein einzelner Abschnitt hervorgehoben ist. Die DNA-Stränge sind blau dargestellt, während ein kurzer Abschnitt der Basenpaare und die dazugehörigen Verbindungen rot leuchten. Die Bildsprache kommuniziert eine Mutation oder gezielte Veränderung auf molekularer Ebene, wie sie bei genetischer Analyse, Mutationstests oder biotechnologischer Bearbeitung angewendet wird. Der fokussierte Bereich könnte eine Mutation, Reparatur oder einen gezielten Eingriff symbolisieren, wie er in der Genforschung verwendet wird.

In der modernen PHP-Entwicklung ist die große Auswahl an Entwicklungs- und Analysewerkzeugen manchmal Fluch und Segen zugleich. Für viele Aufgaben gibt es mehrere etablierte Tools, die jeweils eigene Stärken, Schwächen und Philosophien aufweisen.

Dieser Artikel ist Teil einer Reihe, in der jeweils zwei dieser Werkzeuge im direkten Vergleich beleuchtet werden. Ich zeige, wie sich ihre Konzepte unterscheiden, für welche Einsatzzwecke sie sich besonders gut eignen und worauf Teams achten sollten, wenn sie robuste, wartbare und effiziente Entwicklungsumgebungen aufbauen wollen.

Ich habe bereits über PCOV und Xdebug, PHP_CodeSniffer und PHP-CS-Fixer sowie Psalm und PHPStan geschrieben. In diesem Artikel möchte ich der Frage nachgehen, ob Path Coverage oder Mutation Testing die bessere Lösung für die Beurteilung der Qualität einer Testsuite ist.

Code Coverage

Die Code Coverage ist eine fundamentale Softwaremetrik im Software Testing und misst den Anteil des Quellcodes, der während der Testausführung tatsächlich durchlaufen wird, also durch Tests abgedeckt ist. Sie wird in Prozent angegeben und setzt in ihrer einfachsten Variante, der sogenannten Line Coverage, die Anzahl der von Tests ausgeführten Codezeilen ins Verhältnis zur Anzahl der ausführbaren Codezeilen. Eine Zeile gilt als ausgeführt, wenn mindestens eine Anweisung, die in dieser Zeile steht, mindestens einmal während der Testausführung ausgeführt wurde.

Die Branch Coverage geht einen Schritt weiter und misst, ob alle möglichen Ausgänge von Entscheidungspunkten (if-Anweisungen, Schleifen etc.) getestet wurden. Jede Verzweigung muss sowohl den true- als auch den false-Fall durchlaufen haben. 100 % Branch Coverage garantieren 100 % Line Coverage, aber nicht umgekehrt.

Path Coverage ist die umfassendste Form der Code Coverage und betrachtet alle möglichen Ausführungspfade durch den Code. Ein solcher Pfad ist eine einzigartige Abfolge von Anweisungen vom Startpunkt bis zum Endpunkt einer Codeeinheit, beispielsweise einer Funktion oder Methode. 100 % Path Coverage garantieren 100 % Branch Coverage, aber nicht umgekehrt.

Code Coverage ist ein quantitatives Maß und kein qualitatives. Es beantwortet die Frage "Wie viel?", nicht "Wie gut?". Deshalb ist es wichtig, Code Coverage als das zu verstehen, was sie ist: ein Indikator für ausgeführten Code und nicht für die Qualität der Tests.

In Kombination mit Xdebug kann PHPUnit Daten zur Path Coverage sammeln und in HTML- sowie XML-Reports ausgeben. In diesem Vortrag kannst du das in Aktion sehen:

So you think you know PHPUnit

Mutation Testing

Mutation Testing ist eine Technik, mit der sich die Qualität bestehender Tests bewerten lässt. Außerdem kann sie bei der Entwicklung neuer Tests helfen. Dazu werden kontrollierte Änderungen am Code vorgenommen, die typische Programmierfehler simulieren oder wertvolle Tests erzwingen. Jede modifizierte Version des Codes wird als "Mutante" bezeichnet. Die Tests müssen diese Mutanten "töten", indem sie die Unterschiede zwischen dem ursprünglichen Code und dem mutierten Code aufzeigen.

Mithilfe von Mutation Testing können wir Codestellen aufdecken, die nicht gut genug getestet sind, obwohl sie vielleicht bereits durch einen Test abgedeckt sind. Mithilfe der Softwaremetrik Mutation Score können wir die Effektivität der Tests messen. Auch diese Metrik wird in Prozent angegeben: sie setzt die Anzahl getöteter Mutanten ins Verhältnis zur Gesamtanzahl der Mutanten. Ein Mutation Score von 100 % bedeutet, dass alle Mutanten von den Tests erkannt wurden.

Wie gut sind meine Tests tatsächlich darin, Fehler zu erkennen? Sind sie effektiv genug, um korrektes von fehlerhaftem Code zu unterscheiden? Testen meine Tests das richtige Verhalten? Dies sind die Fragen, die wir durch Mutation Testing beantworten können. Ein hoher Mutation Score bedeutet, dass deine Tests robust sind und wahrscheinlich auch echte Bugs finden würden. Ein niedriger Score zeigt hingegen Schwachstellen in deiner Testsuite auf, die verbessert werden müssen.

Mit Infection kann Mutation Testing für Code durchgeführt werden, der über mit PHPUnit implementierte Tests verfügt.

Was denn nun?

Path Coverage fokussiert sich auf die strukturelle Vollständigkeit und stellt sicher, dass alle möglichen Ausführungspfade durch den Code getestet werden. Mutation Testing hingegen konzentriert sich auf die Qualität der Testlogik und überprüft, ob die Tests tatsächlich in der Lage sind, Fehler zu erkennen.

Code Coverage, einschließlich Path Coverage, kann nur messen, in welchem Umfang Code durch Tests ausgeführt wird, aber nicht, ob die Tests das Codeverhalten tatsächlich validieren. Mutation Testing überwindet diese Probleme der Code Coverage, indem es sowohl Ausführung als auch Assertions abdeckt.

Das Sammeln von Daten für Path Coverage mit Xdebug benötigt ein Vielfaches an Zeit und Arbeitsspeicher dessen, was für Line Coverage notwendig ist. Und da ich mit der Darstellung der Path Coverage in PHPUnit noch nicht zufrieden bin, setze ich persönlich in meiner täglichen Arbeit vorrangig auf Mutation Testing mit Infection. Die mit Infection erzeugten Reports sind gut lesbar und bieten konkrete Handlungsempfehlungen, während der Ressourcenbedarf deutlich moderater ausfällt. Path Coverage setze ich daher nur gezielt ein, wenn spezifische Analysen dies erfordern.