PHPUnit-Ausgabe fuer GitLab richtig konfigurieren
Ein gruener Test-Job im Pipeline-Ueberblick sagt nur, dass Tests gelaufen sind, nicht wie viele, welche davon fehlgeschlagen sind oder wie gut der Code tatsaechlich abgedeckt ist. Mit artifacts:reports:junit und artifacts:reports:coverage_report holt GitLab genau diese Details direkt aus der Testausgabe heraus und zeigt sie strukturiert im Merge-Request-Widget an, bis hin zu farbig markierten Zeilen im Diff. Dieser Artikel zeigt die konkrete Konfiguration fuer PHPUnit, von der richtigen Ausgabeformat-Einstellung bis zur Interpretation der Ergebnisse.
Inhaltsverzeichnis
- 1. Warum reine Exit-Codes nicht ausreichen
- 2. artifacts:reports:junit: Das JUnit-XML-Format verstehen
- 3. PHPUnit fuer die JUnit-Ausgabe konfigurieren
- 4. artifacts:reports:coverage_report fuer Code-Coverage einrichten
- 5. Die Gesamt-Coverage-Prozentzahl im Pipeline-Badge anzeigen
- 6. Wie die Ergebnisse im Merge-Request-Widget dargestellt werden
- 7. Coverage-Schwellenwerte als Qualitaetsgate erzwingen
- 8. Ausblick: JUnit und Coverage fuer andere Sprachen und Frameworks
- 9. Best Practices und Formatvergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum reine Exit-Codes nicht ausreichen
Ein Test-Job in GitLab CI gilt als erfolgreich, sobald das ausgefuehrte Kommando mit Exit-Code 0 zurueckkehrt, und als fehlgeschlagen bei jedem anderen Code. Diese binaere Information reicht fuer eine grobe Go- oder No-Go-Entscheidung, verrraet aber nichts darueber, welche einzelnen Tests fehlgeschlagen sind, wie lange sie gedauert haben oder ob eine neue Aenderung die Testabdeckung verschlechtert hat. Wer diese Details sehen will, muss bislang die vollstaendigen Job-Logs durchsuchen, was bei grossen Testsuiten mit tausenden Einzeltests schnell unpraktikabel wird.
GitLabs Report-Artifacts loesen dieses Problem, indem sie strukturierte Ausgabedateien aus dem Test-Lauf gezielt einsammeln und in der GitLab-Oberflaeche aufbereitet darstellen. artifacts:reports:junit verarbeitet das branchenuebliche JUnit-XML-Format und zeigt einzelne Testergebnisse direkt im Merge-Request-Widget, waehrend artifacts:reports:coverage_report Coverage-Daten im Cobertura-Format einliest und daraus sowohl eine Gesamtquote als auch farbig markierte Zeilen im Diff-View erzeugt. Beide Mechanismen setzen voraus, dass die Testausgabe im richtigen Format vorliegt, was bei PHPUnit eine bewusste Konfiguration erfordert.
2. artifacts:reports:junit: Das JUnit-XML-Format verstehen
Das JUnit-XML-Format stammt urspruenglich aus dem Java-Oekosystem, wird inzwischen aber von praktisch jedem gaengigen Testframework unterstuetzt, darunter auch PHPUnit fuer PHP und Jest oder Mocha fuer Node.js. Es beschreibt jede einzelne Testklasse als testsuite-Element mit darin verschachtelten testcase-Elementen, jeweils mit Name, Laufzeit und im Fehlerfall einem failure- oder error-Kindelement inklusive Stacktrace. GitLab parst diese Struktur und stellt sie im Merge-Request-Widget als aufklappbare Liste dar, in der fehlgeschlagene Tests farblich hervorgehoben ganz oben erscheinen.
Besonders wertvoll ist die automatische Erkennung neuer im Vergleich zu bereits vorher fehlgeschlagenen Tests: GitLab vergleicht das JUnit-Ergebnis des Merge Requests mit dem des Ziel-Branches und markiert explizit, welche Tests durch die aktuelle Aenderung neu kaputtgegangen sind, im Gegensatz zu bereits laenger bestehenden, bekannten Fehlschlaegen. Das erspart Reviewern die muehsame manuelle Recherche, ob ein roter Test tatsaechlich durch den aktuellen Merge Request verursacht wurde oder ob er schon vorher fehlgeschlagen ist.
# .gitlab-ci.yml
phpunit:
stage: test
script:
- vendor/bin/phpunit --log-junit report.xml
artifacts:
when: always
reports:
junit: report.xml
3. PHPUnit fuer die JUnit-Ausgabe konfigurieren
PHPUnit erzeugt das JUnit-XML-Format ueber den Kommandozeilenparameter --log-junit gefolgt vom Zieldateipfad, was fuer eine einfache Integration voellig ausreicht. Fuer Projekte, die ihre PHPUnit-Konfiguration ueber eine phpunit.xml Datei statt ueber Kommandozeilenparameter steuern, laesst sich derselbe Effekt ueber das logging-Element innerhalb dieser Datei erreichen, was den Vorteil hat, dass die Konfiguration versioniert im Repository liegt statt implizit im CI-Skript verstreut zu sein.
Wichtig ist die Einstellung artifacts:when: always, denn ohne sie wird das Artifact nur bei erfolgreichem Job-Exit-Code hochgeladen. Genau dann, wenn Tests fehlschlagen und der Exit-Code ungleich null ist, waere das JUnit-Report ohne diese Einstellung nicht verfuegbar, obwohl gerade die Information ueber die fehlgeschlagenen Tests in diesem Moment am wertvollsten ist. Dieser Stolperstein fuehrt in der Praxis haeufig dazu, dass das Merge-Request-Widget bei fehlgeschlagenen Tests leer bleibt, obwohl die JUnit-Konfiguration grundsaetzlich korrekt aussieht.
<!-- phpunit.xml -->
<phpunit>
<logging>
<junit outputFile="report.xml"/>
</logging>
</phpunit>
4. artifacts:reports:coverage_report fuer Code-Coverage einrichten
Waehrend junit einzelne Testergebnisse abbildet, kuemmert sich coverage_report um die flaechendeckende Codeabdeckung. GitLab erwartet dafuer das Cobertura-XML-Format, das ebenfalls ein Industriestandard ist und von PHPUnit ueber die Coverage-Erweiterung Xdebug oder PCOV erzeugt werden kann. Die Konfiguration erfolgt im artifacts:reports Block mit dem Schluessel coverage_report, unter dem sowohl der Pfad zur XML-Datei als auch der path_type Cobertura anzugeben sind, damit GitLab das Format korrekt interpretiert.
Fuer PHPUnit selbst ist zusaetzlich in der phpunit.xml ein coverage-Element mit einem clover- oder cobertura-Report noetig, wobei Cobertura direkt das von GitLab erwartete Format liefert und damit den einfacheren Weg darstellt. Ohne eine installierte Coverage-Extension wie Xdebug oder PCOV im Test-Container bleibt der Coverage-Report leer oder der Job schlaegt mit einer Fehlermeldung fehl, was ein haeufiger erster Stolperstein bei der Einrichtung ist, insbesondere in schlanken Docker-Images, die Xdebug bewusst nicht mitliefern, um die Testausfuehrung schneller zu machen.
# .gitlab-ci.yml
phpunit:
stage: test
variables:
XDEBUG_MODE: coverage
script:
- vendor/bin/phpunit --log-junit report.xml --coverage-cobertura coverage.xml
artifacts:
when: always
reports:
junit: report.xml
coverage_report:
coverage_format: cobertura
path: coverage.xml
5. Die Gesamt-Coverage-Prozentzahl im Pipeline-Badge anzeigen
Zusaetzlich zum strukturierten coverage_report bietet GitLab die Moeglichkeit, ueber die Projekteinstellung Test coverage parsing einen regulaeren Ausdruck zu hinterlegen, der die textuelle Gesamt-Coverage-Prozentzahl direkt aus den Job-Logs extrahiert und als Badge auf der Pipeline-Uebersichtsseite sowie im Repository-Badge anzeigt. Fuer PHPUnit eignet sich dafuer ein Ausdruck wie Lines:\s*\d+\.\d+\%, der auf die von PHPUnits Textausgabe erzeugte Zeile passt, sofern PHPUnit mit dem zusaetzlichen Parameter --coverage-text ausgefuehrt wird.
Diese textbasierte Extraktion ist unabhaengig vom strukturierten coverage_report und laesst sich sogar ohne diesen nutzen, deckt aber nur die aggregierte Gesamtquote ab, nicht die zeilengenaue Darstellung im Diff. In der Praxis lohnt sich meist beides gleichzeitig: Der Regex fuer das schnell sichtbare Badge in der Projektuebersicht, der strukturierte coverage_report fuer die detaillierte, zeilenbasierte Darstellung direkt im Merge-Request-Diff, wo Reviewer sofort sehen, welche neu hinzugefuegten Zeilen tatsaechlich von einem Test abgedeckt sind.
# .gitlab-ci.yml
phpunit:
script:
- vendor/bin/phpunit --coverage-text --colors=never
coverage: '/^\s*Lines:\s*\d+\.\d+\%/'
6. Wie die Ergebnisse im Merge-Request-Widget dargestellt werden
Im Merge-Request-Widget erscheint bei vorhandenem JUnit-Report eine kompakte Zusammenfassung mit Gesamtzahl der Tests, Anzahl der Fehlschlaege und deren Laufzeit, aufklappbar zu den einzelnen fehlgeschlagenen Testfaellen inklusive Stacktrace, ohne dass Reviewer die vollstaendigen Job-Logs oeffnen muessen. Bei vorhandenem coverage_report ergaenzt GitLab den Diff-View um farbige Randmarkierungen: gruen fuer durch Tests abgedeckte, neu hinzugefuegte Zeilen, rot fuer nicht abgedeckte Zeilen, was gezielt darauf hinweist, wo eine Aenderung ohne begleitenden Test eingefuehrt wurde.
Diese visuelle Rueckmeldung direkt im Code-Review-Kontext ist deutlich wirksamer als eine reine Gesamt-Coverage-Prozentzahl, weil sie punktgenau auf die tatsaechlich geaenderten Zeilen zeigt, statt eine abstrakte, projektweite Kennzahl zu liefern, die neue, ungetestete Zeilen in einer grossen, gut getesteten Codebasis leicht verstecken kann. Ein Reviewer sieht damit auf einen Blick, ob genau die neu eingefuehrte Geschaeftslogik tatsaechlich getestet wurde, unabhaengig davon, wie hoch die Gesamt-Coverage des Projekts insgesamt ausfaellt.
7. Coverage-Schwellenwerte als Qualitaetsgate erzwingen
Ueber die Projekteinstellung Merge request approvals lassen sich Regeln definieren, die einen Merge blockieren, sofern die Coverage unter einen definierten Schwellenwert faellt oder gegenueber dem Ziel-Branch sinkt. Fuer PHPUnit-Projekte bewaehrt sich dafuer haeufig ein separater Job, der die Cobertura-Datei parst und bei Unterschreiten eines Mindestwerts explizit mit exit 1 fehlschlaegt, was sich sauber in bestehende rules-Bedingungen integrieren laesst, um zum Beispiel nur bei Merge Requests gegen main zu greifen.
Wichtig ist dabei ein realistisches Augenmass: Ein zu hoch angesetzter globaler Schwellenwert fuehrt schnell dazu, dass Entwickler Tests nur noch schreiben, um die Zahl zu erreichen, statt um tatsaechlich sinnvolle Faelle abzudecken. Ein pragmatischerer Ansatz ist, statt eines absoluten Schwellenwerts vor allem darauf zu bestehen, dass die Coverage durch einen Merge Request nicht sinkt, kombiniert mit gezielten, hoeheren Anforderungen fuer neu geschriebenen, sicherheitsrelevanten Code.
8. Ausblick: JUnit und Coverage fuer andere Sprachen und Frameworks
Das JUnit- und Cobertura-Format sind bewusst sprachunabhaengig konzipiert, weshalb sich dieselbe GitLab-Konfiguration ohne Aenderung fuer andere Testframeworks eignet, sofern diese ein kompatibles Ausgabeformat unterstuetzen. Jest fuer JavaScript erzeugt JUnit-XML ueber den Reporter jest-junit, Node-Coverage laesst sich mit dem Paket nyc oder ueber Jests eingebautes Coverage-Feature ebenfalls im Cobertura-Format ausgeben, waehrend Go-Tests ueber go-junit-report und gocover-cobertura denselben Weg gehen.
Fuer Teams mit gemischten Technologie-Stacks bedeutet das, dass die einmal erlernte GitLab-Report-Konfiguration ueber Projektgrenzen hinweg wiederverwendbar ist, unabhaengig von der jeweiligen Programmiersprache. Das reduziert den Einarbeitungsaufwand fuer neue Projekte erheblich, da lediglich das jeweilige sprachspezifische Werkzeug zur Erzeugung der JUnit- oder Cobertura-Datei ausgetauscht werden muss, waehrend die GitLab-seitige artifacts:reports-Konfiguration strukturell identisch bleibt.
9. Best Practices und Formatvergleich
Ein solider Umgang mit Reports beginnt mit artifacts:when: always, damit auch fehlgeschlagene Test-Jobs ihre Reports zuverlaessig hochladen, gefolgt von einer bewussten Entscheidung, ob zusaetzlich zum strukturierten coverage_report auch ein Text-Regex fuer das schnelle Badge gepflegt werden soll. Fuer neue Projekte lohnt es sich, beide Report-Typen von Anfang an einzurichten, statt sie spaeter nachzuruesten, wenn die Testsuite bereits deutlich gewachsen ist und Nachbesserungen aufwendiger werden.
Die folgende Tabelle stellt die relevanten Report-Formate und ihre jeweilige Rolle im GitLab-Kontext gegenueber, um bei der Einrichtung fuer ein konkretes Projekt schnell die richtige Kombination auszuwaehlen, ohne jedes Format erst einzeln ausprobieren zu muessen.
| Report-Typ | GitLab-Schluessel | Erwartetes Format | Darstellung |
|---|---|---|---|
| Testergebnisse | artifacts:reports:junit | JUnit-XML | Liste im Merge-Request-Widget, neu vs. bestehend |
| Code-Coverage | artifacts:reports:coverage_report | Cobertura-XML | Farbige Zeilenmarkierung im Diff-View |
| Gesamt-Coverage-Badge | coverage (Regex) | Textausgabe im Job-Log | Prozentzahl auf Pipeline- und Projektseite |
| Qualitaetsgate | Eigener Job plus Approval-Regel | Ausgewerteter Cobertura-Wert | Merge-Blockade bei Unterschreitung |
Mironsoft
CI/CD-Pipelines, Zero-Downtime-Deployments und Release-Automatisierung
Deployments, die ohne Ausfallzeit und ohne Nervenkitzel laufen?
Wir prüfen bestehende GitLab-Pipelines auf fragile Deployment-Schritte und fehlende Absicherung und bauen daraus einen Release-Prozess mit Zero-Downtime-Deployments, automatisierten Checks und einem Rollback, dem ihr im Ernstfall vertrauen könnt.
Pipeline-Review
Bestehende .gitlab-ci.yml auf Fragilität, fehlende Stages und Sicherheitslücken prüfen.
Zero-Downtime-Deployment
Symlink-Releases, Health-Checks und Rollback-Strategien für Magento-Shops aufbauen.
CI/CD-Automatisierung
Tests, Security-Scans und Deployments zu einer zuverlässigen Pipeline verbinden.
10. Zusammenfassung
GitLab JUnit und Coverage Reports: Das Wichtigste auf einen Blick
Zwei Report-Typen
junit fuer einzelne Testergebnisse, coverage_report fuer zeilengenaue Coverage im Diff.
Always hochladen
artifacts:when: always ist Pflicht, sonst fehlt der Report genau bei fehlgeschlagenen Tests.
PHPUnit-Flags
--log-junit fuer JUnit, --coverage-cobertura fuer Coverage, beide kombinierbar in einem Lauf.
Sprachunabhaengig
JUnit und Cobertura sind Industriestandards, dieselbe GitLab-Konfiguration passt fuer viele Testframeworks.