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: Entwickler:innen 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 die 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.
Ich habe diesen Benchmark mit PHP 8.3.4, Xdebug 3.3.1, PCOV 1.0.11 und PHPUnit 11.0.7 auf einem Apple MacBook Air M3 (2024) durchgeführt und dabei die Testsuite meines raytracer -Projektes verwendet.
Wenn weder Xdebug noch PCOV geladen sind, werden die Tests in 13 Sekunden ausgeführt. Ist Xdebug geladen, aber mit xdebug.mode=off
deaktiviert, gibt es keinen messbaren Overhead.
Die Einstellung xdebug.mode=debug
aktiviert den Step-Debugger von Xdebug. Mit dieser Konfiguration werden die Tests in 33 Sekunden ausgeführt. Die Einstellung xdebug.mode=develop
aktiviert die Entwicklungswerkzeuge von Xdebug. Mit dieser Konfiguration werden die Tests innerhalb von 35 Sekunden ausgeführt.
Die Einstellung xdebug.mode=coverage
aktiviert die Code Coverage-Funktionalität von Xdebug. Mit dieser Konfiguration werden die Tests innerhalb von 50 Sekunden ausgeführt. Dabei spielt es scheinbar keine Rolle, ob PHPUnit Xdebug Code Coverage-Daten sammeln lässt oder nicht. Wenn PHPUnit das Sammeln von Path Coverage Daten anfordert, dauert die Ausführung der Tests 127 Sekunden.
Mit PCOV werden die Tests in 15 Sekunden ausgeführt. Auch hier spielt es scheinbar keine Rolle, ob PHPUnit Code Coverage-Daten sammeln lässt oder nicht.
Das Erzeugen eines Code Coverage-Reports mit Line Coverage ist mit PCOV (15 Sekunden) immer noch schneller als mit Xdebug 3 (50 Sekunden). Allerdings wird Xdebug von Derick aktiv weiter entwickelt und liefert, gerade in Edge Cases, häufig bessere Daten als PCOV.