Warum eine globale Prozentzahl truegt
Eine einzelne projektweite Coverage-Zahl klingt beruhigend, kann aber genau die Kernmodule verstecken, die am dringendsten Tests braucht. Wer Coverage-Schwellenwerte pro Modul definiert und in der CI-Pipeline durchsetzt, schuetzt gezielt die Bereiche mit dem hoechsten Risiko, statt sich von einem guenstigen Durchschnitt taeuschen zu lassen.
Inhaltsverzeichnis
- 1. Das Problem mit einer einzigen globalen Coverage-Zahl
- 2. Eine konkrete Beispielrechnung zum Verstecken-Effekt
- 3. Coverage pro Verzeichnis mit PHPUnit erzeugen
- 4. Wie man sinnvolle Schwellenwerte pro Modul festlegt
- 5. Durchsetzung in der CI-Pipeline
- 6. Grenzen der modul-basierten Betrachtung
- 7. Wie Modulgrenzen fuer die Coverage-Messung definiert werden
- 8. Transparente Reports fuer das Team schaffen
- 9. Line-Coverage versus Branch-Coverage pro Modul
- 10. Zusammenfassung
- 11. FAQ
1. Das Problem mit einer einzigen globalen Coverage-Zahl
Eine projektweite Coverage-Zahl von siebzig Prozent klingt nach einem soliden Testfundament. Sie sagt aber nichts darueber aus, wie diese siebzig Prozent zustande kommen. In einem typischen Projekt verteilt sich Testabdeckung selten gleichmaessig: Utility-Klassen mit trivialer Logik und viele generierte oder einfache Getter-Setter-Strukturen erreichen fast mühelos hundert Prozent, waehrend die eigentliche Geschaeftslogik, etwa Preisberechnung, Rabattregeln oder Checkout-Validierung, bei zwanzig Prozent verharrt, ohne dass die globale Zahl das sichtbar macht.
Dieses Verstecken-Effekt ist besonders gefaehrlich, weil Teams sich auf die globale Zahl als Erfolgsmetrik verlassen und in dem Glauben handeln, ausreichend abgesichert zu sein. Ein einzelner Bug in einem schlecht getesteten Kernmodul kann dann trotz insgesamt guter Coverage-Zahl in Produktion auftauchen, weil die Statistik den Mangel an genau der Stelle verschleiert hat, an der er am teuersten gewesen waere.
2. Eine konkrete Beispielrechnung zum Verstecken-Effekt
Nehmen wir ein Projekt mit drei Modulen: einem Utility-Modul mit 2000 Zeilen und 95 Prozent Abdeckung, einem Reporting-Modul mit 1500 Zeilen und 80 Prozent Abdeckung, sowie einem Checkout-Modul mit 500 Zeilen und nur 20 Prozent Abdeckung. Die projektweite, zeilengewichtete Coverage betraegt dann etwa 75 Prozent, ein auf den ersten Blick guter Wert. Das kleine, aber geschaeftskritische Checkout-Modul mit seiner duennen Abdeckung faellt dabei rechnerisch kaum ins Gewicht, obwohl genau hier ein Fehler den groessten finanziellen Schaden anrichten wuerde.
Wird stattdessen fuer jedes Modul ein eigener Schwellenwert definiert, etwa neunzig Prozent fuer Utility, siebzig Prozent fuer Reporting und funfundsiebzig Prozent fuer Checkout, faellt der Checkout-Wert von zwanzig Prozent sofort als klarer Verstoss auf und blockiert den Build, statt in einem guenstigen Durchschnitt zu verschwinden. Genau dieser Mechanismus macht modul-basierte Schwellenwerte einer einzelnen globalen Zahl ueberlegen.
3. Coverage pro Verzeichnis mit PHPUnit erzeugen
PHPUnit selbst erzeugt Coverage-Reports meist projektweit, etwa als Clover-XML oder HTML-Report. Um Werte pro Modul zu erhalten, ist es sinnvoll, PHPUnit pro Modulverzeichnis separat mit einer eigenen phpunit.xml oder einem eigenen testsuite-Eintrag laufen zu lassen, oder den erzeugten Clover-Report anschliessend nach Verzeichnis auszuwerten. Beide Ansaetze liefern granularere Daten als ein einziger Gesamtlauf, wobei die separate Ausfuehrung pro Modul zusaetzlich den Vorteil hat, dass Module unabhaengig voneinander parallel getestet werden koennen.
Ein Clover-XML-Report enthaelt fuer jede Datei die Anzahl abgedeckter und gesamter Statements, sodass sich daraus mit einem einfachen Skript eine Aggregation pro Verzeichnis berechnen laesst. Dieses Skript kann dann pro Modul pruefen, ob der jeweils definierte Schwellenwert erreicht wurde, und bei einer Unterschreitung den Build mit einem nicht-null Exit-Code beenden.
<?php
// tools/coverage-per-module.php
declare(strict_types=1);
$thresholds = [
'app/code/Vendor/Checkout' => 75.0,
'app/code/Vendor/Pricing' => 85.0,
'app/code/Vendor/Reporting' => 60.0,
];
$clover = simplexml_load_file(__DIR__ . '/../var/coverage/clover.xml');
$exitCode = 0;
foreach ($thresholds as $modulePath => $minCoverage) {
[$covered, $total] = [0, 0];
foreach ($clover->xpath("//file[contains(@name, '{$modulePath}')]") as $file) {
$metrics = $file->metrics;
$covered += (int) $metrics['coveredstatements'];
$total += (int) $metrics['statements'];
}
$actual = $total > 0 ? ($covered / $total) * 100 : 0.0;
if ($actual < $minCoverage) {
fwrite(STDERR, sprintf(
"FEHLER: %s hat nur %.1f%% Coverage (mindestens %.1f%% erforderlich)\n",
$modulePath, $actual, $minCoverage
));
$exitCode = 1;
} else {
fwrite(STDOUT, sprintf("OK: %s hat %.1f%% Coverage\n", $modulePath, $actual));
}
}
exit($exitCode);
4. Wie man sinnvolle Schwellenwerte pro Modul festlegt
Ein pauschaler Schwellenwert fuer alle Module, etwa 'ueberall achtzig Prozent', wiederholt im Kleinen den gleichen Fehler wie die globale Zahl, nur auf Modulebene verschoben. Sinnvoller ist es, den Schwellenwert an das Geschaeftsrisiko des Moduls zu koppeln: Zahlungsabwicklung, Preisberechnung und Bestandsverwaltung rechtfertigen hohe Werte von achtzig bis neunzig Prozent, waehrend ein internes Admin-UI-Modul mit ueberwiegend deklarativem Layout-Code auch mit vierzig oder fuenfzig Prozent ausreichend abgesichert sein kann.
Bei neuen Modulen empfiehlt sich, den Schwellenwert bewusst hoch anzusetzen, etwa achtzig Prozent von Anfang an, da hier keine Legacy-Last existiert, die eine niedrigere Zahl rechtfertigen wuerde. Bei bestehenden Modulen mit historisch gewachsener, duenner Abdeckung ist ein niedrigerer Startwert realistisch, der dann ueber ein Ratchet-Prinzip schrittweise angehoben wird, sobald neue Tests hinzukommen.
5. Durchsetzung in der CI-Pipeline
Damit modul-basierte Schwellenwerte wirken, muessen sie als eigener, blockierender Schritt in der CI-Pipeline verankert sein, nicht nur als informativer Report, der nach dem Merge betrachtet wird. Ein typisches Setup fuehrt PHPUnit mit aktivierter Coverage-Erfassung aus, erzeugt einen Clover-Report, und laesst anschliessend ein Skript wie im vorherigen Abschnitt ueber die definierten Schwellenwerte laufen. Schlaegt eine Modul-Pruefung fehl, bricht die Pipeline mit einem klaren, modul-spezifischen Fehlertext ab, statt nur eine generische 'Coverage zu niedrig'-Meldung auszugeben.
Wichtig ist, diese Pruefung fuer jeden Merge-Request oder Pull-Request laufen zu lassen, nicht nur periodisch auf dem Hauptzweig. Nur so wird verhindert, dass ein Entwickler versehentlich ein Modul unter den Schwellenwert druecken kann, ohne dass es sofort auffaellt. Ein spaeter periodischer Check faengt das Problem erst, nachdem der schlecht getestete Code bereits im Hauptzweig gelandet ist.
# .gitlab-ci.yml (Ausschnitt)
coverage-per-module:
stage: test
script:
- vendor/bin/phpunit --coverage-clover var/coverage/clover.xml
- php tools/coverage-per-module.php
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
6. Grenzen der modul-basierten Betrachtung
Modul-basierte Schwellenwerte loesen nicht automatisch das Problem oberflaechlicher Tests, die zwar Zeilen ausfuehren, aber keine sinnvollen Assertions treffen. Ein Modul kann formal neunzig Prozent Coverage erreichen und trotzdem grobe Fehler enthalten, wenn Tests lediglich Methoden aufrufen, ohne die Rueckgabewerte oder Seiteneffekte zu pruefen. Coverage misst ausgefuehrten Code, nicht verifiziertes Verhalten, und dieser Unterschied bleibt auch bei granularer Betrachtung pro Modul bestehen.
Ergaenzend lohnt sich deshalb der Einsatz von Mutation Testing fuer die kritischsten Module, das gezielt kleine Aenderungen am Produktionscode vornimmt und prueft, ob mindestens ein Test dadurch fehlschlaegt. Ein Modul mit hoher Zeilen-Coverage, aber niedriger Mutation Score, signalisiert, dass die vorhandenen Tests zwar Code ausfuehren, aber Fehler nicht zuverlaessig erkennen wuerden.
7. Wie Modulgrenzen fuer die Coverage-Messung definiert werden
Die Aussagekraft modul-basierter Schwellenwerte haengt direkt davon ab, wie sinnvoll die Modulgrenzen selbst gezogen sind. In einem Magento-Projekt bieten sich die einzelnen app/code/Vendor/Modulname-Verzeichnisse als natuerliche Grenze an, da sie ohnehin die Deployment- und Verantwortlichkeitseinheit bilden. Ein zu grobes Modul, das mehrere fachlich unabhaengige Bereiche buendelt, verwaesert die Aussagekraft des Schwellenwerts auf die gleiche Weise wie eine projektweite Zahl, nur auf kleinerer Flaeche.
Bei sehr grossen Modulen kann es sinnvoll sein, zusaetzliche Unterebenen zu definieren, etwa getrennte Schwellenwerte fuer Model-, Service- und Controller-Schichten innerhalb eines Moduls, wenn diese Schichten deutlich unterschiedliche Risikoprofile haben. Diese Feinsteuerung sollte aber bewusst sparsam eingesetzt werden, da zu viele einzelne Schwellenwerte die Konfiguration unuebersichtlich machen und die Pflegekosten steigen.
8. Transparente Reports fuer das Team schaffen
Neben der reinen Durchsetzung in der Pipeline lohnt sich ein regelmaessig aktualisiertes Dashboard, das die Coverage-Werte aller Module nebeneinander zeigt, idealerweise mit einer farblichen Kennzeichnung, welche Module ueber, nahe an, oder unter ihrem Schwellenwert liegen. Ein solches Dashboard macht sichtbar, welche Module tatsaechlich Aufmerksamkeit brauchen, und verhindert, dass die Diskussion ueber Testabdeckung auf eine einzelne, wenig aussagekraeftige Zahl reduziert wird.
Ergaenzend hilft ein kurzer Kommentar im Merge-Request, der automatisiert die Coverage-Aenderung pro betroffenem Modul auflistet, etwa 'Checkout-Modul: 42% auf 47%, Pricing-Modul: unveraendert bei 88%'. Diese unmittelbare, kontextbezogene Rueckmeldung wirkt staerker auf das Verhalten von Entwicklern als ein monatlicher Gesamtbericht, der oft erst mit deutlicher Verzoegerung wahrgenommen wird.
9. Line-Coverage versus Branch-Coverage pro Modul
Selbst ein modul-basierter Schwellenwert kann taeuschen, wenn er ausschliesslich auf Zeilen-Coverage basiert. Eine einzelne Zeile mit einer komplexen bedingten Bedingung, etwa if ($status === 'vip' && $amount > 100 || $isPromoActive), gilt bereits als abgedeckt, sobald sie einmal in irgendeiner Kombination durchlaufen wurde, auch wenn drei von vier moeglichen Bedingungspfaden nie getestet wurden. Fuer risikoreiche Module wie Checkout oder Preisberechnung ist Branch-Coverage daher eine deutlich aussagekraeftigere Messgroesse als reine Zeilen-Coverage.
PHPUnit unterstuetzt ueber die zugrunde liegende Xdebug- oder PCOV-Treiber-Integration auch Pfad- und Branch-Metriken, die sich in der phpunit.xml gezielt fuer die kritischsten Module aktivieren lassen, waehrend weniger riskante Module weiterhin nur mit einfacher Zeilen-Coverage geprueft werden, um die Ausfuehrungszeit der Coverage-Erfassung nicht unnoetig fuer das gesamte Projekt zu erhoehen.
<!-- phpunit.xml, Ausschnitt fuer ein Modul mit Branch-Coverage-Pflicht -->
<coverage>
<report>
<html outputDirectory="var/coverage/html"/>
<clover outputFile="var/coverage/clover.xml"/>
</report>
<include>
<directory suffix=".php">app/code/Vendor/Checkout/Model</directory>
</include>
</coverage>
<!-- Ausfuehrung mit Pfad-Coverage fuer das Checkout-Modul -->
<!-- vendor/bin/phpunit --path-coverage --coverage-clover var/coverage/checkout-clover.xml -->
| Modul-Typ | Empfohlener Schwellenwert | Begruendung |
|---|---|---|
| Zahlung / Checkout | 80-90% | Hohes finanzielles Risiko bei Fehlern |
| Preisberechnung / Rabatte | 80-90% | Direkter Umsatzeinfluss, komplexe Regeln |
| Reporting / Export | 55-65% | Fehler sichtbar, aber selten geschaeftskritisch |
| Admin-UI / Layout | 35-50% | Ueberwiegend deklarativ, geringes Logikrisiko |
| Neue Module ohne Legacy-Last | 80%+ | Kein historischer Nachholbedarf, hoher Standard von Anfang an |
Mironsoft
Testautomatisierung, Magento-Qualitätssicherung und CI-Integration
Tests, die echte Fehler finden statt nur grün zu leuchten?
Wir prüfen bestehende PHPUnit-Suiten auf Implementierungsdetail-Tests, flaky Tests und fehlende Coverage an kritischen Stellen und bauen daraus eine Teststrategie, die bei jedem Magento-Update wirklich Sicherheit gibt.
Test-Audit
Bestehende Suiten auf Mocking-Antipatterns und blinde Flecken prüfen.
Teststrategie
Unit-, Integrations- und MFTF-Tests sinnvoll für Magento-Projekte kombinieren.
CI-Integration
Schnelle, zuverlässige Testläufe in GitLab CI oder GitHub Actions einrichten.
10. Zusammenfassung
Modul-Coverage: Das Wichtigste auf einen Blick
Problem
Eine globale Coverage-Zahl kann schlecht getestete Kernmodule statistisch verstecken.
Loesung
Eigene Schwellenwerte pro Modul, gekoppelt an das jeweilige Geschaeftsrisiko.
Umsetzung
Clover-Report nach Verzeichnis auswerten, Skript in der CI-Pipeline blockierend einsetzen.
Grenze
Coverage misst Ausfuehrung, nicht Verifikation, Mutation Testing ergaenzt die kritischsten Module.