NCA Social Media
Aktualisiert:
Autor:
Roland Golla
Rotes Baby Alien hinter Messgerät mit 100 Prozent und Schild Code Coverage

Code Coverage: Definition

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.

Inhalt

Code Coverage mit NCA: Schnelle Hilfe vom Experten

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.

NCA KI Workshop für Developer

Agentic Coding im Team, nicht nur im Glossar

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.

  1. 1
    Kostenloses Vorgespräch
    Stand, Rollen und Ziele klären
  2. 2
    Workshoptag bei euch
    Praxis am Vormittag, Hands on danach
  3. 3
    Praxistag optional
    Agenten im eigenen Projekt
Benjamin Klein
Benjamin Klein
Senior Consultant
Zum Workshop für Developer arrow_forward

Line, Branch und Path Coverage: Die Arten im Überblick

Coverage ist nicht gleich Coverage. Ein kleines Beispiel zeigt, warum:

Code:
          

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?

Coverage Stufen im Vergleich

Stufe Was gemessen wird Was sie aufdeckt
Line Coverage Jede ausführbare Zeile, die mindestens einmal lief Komplett ungetesteten Code, schnell messbar mit PCOV
Branch Coverage Jeder Zweig einer Bedingung, also wahr und falsch Bedingungen, bei denen nur ein Fall getestet ist, braucht Xdebug
Path Coverage Jede Kombination von Zweigen durch eine Funktion Fehler, die nur in bestimmten Kombinationen auftreten, braucht Xdebug
Mutation Score Anteil künstlich eingebauter Fehler, die Tests bemerken Tests, die laufen, aber nichts Relevantes prüfen, gemessen mit Infection
Aufsteigendes Säulendiagramm der vier Coverage Stufen Line, Branch, Path und Mutation. Inhalt steht textuell in der Tabelle darüber.

Coverage messen mit PHPUnit, Xdebug und PCOV

PHPUnit sammelt Coverage nicht selbst. Es braucht einen Treiber als PHP Extension:

  • PCOV ist schnell und leichtgewichtig, liefert aber nur Line Coverage. Gut für jeden CI Lauf.
  • Xdebug im Modus 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:

Code:
          

# 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.

Goodhart's Law: Wenn die Metrik zum Ziel wird

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:

  • Triviales zuerst: Getter, Setter und Konstruktoren sind schnell abgedeckt. Die komplexe Logik, in der die Fehler stecken, bleibt liegen.
  • Aufruf ohne Prüfung: Ein Test ruft eine Methode auf und schaut sich das Ergebnis nie an.
  • Nur keine Exception: Der Test ist grün, solange nichts explodiert. Ein falsches Ergebnis fällt nicht auf.

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.

Riskante Tests: Wie PHPUnit gegensteuert

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:

Code:
          

<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:

Code:
          

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));
    }
}

Mutation Testing: Die Gegenprobe zur Coverage

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.

Code:
          

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.

KI Agents und Coverage: Goodhart im Turbo

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:

  • Verhalten statt Zahl im Prompt: Beschreibe Fälle und erwartete Ergebnisse, keine Prozentwerte.
  • Riskante Tests als Fehler: failOnRisky und requireCoverageMetadata in der phpunit.xml.
  • Mutation Testing auf dem Diff: Infection nur auf geänderte Dateien, damit der Lauf schnell bleibt.
  • Menschliches Review der Tests: Tests sind Spezifikation. Ein Mensch entscheidet, ob sie das Richtige festhalten.

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.

Coverage richtig nutzen: Indikator statt Vorgabe

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:

  • Den Report lesen, nicht die Zahl: Rote Zeilen im HTML Report sind konkrete Aufgaben. Der Prozentwert oben ist nur die Summe.
  • Trends beobachten: Sinkt die Coverage bei neuem Code, fehlen Tests. Steigt sie sprunghaft, lohnt ein Blick auf die neuen Tests.
  • Neue Zeilen statt Gesamtwert: Patch Coverage im Pull Request ist fairer als eine feste Quote für das ganze Projekt.
  • Risiko zuerst: Komplexe und oft geänderte Klassen, die Churn PHP findet, verdienen Branch Coverage. Ein DTO braucht sie nicht.
  • Keine Boni an Kennzahlen: Eine Zahl, die das Team selbst zur Verbesserung nutzt, bleibt ehrlich. Dieselbe Zahl als KPI von außen wird optimiert.

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.

Sebastian Bergmann, Schöpfer von PHPUnit – phpunit.expert

Die NCA Erfahrung mit Testsuiten und Kennzahlen

Eine 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.

CYPRESS.IO Ambassador und IT Consultant für QA Engenieering und Qualität in PHP Projekten.
Erreichen Sie unsere PHP Consultant Spezialisten

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.

Häufige Fragen zu Code Coverage

Die wichtigsten Antworten zu Code Coverage in PHP: Arten, Werkzeuge, sinnvolle Werte und der Umgang mit Vorgaben im Team.

Was ist Code Coverage 2026 in PHP?

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.

Welche Code Coverage ist 2026 sinnvoll?

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.

Code Coverage oder Mutation Testing: Was ist 2026 besser?

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 oder Xdebug für Coverage 2026?

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.

Was bedeutet Goodhart's Law für Code Coverage 2026?

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.

Was ist der Unterschied zwischen Line und Branch Coverage?

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.

Was ist Path Coverage?

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.

Warum zählt mein Test nicht zur Coverage?

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.

Wofür ist das Attribut DoesNotPerformAssertions da?

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.

Bedeuten 100 Prozent Coverage fehlerfreien Code?

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.

Wie gehe ich mit KI generierten Tests und Coverage um?

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.

Sollte die CI bei zu niedriger Coverage abbrechen?

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.

Wie starte ich mit Coverage in einem Legacy Projekt?

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.