Ein Mann sitzt auf einer Düne mit trockenem Gras. Er Mann wirkt nachdenklich oder vielleicht sogar traurig, denn er stützt seinen Kopf mit einer Hand auf den Arm, während er nach unten schaut.

In den letzten Monaten wurde mir immer wieder die Frage gestellt: "Sebastian, mit welcher PHP-Erweiterung sammelst du eigentlich Code Coverage-Daten?" Diese Frage mag einfach klingen, aber um sie zu beantworten, bedarf es einer kleinen Geschichtsstunde.

Die Geschichte von Code Coverage für PHP beginnt mit Xdebug 1.2 im April 2003. Eigentlich beginnt sie natürlich schon früher, denn Derick Rethans musste das Feature erst entwickeln, bevor es seinen Weg in eine erste Version finden konnte. Damals war das Sammeln von Code-Coverage-Daten mit Xdebug sehr langsam und die erste Version des Reports, den PHPUnit aus diesen Daten generierte, war sehr hässlich.

Von April bis Dezember 2006 lebte ich in Norwegen und schrieb meine Diplomarbeit bei eZ Systems, wo auch Derick zu der Zeit arbeitete. Wir arbeiteten zusammen an dem Open Source-Projekt eZ Components, für das es natürlich auch Tests gab. Ohne Code Coverage-Analyse konnten wir diese Tests in wenigen Minuten durchführen. Mit Code Coverage-Analyse dauerte es den halben Tag. Das war leider viel zu langsam, um wirklich einen praktischen Nutzen zu haben.

Xdebug 2

Mit der Veröffentlichung von Xdebug 2 im Jahr 2007 wurde die Code Coverage-Analyse deutlich schneller und damit sinnvoll nutzbar. Seitdem hat Derick die Code Coverage-Funktionalität immer wieder verbessert. Beispielsweise wurde mit Xdebug 2.6 ein Filtermechanismus eingeführt, der die Code Coverage-Analyse beschleunigt.

In einem früheren Artikel habe ich bereits über diesen Filtermechanismus geschrieben, der übrigens wegen anderer Entwicklungen überflüssig geworden ist.

In einem weiteren Artikel habe ich die Verbesserungen vorgestellt, die wir an PHPUnit vorgenommen haben, um Code Coverage-Reports schneller erzeugen zu können.

PCOV

Natürlich könnte die Performance einer Funktionalität wie der Code Coverage Analyse immer besser sein: Entwicklerinnen und Entwicklern möchten weniger Zeit mit dem Warten auf das Ausführen der Tests verbringen und für den Planeten ist es auch besser, wenn die CPU dabei weniger Strom verbraucht.

Das war auch die Motivation von Joe Watkins, der 2019 mit PCOV eine Alternative für das Sammeln von Code Coverage-Daten vorgestellt hat. Seitdem unterstützt PHPUnit neben Xdebug auch PCOV.

Leider wird PCOV derzeit nicht weiterentwickelt. Die letzte Version wurde 2021 veröffentlicht, kann aber mit aktuellen PHP-Versionen verwendet werden. Ohne einen Patch für das kommende PHP 8.4 kann es jedoch nicht kompiliert werden.

Im Gegensatz zu Xdebug unterstützt PCOV nur die sogenannte Line Coverage. Diese Softwaremetrik misst, ob jede ausführbare Zeile ausgeführt wurde. Zusätzlich unterstützt Xdebug seit der Version 2.3 auch Path Coverage. Diese misst, ob jeder mögliche Ausführungspfad in einer Funktion oder Methode während der Ausführung der Testsuite ausgeführt wurde.

Für den täglichen Gebrauch reicht mir die Line Coverage völlig aus. In den letzten Jahren habe ich dafür PCOV verwendet, weil es deutlich schneller als Xdebug war, vor allem im Vergleich zu Xdebug 2.

Ich verwende Path Coverage nicht regelmäßig, wenn ich meine Tests während der Programmierung von Hand ausführe, und auch nicht in meinen CI-Pipelines. Ich verwende sie nur, wenn ich mir die Ausführungspfade einer konkreten Methode im Detail ansehen möchte. Daher habe ich in den letzten Jahren Xdebug immer nur dann geladen, wenn ich mir die Path Coverage ansehen wollte.

Zurück zur Frage ...

Da ich mit PCOV zufrieden war, habe ich dem Ende 2020 veröffentlichten Xdebug 3 nicht wirklich eine Chance im täglichen Einsatz gegeben. Dies habe ich nun nachgeholt, angeregt durch die eingangs erwähnte Frage, die mir in letzter Zeit häufiger gestellt wurde.

Sebastian, mit welcher PHP-Erweiterung erzeugst du eigentlich Code Coverage?

Jetzt kann ich diese Frage beantworten!

Nach fünf Jahren PCOV kehre ich gerade zu Xdebug zurück. Dafür gibt es zwei Gründe: die mit Xdebug 3 eingeführte Konfigurationseinstellung xdebug.mode und die derzeit ungewisse Zukunft von PCOV.

Xdebug 3

Wie Xdebug arbeitet, kann seit Version 3 über die Konfigurationseinstellung xdebug.mode oder die Umgebungsvariable XDEBUG_MODE eingestellt werden.

Ich habe in meiner PHP-Konfigurationsdatei xdebug.mode=off gesetzt. Das bedeutet, dass Xdebug nur geladen wird, aber nichts tut. Dadurch gibt es keinen Overhead, nur weil Xdebug geladen ist.

Wenn ich einen Code Coverage-Report haben möchte, rufe ich PHPUnit mit XDEBUG_MODE=coverage ./tools/phpunit anstatt mit ./tools/phpunit auf.

Ich rate davon ab, die Code Coverage-Funktionalität von Xdebug pauschal in der PHP Konfigurationsdatei mit xdebug.mode=coverage zu aktivieren. Der folgende Benchmark macht hoffentlich deutlich, warum.

Benchmark

Wenn weder Xdebug noch PCOV geladen sind, werden die Tests in 14.031 Sekunden ausgeführt. Ist Xdebug geladen, aber mit xdebug.mode=off deaktiviert, gibt es keinen signifikanten Overhead (14.076 Sekunden).

Durch die Einstellung xdebug.mode=debug wird der Step-Debugger von Xdebug aktiviert. Mit dieser Konfiguration werden die Tests in 31,928 Sekunden ausgeführt.

Die Einstellung xdebug.mode=develop aktiviert die Entwicklungswerkzeuge von Xdebug. Mit dieser Konfiguration werden die Tests innerhalb von 37,593 Sekunden ausgeführt.

Durch die Einstellung xdebug.mode=coverage wird die Code-Coverage-Funktionalität von Xdebug aktiviert. Mit dieser Konfiguration werden die Tests in 53,028 Sekunden ausgeführt, also circa 3,8 Mal langsamer. Sammelt PHPUnit Line Coverage-Daten mit Xdebug, so dauert die Ausführung der Tests 53,488 Sekunden. Es spielt scheinbar keine Rolle, ob PHPUnit Xdebug Code Coverage-Daten sammeln lässt oder nicht.

Wenn PHPUnit Path Coverage-Daten mit Xdebug sammelt, dauert die Ausführung der Tests 146,811 Sekunden. Das ist circa 2,7 Mal langsamer, als wenn nur Line Coverage-Daten mit Xdebug gesammelt werden.

Ist die PCOV-Extension geladen, so werden die Tests in 18,844 Sekunden ausgeführt. Sammelt PHPUnit Line Coverage-Daten mit PCOV, so dauert die Ausführung der Tests 18,862 Sekunden. Es spielt scheinbar keine Rolle, ob PHPUnit PCOV Code Coverage-Daten sammeln lässt oder nicht.

Das Sammeln der Daten für einen Code Coverage-Report mit Line Coverage ist mit Xdebug (53,488 Sekunden) circa 2,8 Mal langsamer als mit PCOV (18,862 Sekunden). Allerdings wird Xdebug von Derick aktiv weiterentwickelt und liefert genauere Daten als PCOV.

Ich habe diesen Benchmark mit PHP 8.5.0, Xdebug 3.5.0, PCOV 1.0.12 und PHPUnit 12.5.2 auf einem Apple MacBook Air M3 (2024) mit macOS 26.1 durchgeführt. Dabei kam die Testsuite meines Raytracer-Projektes zum Einsatz.

Die Messungen wurden mit dem Werkzeug hyperfine durchgeführt. PHPUnit 12.5.2 wurde minimal so modifiziert, dass Code Coverage-Daten gesammelt, aber keine Code Coverage-Reports erzeugt werden. Für diesen Benchmark interessiert uns schließlich die für die Ausführung der Tests benötigte Zeit und nicht die für die Erstellung eines Code-Coverage-Reports benötigte Zeit.

Update vom 11.12.2025: Leider war die SVG-Grafikdatei mit dem Balkendiagramm nicht sinnvoll, da sie nur eine Bitmap-Grafik einbettete statt einer echten, skalierbaren Vektorgrafik. Diese Bitmap-Grafik war im "Dark Mode" der Website nicht gut lesbar. Ich habe dies zum Anlass genommen, den Benchmark mit aktuellen Versionen von PHP, PCOV und Xdebug erneut durchzuführen, um eine Grundlage für ein besser lesbares Balkendiagramm zu schaffen.

Seit über 35 Jahren entwickle ich Software, davon fast 30 Jahre mit PHP. Mehr als 25 Jahre widme ich mich außerdem schon der Entwicklung von PHPUnit. Das Wissen aus dieser Zeit fließt in meine Artikel ein, doch es ist nur die Spitze des Eisbergs.

Wenn du und dein Team von meiner Erfahrung durch Beratung und individuelles Coaching profitieren möchtet, freue ich mich auf deine Nachricht.