Code Coverage in PHP erklärt: Line, Branch und Path Coverage, riskante Tests in PHPUnit, Mutation Testing als Gegenprobe und warum Vorgaben die Zahl entwerten
Mehr erfahren
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.
[
{"event": "login", "user": 42, "ts": "2026-10-14T09:12:00Z"},
{"event": "checkout", "user": 42, "ts": "2026-10-14T09:15:31Z"}
]
{"event": "login", "user": 42, "ts": "2026-10-14T09:12:00Z"}
{"event": "checkout", "user": 42, "ts": "2026-10-14T09:15:31Z"}
function readJsonl(string $path): Generator
{
$handle = fopen($path, 'r');
while (($line = fgets($handle)) !== false) {
$line = trim($line);
if ($line === '') {
continue;
}
yield json_decode($line, true, 512, JSON_THROW_ON_ERROR);
}
fclose($handle);
}
foreach (readJsonl('events.jsonl') as $event) {
echo $event['event'], PHP_EOL;
}
function appendJsonl(string $path, array $record): void
{
$line = json_encode(
$record,
JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
file_put_contents($path, $line . PHP_EOL, FILE_APPEND | LOCK_EX);
}
appendJsonl('events.jsonl', ['event' => 'login', 'user' => 42]);
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-jsonl build/coverage-jsonl
<coverage>
<report>
<jsonl outputDirectory="build/coverage-jsonl"/>
</report>
</coverage>
wc -l build/coverage-jsonl/coverage.jsonl
tail -n 5 build/coverage-jsonl/coverage.jsonl
grep '"src/Checkout/' build/coverage-jsonl/coverage.jsonl
# Dateien mit den meisten ungetesteten Zeilen zuerst
jq -c '{path, uncovered: (.uncoveredLines | length)}' coverage.jsonl \
| sort -t: -k3 -nr | head -n 10
# Nur komplett ungetestete Methoden ausgeben
jq -c '.functions[] | select(.status == "uncovered") | .name' coverage.jsonl
JSONL is one of those formats that looks almost too simple.
Code Coverage in PHP erklärt: Line, Branch und Path Coverage, riskante Tests in PHPUnit, Mutation Testing als Gegenprobe und warum Vorgaben die Zahl entwerten
Mehr erfahren
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.
Eine JSONL Datei (JSON Lines) enthält pro Zeile ein vollständiges JSON Objekt, getrennt durch Zeilenumbrüche. Es gibt kein umschließendes Array und keine Kommas zwischen den Einträgen. Dadurch lässt sich die Datei zeilenweise lesen und anhängen, ohne sie komplett in den Speicher zu laden. Die Endung ist .jsonl, der zweite Name ist NDJSON.
PHPUnit 13.4 bringt die Option --coverage-jsonl und das Element jsonl in der XML Konfiguration. Damit entsteht ein Coverage Report, der nur ungetesteten Code listet statt jeder ausführbaren Zeile. In das Zielverzeichnis kommen meta.json mit den Summen, coverage.jsonl mit einem Objekt pro Quelldatei und tests.jsonl mit einem Objekt pro Test.
JSONL passt, wenn Datensätze nacheinander entstehen oder verarbeitet werden: Logs, Event Streams, große Exporte, Trainingsdaten für KI Modelle und Berichte von Werkzeugen. Normales JSON bleibt besser, wenn ein Konsument das ganze Dokument auf einmal braucht, etwa bei Konfigurationsdateien oder einer API Antwort mit einem einzelnen Objekt.
Mit fopen und fgets Zeile für Zeile, jede Zeile durch json_decode. Ein Generator mit yield liefert die Objekte einzeln und hält den Speicherbedarf konstant. Leere Zeilen überspringst du mit trim, Fehler fängst du mit JSON_THROW_ON_ERROR ab. Eine Bibliothek ist dafür nicht nötig.
Ein Agent kann JSONL zeilenweise filtern und muss nicht den ganzen Bericht ins Context Window laden. Der PHPUnit Coverage Report als JSONL ist dadurch direkt eine Aufgabenliste: diese Datei, diese Methode, diese Zeilen fehlen. Nach dem nächsten Testlauf zeigt ein Diff, ob die Lücke kleiner geworden ist.
Jedes Objekt mit json_encode in einen String wandeln und mit PHP_EOL abschließen, dann per file_put_contents mit FILE_APPEND anhängen. Zeilenumbrüche innerhalb von Strings escaped json_encode automatisch. JSON_UNESCAPED_UNICODE sorgt dafür, dass Umlaute lesbar bleiben.
Technisch sind beide dasselbe: ein JSON Wert pro Zeile, getrennt durch Zeilenumbruch, UTF-8 kodiert. JSONL ist der Name von jsonlines.org, NDJSON steht für newline delimited JSON. Die Dateiendungen .jsonl und .ndjson sind austauschbar, die meisten Werkzeuge akzeptieren beide.
Alles, was zeilenorientiert arbeitet: wc -l zählt Einträge, tail zeigt die letzten, grep filtert nach Text. Für Abfragen in die Struktur ist jq das Werkzeug der Wahl, es liest JSONL nativ Objekt für Objekt. Die Option --slurp lädt dagegen alles in ein Array und verschenkt den Streaming Vorteil.
Nur dieser eine Eintrag ist betroffen. Alle anderen Zeilen bleiben gültig und lesbar, weil jede Zeile für sich steht. Beim Einlesen entscheidest du, ob du die fehlerhafte Zeile überspringst oder den Lauf abbrichst. Bei einer normalen JSON Datei macht ein einzelner Syntaxfehler dagegen das ganze Dokument unbrauchbar.
Nein, er ergänzt sie. Clover XML bleibt das Format für Codecov, Coveralls, SonarQube und IDEs, HTML bleibt der Bericht für Menschen. JSONL ist für Skripte, CI Checks und KI Agents gedacht, die gezielt die Lücken brauchen. Mehrere Formate lassen sich in einem Lauf gleichzeitig erzeugen.
Ja, wie für jeden Coverage Report. PHPUnit misst Coverage nicht selbst, sondern über eine PHP Extension. PCOV liefert Line Coverage und ist schnell, Xdebug im Coverage Modus liefert zusätzlich Branch Coverage, die dann auch in coverage.jsonl auftaucht. Ohne Treiber bleibt das Verzeichnis leer.
Die Bibliothek php-code-coverage sortiert Einträge nach Pfad und Test Identifier und nutzt auf allen Plattformen den Slash als Trenner. Dieselben Daten erzeugen so byteidentische Dateien. Das macht einen Diff zwischen zwei Läufen aussagekräftig und erlaubt Checks im CI, die neue Lücken erkennen.