Wie @group und --filter dabei helfen, in tausenden Tests gezielt die richtige Teilmenge zu finden
Eine Testsuite mit wenigen hundert Tests laeuft in Sekunden komplett durch, und niemand denkt ueber Teilmengen nach. Sobald ein Projekt aber auf mehrere tausend Tests waechst, wird die vollstaendige Ausfuehrung bei jeder kleinen Aenderung unpraktisch. PHPUnits @group-Attribute und der --filter-Parameter der Kommandozeile erlauben es, gezielt nur die Tests laufen zu lassen, die fuer die aktuelle Aenderung relevant sind, etwa nur schnelle Unit-Tests oder nur ein bestimmtes Modul, vorausgesetzt, ein Team einigt sich auf eine konsistente Taxonomie.
Inhaltsverzeichnis
- 1. Warum grosse Suiten eine gezielte Auswahl brauchen
- 2. Tests mit @group kategorisieren
- 3. Gezielte Auswahl mit --filter
- 4. Testsuiten in phpunit.xml als grobe Vorstufe
- 5. Eine sinnvolle Gruppen-Taxonomie fuer ein Team entwerfen
- 6. Gruppen und Filter in der CI-Pipeline gezielt einsetzen
- 7. Filter im lokalen Entwickler-Workflow
- 8. Haeufige Kombinationen als Composer-Scripts festhalten
- 9. Taxonomie pflegen, statt sie verwildern zu lassen
- 10. Zusammenfassung
- 11. FAQ
1. Warum grosse Suiten eine gezielte Auswahl brauchen
In einem kleinen Projekt mit wenigen hundert Tests ist die Frage, welche Tests man laufen laesst, praktisch nicht relevant: Alle Tests laufen in ein paar Sekunden komplett durch. In einem gewachsenen Projekt mit mehreren tausend Tests, darunter langsame Integrationstests, Datenbanktests und externe API-Aufrufe, kann ein vollstaendiger Lauf dagegen zehn Minuten oder laenger dauern. Bei jeder kleinen Aenderung die gesamte Suite laufen zu lassen, ist dann weder praktikabel noch noetig, weil die allermeisten Aenderungen nur einen kleinen Teil der Codebasis betreffen.
Genau hier setzen zwei sich ergaenzende Mechanismen von PHPUnit an: das @group-Attribut, mit dem Tests inhaltlich kategorisiert werden koennen, und der --filter-Parameter, mit dem Tests anhand ihres Namens oder ihrer Klasse selektiert werden. Beide Mechanismen loesen unterschiedliche Probleme: Gruppen eignen sich fuer wiederkehrende, thematische Teilmengen wie schnell versus langsam, Filter eignen sich fuer punktuelle, ad-hoc Selektionen waehrend der aktiven Entwicklung an einem einzelnen Feature.
2. Tests mit @group kategorisieren
Das @group-Attribut (beziehungsweise das Group-Attribut in neueren PHPUnit-Versionen) wird ueber einer Testklasse oder einer einzelnen Testmethode platziert und ordnet den Test einer oder mehreren benannten Gruppen zu. Eine Testmethode kann dabei mehreren Gruppen gleichzeitig angehoeren, etwa gleichzeitig als unit und als checkout markiert sein, was flexible Kombinationen bei der spaeteren Auswahl erlaubt.
In der Kommandozeile laesst sich mit dem Parameter --group gezielt nur eine bestimmte Gruppe ausfuehren, mit --exclude-group laesst sich umgekehrt eine Gruppe explizit ausschliessen. Beide Parameter lassen sich kombinieren und mehrfach angeben, sodass zum Beispiel alle Tests der Gruppe unit ausser denen der Gruppe legacy laufen koennen, was in der Praxis sehr genaue, wiederholbare Teilmengen erlaubt, ohne dass die Testdateien selbst durchsucht werden muessen.
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
#[Group('checkout')]
final class CheckoutTotalCalculatorTest extends TestCase
{
#[Group('unit')]
#[Group('fast')]
public function testCalculatesTotalWithoutDiscount(): void
{
$calculator = new CheckoutTotalCalculator();
self::assertSame(4999, $calculator->calculate(4999, null));
}
#[Group('integration')]
#[Group('slow')]
public function testCalculatesTotalWithLiveTaxServiceCall(): void
{
$calculator = new CheckoutTotalCalculator(new LiveTaxRateClient());
self::assertGreaterThan(0, $calculator->calculate(4999, 'DE'));
}
}
// CLI: nur schnelle Unit-Tests im checkout-Bereich ausfuehren
// vendor/bin/phpunit --group checkout --group unit
// CLI: alle Tests ausser den langsamen Integrationstests
// vendor/bin/phpunit --exclude-group slow
3. Gezielte Auswahl mit --filter
Waehrend @group eine dauerhafte, im Code hinterlegte Kategorisierung ist, wirkt der --filter-Parameter ad hoc auf der Kommandozeile, ohne dass der Testcode selbst etwas ueber Gruppen wissen muss. Der Filter akzeptiert einen regulaeren Ausdruck, der gegen den vollstaendig qualifizierten Namen jeder Testmethode geprueft wird, bestehend aus Klassenname und Methodenname. Das macht ihn ideal fuer die Situation, in der ein Entwickler gerade an einer einzelnen Klasse arbeitet und nur deren Tests sehen moechte, ohne extra eine Gruppe anzulegen.
Ein haeufiges Muster ist, den Filter mit dem Namen der aktuell bearbeiteten Klasse oder sogar nur einer einzelnen Methode zu kombinieren, waehrend eines TDD-Zyklus etwa, um innerhalb von Millisekunden Feedback zu einer einzigen Testmethode zu bekommen, statt jedes Mal die gesamte Klasse oder gar die gesamte Suite laufen zu lassen. Sobald die Arbeit an dieser Stelle abgeschlossen ist, laeuft die volle Suite oder zumindest die relevante Gruppe erneut, um sicherzustellen, dass nichts anderes kaputtgegangen ist.
// Nur die Testklasse CheckoutTotalCalculatorTest ausfuehren
vendor/bin/phpunit --filter CheckoutTotalCalculatorTest
// Nur eine einzelne Testmethode ausfuehren (regulaerer Ausdruck)
vendor/bin/phpunit --filter '::testCalculatesTotalWithoutDiscount$'
// Alle Tests, deren Name "Discount" enthaelt, quer durch die Suite
vendor/bin/phpunit --filter Discount
// Kombination aus Filter und Verzeichnis, um die Suche einzugrenzen
vendor/bin/phpunit --filter Discount tests/Unit/Checkout
4. Testsuiten in phpunit.xml als grobe Vorstufe
Neben Gruppen und Filtern bietet die phpunit.xml eine dritte Ebene der Organisation: benannte testsuite-Bloecke, die jeweils eigene Verzeichnisse referenzieren. Diese Ebene ist grober als Gruppen, weil sie an der Verzeichnisstruktur haengt statt an inhaltlicher Kategorisierung, eignet sich aber gut fuer die grundlegende Trennung zwischen Unit-Tests, Integrationstests und funktionalen Tests, die in vielen Projekten ohnehin schon in getrennten Verzeichnissen liegen.
In der Praxis ergaenzen sich beide Ebenen: Die testsuite-Struktur trennt grob nach Verzeichnis und technischer Testart, waehrend @group innerhalb dieser groben Struktur feinere, fachliche Kategorien wie checkout oder inventory ermoeglicht. Ein Entwickler kann so zunaechst per --testsuite auf die Unit-Tests eingrenzen und dann per --group zusaetzlich auf ein bestimmtes fachliches Modul filtern.
<!-- phpunit.xml -->
<phpunit bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="integration">
<directory>tests/Integration</directory>
</testsuite>
<testsuite name="functional">
<directory>tests/Functional</directory>
</testsuite>
</testsuites>
</phpunit>
<!-- CLI: nur die unit-Testsuite laufen lassen -->
<!-- vendor/bin/phpunit --testsuite unit -->
5. Eine sinnvolle Gruppen-Taxonomie fuer ein Team entwerfen
Die groesste Gefahr bei @group ist nicht die Technik, sondern eine wild wachsende, inkonsistente Taxonomie: Ein Entwickler nennt eine Gruppe langsam, ein anderer slow, ein dritter erfindet fuer denselben Zweck integration-slow. Ohne Absprache im Team wird die Gruppenstruktur schnell unbrauchbar, weil niemand mehr sicher sein kann, welche Gruppen tatsaechlich existieren und was sie bedeuten. Eine gute Taxonomie ist deshalb kurz, dokumentiert und auf zwei bis drei Dimensionen begrenzt, die sich nicht ueberschneiden.
Eine bewaehrte Grundstruktur unterscheidet nach Geschwindigkeit (etwa fast und slow), nach Testart (etwa unit, integration und functional) und optional nach fachlichem Modul (etwa checkout, inventory und customer). Diese drei Dimensionen lassen sich beliebig kombinieren, ohne dass die Zahl der Gruppen explodiert, weil jede Testmethode einfach diejenigen Gruppen erhaelt, die inhaltlich zutreffen, meist zwei bis drei pro Methode.
6. Gruppen und Filter in der CI-Pipeline gezielt einsetzen
Eine durchdachte Taxonomie zahlt sich vor allem in der CI-Pipeline aus. Bei jedem Push in einen Feature-Branch kann zunaechst nur die Gruppe fast laufen, um innerhalb weniger Sekunden erstes Feedback zu geben. Erst beim Merge in den Hauptbranch oder in einer separaten, parallel laufenden Pipeline-Stufe laufen zusaetzlich die Gruppen slow und integration, die insgesamt laenger dauern duerfen, weil sie seltener und nicht bei jedem einzelnen Commit ausgefuehrt werden muessen.
Dieses gestufte Vorgehen reduziert die durchschnittliche Wartezeit fuer Entwickler erheblich, ohne die Testabdeckung zu verringern, weil am Ende trotzdem alle Tests laufen, nur eben zeitlich gestaffelt nach ihrer Prioritaet fuer schnelles Feedback. Wichtig ist, dass die langsamere zweite Stufe zuverlaessig und fuer jeden sichtbar laeuft, damit sie nicht in Vergessenheit geraet und am Ende doch niemand die vollstaendige Suite regelmaessig ausfuehrt.
# .gitlab-ci.yml (Ausschnitt)
fast-feedback:
stage: test
script:
- vendor/bin/phpunit --group fast
full-suite:
stage: test
only:
- main
- merge_requests
script:
- vendor/bin/phpunit --exclude-group fast
- vendor/bin/phpunit --group fast
7. Filter im lokalen Entwickler-Workflow
Im lokalen Alltag ist --filter meist das praktischere Werkzeug, weil es keine vorherige Kategorisierung im Code voraussetzt. Waehrend ein Entwickler an einem Bugfix in einer bestimmten Klasse arbeitet, laesst sich mit einem einzigen Befehl genau die relevante Testklasse isolieren, was den Rueckkopplungszyklus von TDD drastisch verkuerzt. Viele IDEs, darunter PhpStorm, bieten dafuer eingebaute Buttons, die im Hintergrund automatisch den passenden --filter-Aufruf erzeugen, ohne dass der Entwickler die Syntax von Hand eingeben muss.
Ein guter Workflow kombiniert beide Werkzeuge situationsabhaengig: --filter fuer die punktuelle Arbeit an einer einzelnen Klasse waehrend der Entwicklung, --group fuer die bewusste, dokumentierte Aufteilung der Suite in der CI-Pipeline und fuer regelmaessig wiederkehrende lokale Laeufe wie einen taeglichen Smoke-Test mit nur der Gruppe fast.
8. Haeufige Kombinationen als Composer-Scripts festhalten
Damit die richtige Gruppen- und Filterkombination nicht bei jedem Entwickler neu erfunden oder falsch abgetippt wird, lohnt es sich, die gaengigsten Aufrufe als Composer-Scripts in der composer.json zu hinterlegen. Ein Befehl wie composer test:fast fuehrt dann intern immer denselben, im Team abgestimmten PHPUnit-Aufruf aus, unabhaengig davon, ob ein einzelner Entwickler sich noch an die genaue Gruppen-Syntax erinnert oder nicht.
Das reduziert nicht nur Tippfehler, sondern macht die Gruppen-Taxonomie gleichzeitig fuer neue Team-Mitglieder entdeckbar: Ein Blick in die composer.json zeigt sofort, welche sinnvollen Teilmengen es gibt, ohne dass jemand die vollstaendige Dokumentation der Taxonomie lesen muss. Aenderungen an der Gruppenstruktur muessen dann nur an einer zentralen Stelle gepflegt werden, statt in den Kommandozeilen-Gewohnheiten jedes einzelnen Entwicklers zu verwaisen.
{
"scripts": {
"test:fast": "vendor/bin/phpunit --group fast",
"test:full": "vendor/bin/phpunit",
"test:checkout": "vendor/bin/phpunit --group checkout",
"test:no-legacy": "vendor/bin/phpunit --exclude-group legacy"
}
}
// Aufruf: composer test:fast
9. Taxonomie pflegen, statt sie verwildern zu lassen
Eine Gruppen-Taxonomie ist kein einmaliges Projekt, sondern erfordert laufende Pflege. Neue Module bekommen neue fachliche Gruppen, alte, nicht mehr benoetigte Gruppen sollten aktiv entfernt werden, statt als toter Ballast im Code liegen zu bleiben. Ein einfacher Grep ueber die Codebasis nach allen verwendeten Gruppennamen, regelmaessig ausgefuehrt, deckt schnell auf, wenn sich Tippfehler oder Varianten eingeschlichen haben, etwa slow neben langsam in derselben Codebasis.
Die folgende Tabelle fasst eine bewaehrte, dreidimensionale Taxonomie zusammen, die sich in vielen mittelgrossen bis grossen PHP-Projekten als praktikabel erwiesen hat und als Ausgangspunkt fuer die eigene Team-Konvention dienen kann.
| Dimension | Beispiel-Gruppen | Zweck | Typischer Einsatzort |
|---|---|---|---|
| Geschwindigkeit | fast, slow | Schnelles Feedback von langsameren Laeufen trennen | CI-Pipeline, gestufte Ausfuehrung |
| Testart | unit, integration, functional | Technische Testebene unterscheiden | phpunit.xml testsuites, CI-Stufen |
| Fachliches Modul | checkout, inventory, customer | Gezielt an einem Bereich arbeiten | Lokale Entwicklung, Feature-Branches |
| Sonderfaelle | legacy, flaky-retry | Bewusst markierte Ausnahmen sichtbar machen | Exclude-Group in Standard-Laeufen |
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
Test Groups und Filter in PHPUnit: Das Wichtigste auf einen Blick
@group
Ordnet Tests dauerhaft im Code einer oder mehreren benannten Gruppen zu, auswaehlbar ueber --group und --exclude-group.
--filter
Ad-hoc-Selektion per regulaerem Ausdruck auf Klassen- und Methodennamen, ideal fuer die punktuelle Arbeit an einer Testklasse.
testsuite in phpunit.xml
Grobe, verzeichnisbasierte Ebene fuer die grundlegende Trennung von Unit-, Integrations- und funktionalen Tests.
Taxonomie
Kurz, dokumentiert und auf zwei bis drei nicht ueberschneidende Dimensionen begrenzt, sonst verwildert die Gruppenstruktur schnell.