GitLab CI artifacts:reports: JUnit-Testergebnisse und Code-Coverage im Merge-Request-Widget
AI generated
CI/CD
.yml
GitLab · CI/CD · Testing
GitLab CI artifacts:reports: JUnit und Coverage im Merge-Request-Widget
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.

15 Min. Lesezeit JUnit Report Code Coverage PHPUnit Merge-Request-Widget

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.

11. FAQ: GitLab JUnit und Coverage Reports: Das Wichtigste auf einen Blick

1Was ist der Unterschied zwischen artifacts:reports:junit und coverage_report?
junit zeigt einzelne Testergebnisse mit Erfolg, Fehlschlag und Laufzeit im Merge-Request-Widget, waehrend coverage_report Code-Coverage-Daten einliest und daraus eine zeilengenaue, farbige Markierung im Diff-View sowie eine Gesamtquote erzeugt.
2Warum erscheint mein JUnit-Report nicht, obwohl die Konfiguration korrekt aussieht?
Meist fehlt artifacts:when: always. Ohne diese Einstellung wird das Artifact nur bei erfolgreichem Job-Exit-Code hochgeladen, genau dann also nicht, wenn Tests fehlschlagen und der Report am wertvollsten waere.
3Wie erzeugt PHPUnit ein JUnit-kompatibles XML?
Ueber den Kommandozeilenparameter --log-junit gefolgt vom Zieldateipfad, oder alternativ ueber ein logging-Element mit junit-Kindelement innerhalb der phpunit.xml Konfigurationsdatei.
4Welches Coverage-Format erwartet GitLab?
Cobertura-XML. PHPUnit erzeugt es ueber den Parameter --coverage-cobertura, was eine installierte Coverage-Extension wie Xdebug oder PCOV im Test-Container voraussetzt.
5Warum bleibt mein Coverage-Report leer?
Meist fehlt eine Coverage-Extension wie Xdebug oder PCOV im verwendeten Docker-Image, da schlanke PHP-Images sie oft bewusst weglassen, um die normale Testausfuehrung zu beschleunigen. Zusaetzlich muss bei Xdebug 3 die Variable XDEBUG_MODE auf coverage gesetzt sein.
6Kann ich sowohl JUnit als auch Coverage in einem einzigen PHPUnit-Lauf erzeugen?
Ja, --log-junit und --coverage-cobertura lassen sich in einem Kommando kombinieren, sodass ein einziger Testlauf beide Report-Dateien gleichzeitig erzeugt, ohne die Testsuite zweimal ausfuehren zu muessen.
7Wie zeige ich die Gesamt-Coverage als Badge in der Projektuebersicht an?
Ueber die Projekteinstellung Test coverage parsing mit einem regulaeren Ausdruck, der die Prozentzahl aus der Job-Log-Textausgabe extrahiert, zum Beispiel Lines:\s*\d+\.\d+\% bei Verwendung von PHPUnits --coverage-text.
8Kann ich einen Merge blockieren, wenn die Coverage sinkt?
Ja, ueber eine Kombination aus einem eigenen Job, der die Cobertura-Datei auswertet und bei Unterschreiten eines Schwellenwerts fehlschlaegt, und den Merge-Request-Approval-Regeln des Projekts, die auf diesem Job-Status aufbauen koennen.
9Funktioniert dieselbe Konfiguration auch fuer JavaScript- oder Go-Projekte?
Ja, sowohl JUnit- als auch Cobertura-Format sind sprachunabhaengige Industriestandards. Fuer JavaScript erzeugt zum Beispiel jest-junit das JUnit-XML, fuer Go uebernehmen go-junit-report und gocover-cobertura dieselbe Aufgabe.
10Zeigt GitLab, welche Tests durch meinen Merge Request neu fehlschlagen?
Ja, GitLab vergleicht das JUnit-Ergebnis des Merge Requests automatisch mit dem des Ziel-Branches und markiert explizit, welche Tests durch die aktuelle Aenderung neu fehlschlagen, getrennt von bereits vorher bestehenden Fehlschlaegen.