Infection Framework 2026: Mutation Testing für PHP mit MSI, Konfiguration, CI Integration und allen Neuerungen bis Version 0.35.6, Praxiswissen von Never Code Alone
Mehr erfahren
Code Coverage ist der Anteil des Quellcodes, der beim Ausführen der Tests tatsächlich durchlaufen wird. In PHP misst PHPUnit diesen Wert mit Xdebug oder PCOV und zeigt ihn in Prozent pro Zeile, Zweig oder Pfad.
Die Zahl beantwortet genau eine Frage: Welcher Code lief während der Tests? Ob die Tests dabei etwas geprüft haben, steht nicht drin. Code ohne Coverage ist ein klares Warnsignal, denn dort findet garantiert kein Test einen Fehler. Code mit Coverage ist damit aber noch lange nicht getestet.
Testing ist unser Kerngeschäft. Wir arbeiten täglich mit PHPUnit, PHPStan, Psalm und Rector in Symfony Projekten und bauen Quality Gates in GitHub Actions und GitLab CI. Roland Golla beschäftigt sich seit über 20 Jahren mit Softwarequalität. Coverage Berichte lesen wir dabei als Hinweis, wo Tests fehlen. Ob die vorhandenen Tests etwas taugen, klären wir mit anderen Mitteln.
Teams begleiten wir im PHP Consulting, beim Refactoring Workshop und im Vibe Coding Consulting, wenn KI Agents Tests schreiben. Die passenden Werkzeuge stehen im Glossar: PHPUnit als Testframework, Xdebug als Coverage Treiber, Infection für Mutation Testing und Patch Coverage für neue Zeilen im CI.
Ein Tag inhouse mit Benjamin Klein für Developer, Tester und Admins. Context Engineering, Harness, ADRs und zwei Hands on Blöcke mit KI Agenten in eurem Setup.
Coverage ist nicht gleich Coverage. Ein kleines Beispiel zeigt, warum:
function discount(int $amount, bool $isMember): int
{
$rate = $isMember ? 10 : 0;
return $amount - intdiv($amount * $rate, 100);
}
Ein einziger Test mit discount(100, true) führt jede Zeile aus. Line Coverage: 100 Prozent. Der Fall für Nicht Mitglieder lief aber nie. Genau das zeigt Branch Coverage: Sie zählt jeden Zweig einer Bedingung einzeln und meldet hier nur die Hälfte.
Path Coverage geht noch weiter und prüft jede Kombination von Zweigen durch eine Funktion. Bei Methoden mit mehreren Bedingungen wächst die Zahl der Pfade schnell. Deshalb ist sie die gründlichste und zugleich teuerste Messung.
Alle drei Arten messen trotzdem nur Ausführung. Die vierte Stufe in der Tabelle unten fragt anders: Merken die Tests, wenn der Code kaputt geht?
PHPUnit sammelt Coverage nicht selbst. Es braucht einen Treiber als PHP Extension:
coverage ist langsamer, kann dafür auch Branch und Path Coverage. Gut für den gezielten Blick auf kritische Klassen.Welcher Treiber passt, beschreibt PHPUnit Autor Sebastian Bergmann in seinem Artikel PCOV oder Xdebug. Die Befehle im Alltag:
# Schneller Überblick im Terminal mit PCOV
php -d pcov.enabled=1 vendor/bin/phpunit --coverage-text
# HTML Report mit Line Coverage
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html build/coverage
# Zusätzlich Branch und Path Coverage
XDEBUG_MODE=coverage vendor/bin/phpunit --path-coverage --coverage-html build/coverage
# Clover XML für CI Tools
php -d pcov.enabled=1 vendor/bin/phpunit --coverage-clover build/coverage.xml
Der HTML Report ist das wertvollste Format. Er zeigt Zeile für Zeile, was nie lief. Mehrere Teilberichte, etwa aus parallelen Läufen mit Paratest, führt phpcov zusammen. Für Pull Requests eignet sich Patch Coverage, die nur die geänderten Zeilen betrachtet.
Der britische Ökonom Charles Goodhart beschrieb 1975 ein Muster aus der Geldpolitik. Die heute bekannte Kurzform stammt von der Anthropologin Marilyn Strathern: Sobald eine Kennzahl zum Ziel erklärt wird, taugt sie nicht mehr als Kennzahl.
Für Code Coverage heißt das: Solange ein Team die Zahl nur beobachtet, ist sie ein ehrlicher Indikator. Hängt dagegen eine Vorgabe daran, ein Dashboard oder gar ein Bonus, ändert sich das Verhalten. Meist ganz ohne böse Absicht.
Typische Muster, wenn 80, 90 oder 100 Prozent Pflicht sind:
Die Zahl steigt, die Sicherheit nicht. Sebastian Bergmann hat das in seinem Artikel Wenn die Metrik zum Ziel wird ausführlich beschrieben. Den Anstoß gab ein Vortrag von Eberhard Wolff zur Messbarkeit von Entwicklerproduktivität.
Einen Teil dieser Muster erkennt PHPUnit selbst. Ein Test ohne Assertion und ohne Expectation prüft nichts. PHPUnit markiert ihn deshalb als riskant.
Das wichtige Detail: Riskante Tests zählen nicht zur Coverage. PHPUnit verwirft die Daten, die während eines solchen Tests gesammelt wurden. Ein Test, der Code nur ausführt, hebt die Zahl also nicht an.
Manchmal ist ein Test ohne Assertion gewollt, etwa wenn allein der fehlerfreie Durchlauf die Aussage ist. Dafür gibt es das Attribut #[DoesNotPerformAssertions]. Es steht sichtbar im Code und fällt damit im Review auf.
Zusätzlich lässt sich PHPUnit strenger einstellen. So bricht der Lauf bei riskanten Tests ab, und jeder Test muss angeben, welchen Code er abdecken soll:
<phpunit
failOnRisky="true"
beStrictAboutCoverageMetadata="true"
requireCoverageMetadata="true">
<!-- Testsuites und Source wie gewohnt -->
</phpunit>
Im Test selbst legt ein Attribut fest, welcher Code zählt. Coverage, die nebenbei in anderen Klassen entsteht, fließt dann nicht in die Zahl ein:
use PHPUnit\Framework\Attributes\CoversFunction;
use PHPUnit\Framework\TestCase;
#[CoversFunction('discount')]
final class DiscountTest extends TestCase
{
public function testMemberGetsTenPercent(): void
{
$this->assertSame(90, discount(100, true));
}
public function testGuestPaysFullPrice(): void
{
$this->assertSame(100, discount(100, false));
}
}
Ein Test kann Assertions enthalten und trotzdem wertlos sein. Er prüft das Falsche oder viel zu locker. Solche Tests sind für PHPUnit nicht riskant, und die Coverage zählt sie voll mit.
Hier setzt Mutation Testing an. Infection baut kleine Fehler in den Code ein: aus > wird >=, aus true wird false, ein Rückgabewert fällt weg. Danach laufen die Tests. Bleiben sie grün, hat der Fehler überlebt, und die Tests prüfen an dieser Stelle nichts Relevantes.
composer require --dev infection/infection
# Nur geänderte und neue Dateien mutieren
vendor/bin/infection --threads=max --git-diff-filter=AM --show-mutations
Auch der Mutation Score ist nur ein Indikator. Wer einen festen Score erreichen muss, findet Wege dorthin, ohne dass die Tests besser werden. Goodhart's Law gilt für jede Zahl, die zur Vorgabe wird. Den größten Nutzen bringen die überlebenden Mutanten selbst: Sie zeigen konkret, welche Zeile kein Test bewacht.
Mit KI Coding Agents bekommt das alte Problem Tempo. Ein Prompt wie „Bring die Coverage auf 90 Prozent“ ist ein perfektes Goodhart Ziel. Der Agent liefert genau das: viele Tests, schnell, mit hoher Zahl. Ob sie Fehler finden, war nie Teil des Auftrags.
Das Muster ist typisch: Generierte Tests spiegeln oft nur, was der Code gerade tut, oder prüfen Rückgabewerte so allgemein, dass fast jede Änderung grün bleibt. Deshalb gehören bei KI gestützter Entwicklung feste Leitplanken in die Pipeline:
failOnRisky und requireCoverageMetadata in der phpunit.xml.Wie diese Guardrails im Alltag mit OpenCode und Open Weight Modellen aussehen, zeigen wir im Vibe Coding Consulting. Für ungetesteten Bestand starten wir mit Characterization Tests und schauen erst danach auf die Coverage.
Goodhart's Law ist ein menschliches Problem. Kein Tool nimmt einem Team die Entscheidung ab, wie es mit einer Zahl umgeht. Was sich in der Praxis bewährt:
Coverage passt gut neben statische Analyse mit PHPStan und Type Coverage. Jede Messung beleuchtet eine andere Seite der Codequalität.
Die Zahl war zum Ziel geworden, und niemand hatte es bemerkt.
Infection Framework 2026: Mutation Testing für PHP mit MSI, Konfiguration, CI Integration und allen Neuerungen bis Version 0.35.6, Praxiswissen von Never Code Alone
Mehr erfahrenEine hohe Zahl im Dashboard beruhigt. Vertrauen entsteht aber erst, wenn ein roter Test einen echten Fehler meldet. Darauf richten wir jede Testsuite aus, egal ob sie neu entsteht oder schon Jahre läuft.
Die Basis bilden PHPUnit für Unit Tests, Functional Tests und Symfony KernelTestCase für das Zusammenspiel der Services. Wie gut das Netz hält, prüft Infection. Coverage Daten sammeln Xdebug und PCOV, phpcov führt sie zusammen und Paratest hält die Laufzeit kurz. Vor dem Commit greift GrumPHP, im CI kommen PHPStan, Psalm und Rector dazu. Über die Oberfläche testen wir mit Cypress.
Alte Projekte sichern wir zuerst mit Characterization Tests ab. Danach geht es in kleinen Schritten weiter, begleitet im PHP Consulting.
Wir sind Experten für PHP und helfen Ihnen, Ihre digitalen Herausforderungen zu meistern. Unser erfahrenes Team unterstützt Sie bei PHP Updates, PHP Refactoring und berät Sie remote zu allen Fragen rund um PHP. Mit unseren vollautomatischen CI/CD Deployments und einer robusten Docker-Infrastruktur bringen wir Ihre PHP-Projekte auf das nächste Level. Vertrauen Sie auf unsere Expertise für zuverlässige und skalierbare PHP-Lösungen.
Die wichtigsten Antworten zu Code Coverage in PHP: Arten, Werkzeuge, sinnvolle Werte und der Umgang mit Vorgaben im Team.
Code Coverage ist der Anteil des Quellcodes, der während der Tests ausgeführt wird. In PHP misst PHPUnit diesen Wert mit den Extensions Xdebug oder PCOV und gibt ihn in Prozent aus, als Text, HTML Report oder Clover XML. Die Zahl zeigt, welcher Code lief. Ob die Tests das Ergebnis auch geprüft haben, zeigt sie nicht.
Eine feste Quote, die für jedes Projekt passt, gibt es nicht. Wichtiger als der Gesamtwert ist, dass komplexe und oft geänderte Klassen gut abgedeckt sind und neuer Code mit Tests kommt. Wird ein Prozentwert zur Pflicht, entstehen schnell Tests für triviale Stellen. Die Zahl steigt dann, die Sicherheit kaum.
Beides beantwortet verschiedene Fragen. Code Coverage zeigt, welcher Code bei Tests lief. Mutation Testing mit Infection zeigt, ob die Tests einen eingebauten Fehler bemerken. Coverage ist schnell und gut für den täglichen Lauf. Mutation Testing ist langsamer und deckt schwache Tests auf. Am besten nutzt du Coverage breit und Mutation Testing gezielt auf geänderten Code.
PCOV ist schneller und reicht für Line Coverage im CI völlig aus. Xdebug ist langsamer, kann dafür auch Branch und Path Coverage messen. Viele Teams nutzen PCOV für jeden Pull Request und Xdebug für den gezielten Blick auf kritische Klassen. Beide arbeiten direkt mit PHPUnit zusammen, ein Umbau der Tests ist nicht nötig.
Goodhart's Law besagt, dass eine Kennzahl ihren Wert verliert, sobald sie zum Ziel wird. Muss ein Team 90 Prozent Coverage erreichen, schreibt es bevorzugt leichte Tests oder Tests ohne echte Prüfung. Die Zahl steigt, die Aussagekraft sinkt. Coverage bleibt nur dann ehrlich, wenn das Team sie selbst zur Verbesserung beobachtet.
Line Coverage zählt jede Zeile, die mindestens einmal lief. Branch Coverage zählt jeden Zweig einer Bedingung einzeln, also wahr und falsch. Ein ternärer Ausdruck in einer Zeile kann bei Line Coverage voll abgedeckt sein, obwohl nur ein Fall getestet wurde. Branch Coverage macht diese Lücke sichtbar und braucht dafür Xdebug.
Path Coverage misst jede mögliche Kombination von Zweigen durch eine Funktion. Bei zwei unabhängigen Bedingungen gibt es schon vier Pfade, bei mehr Bedingungen wächst die Zahl rasant. Path Coverage ist die gründlichste Messung, aber auch die teuerste. In PHPUnit aktivierst du sie mit der Option path-coverage, Xdebug ist dafür Pflicht.
Wahrscheinlich ist der Test riskant. PHPUnit markiert Tests ohne Assertion oder Expectation als riskant und verwirft die Coverage Daten, die dabei gesammelt wurden. Ein zweiter Grund kann fehlendes oder falsches Coverage Metadata sein, etwa ein CoversClass Attribut, das auf eine andere Klasse zeigt. Der Testbericht nennt den genauen Grund.
Es kennzeichnet Tests, die bewusst keine Assertion enthalten, weil allein der fehlerfreie Durchlauf die Aussage ist. PHPUnit stuft solche Tests dann nicht als riskant ein. Das Attribut steht sichtbar im Code und fällt im Review auf. Nutze es sparsam, denn ein Test ohne Prüfung bleibt die Ausnahme.
Nein. 100 Prozent heißen nur, dass jede Zeile während der Tests lief. Tests ohne Prüfung, zu lockere Assertions oder fehlende Grenzfälle bleiben unsichtbar. Selbst bei voller Branch Coverage kann eine falsche Berechnung durchrutschen, wenn kein Test das Ergebnis vergleicht. Mutation Testing zeigt solche Lücken deutlich besser.
Gib KI Agents Verhalten und erwartete Ergebnisse vor, keine Prozentwerte. Ein Coverage Ziel im Prompt erzeugt schnell viele Tests mit wenig Aussage. Sichere die Pipeline mit failOnRisky und requireCoverageMetadata ab, lass Infection auf geänderte Dateien laufen und prüfe generierte Tests im Review. Tests sind Spezifikation und brauchen eine menschliche Entscheidung.
Ein harter Abbruch bei einer festen Gesamtquote lädt zu Goodhart Effekten ein. Besser funktioniert Patch Coverage im Pull Request, die nur neue und geänderte Zeilen betrachtet. Damit wächst die Abdeckung mit dem Code, ohne dass jemand alte Getter testet, nur um eine Zahl zu treffen. Riskante Tests als Fehler zu werten, ist dagegen immer sinnvoll.
Starte mit Characterization Tests für die Bereiche, die du ändern willst. Sie halten fest, was der Code heute tut. Erzeuge danach einen HTML Report und schau dir die roten Stellen in kritischen Klassen an. Eine Gesamtquote für das ganze Projekt ist am Anfang wenig aussagekräftig. Patch Coverage sorgt dafür, dass neuer Code sauber getestet ankommt.