und was Teams anpassen müssen
PHPUnit 11 ist kein inkrementelles Update – es entfernt Docblock-Annotations zugunsten nativer PHP-Attribute, verschärft die Coverage-Konfiguration, ändert das Mock-System grundlegend und setzt PHP 8.1 als Mindestanforderung. Wer migriert, muss mehr als nur die Versionsnummer in composer.json ändern.
Inhaltsverzeichnis
- 1. PHPUnit 11 im Überblick: Was wirklich neu ist
- 2. Von Docblock-Annotations zu PHP-Attributen
- 3. Coverage-Direktiven in phpunit.xml: whitelist ist weg
- 4. Mock-System: Änderungen und entfernte Methoden
- 5. Neue und entfernte Assertion-Methoden
- 6. phpunit.xml: Schema-Änderungen und neue Direktiven
- 7. PHP-Anforderungen und Typen-Strenge
- 8. Schritt-für-Schritt-Migration für bestehende Projekte
- 9. PHPUnit 10 vs. 11 im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. PHPUnit 11 im Überblick: Was wirklich neu ist
PHPUnit 11 setzt die Bereinigung fort, die mit PHPUnit 10 begann: Alles, was als deprecated markiert war, ist jetzt entfernt. Das betrifft vor allem das Docblock-Annotation-System, das über zwanzig Jahre das primäre Metadaten-System von PHPUnit war. @test, @dataProvider, @depends, @covers und @group – all diese Annotations werden in PHPUnit 11 nicht mehr erkannt, wenn man strenge Attribute-Validierung aktiviert. Stattdessen setzt PHPUnit auf native PHP-Attribute, die seit PHP 8.0 verfügbar sind.
Neben den Annotations sind mehrere Klassen und Methoden aus dem öffentlichen API entfernt worden. TestCase::createMock() existiert zwar noch, aber die Art, wie Mock-Objekte konfiguriert werden, hat sich verändert. withConsecutive() ist komplett entfernt – eine Methode, die in vielen Legacy-Test-Suites intensiv genutzt wurde. Das erfordert oft strukturelle Änderungen in Tests, nicht nur syntaktische.
Der positive Aspekt: PHPUnit 11 ist schneller, strenger und schlägt bei falsch konfigurierten Tests früher fehl, statt stumm fehlerhafte Ergebnisse zu liefern. Teams, die die Migration durchführen, berichten regelmäßig, dass sie dabei echte Fehler in Tests entdecken, die vorher unbemerkt blieben – weil PHPUnit 11 keine stummen Fallbacks mehr für falsch konfigurierte Mocks oder fehlerhafte Assertions akzeptiert.
2. Von Docblock-Annotations zu PHP-Attributen
Die größte sichtbare Änderung in PHPUnit 11 ist der Übergang von Docblock-Annotations zu PHP-Attributen. Statt /** @test */ schreibt man #[Test] vor die Methode. Statt /** @dataProvider provideData */ schreibt man #[DataProvider('provideData')]. Diese Änderung ist nicht nur syntaktisch – PHP-Attribute werden vom Parser verarbeitet, sind typsicher und werden von IDEs wie PhpStorm vollständig verstanden, einschließlich Auto-Completion und Refactoring-Support.
Besonders wichtig: #[CoversClass(MeineKlasse::class)] und #[CoversMethod(MeineKlasse::class, 'methode')] ersetzen die @covers-Annotation. Diese Attribute sorgen für präzise Coverage-Zuordnung – PHPUnit weiß jetzt zur Compile-Zeit (nicht zur Laufzeit), welche Klasse ein Test abdecken soll. Das macht Coverage-Reports zuverlässiger und verhindert falsch-positive Coverage-Zahlen, die durch ungewollte Seiteneffekte entstehen.
<?php
// VORHER: PHPUnit 10 mit Docblock-Annotations
use PHPUnit\Framework\TestCase;
class OrderServiceTest extends TestCase
{
/**
* @test
* @covers \App\Service\OrderService::calculateTotal
* @dataProvider provideOrderData
*/
public function it_calculates_order_total(array $items, float $expected): void
{
// ...
}
public function provideOrderData(): array { /* ... */ }
}
// NACHHER: PHPUnit 11 mit PHP-Attributen
use PHPUnit\Framework\Attributes\CoversMethod;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
#[CoversClass(\App\Service\OrderService::class)]
class OrderServiceTest extends TestCase
{
#[Test]
#[DataProvider('provideOrderData')]
public function itCalculatesOrderTotal(array $items, float $expected): void
{
// ...
}
public static function provideOrderData(): array { /* ... */ }
}
3. Coverage-Direktiven in phpunit.xml: whitelist ist weg
In der phpunit.xml ist die <whitelist>-Direktive seit PHPUnit 10 deprecated und in PHPUnit 11 vollständig entfernt. An ihre Stelle tritt die <source>-Direktive mit <include>- und <exclude>-Elementen. Die Semantik ist dieselbe – welche Dateien in die Coverage-Analyse einbezogen werden –, aber die XML-Struktur hat sich geändert. Wer die alte Konfiguration weiterverwendet, erhält in PHPUnit 11 keine Coverage-Reports mehr.
Ebenfalls neu: die Direktive requireCoverageMetadata="true" auf dem <phpunit>-Element. Wenn gesetzt, schlägt PHPUnit fehl, wenn ein Test keine #[CoversClass]- oder #[CoversMethod]-Attribute hat. Das zwingt Teams dazu, Coverage-Zuordnungen explizit zu machen. In großen Projekten empfiehlt sich zunächst requireCoverageMetadata="false" und eine schrittweise Migration. Das Attribut beStrictAboutCoverageMetadata steuert, ob fehlende Metadata als Warnung oder Fehler behandelt wird.
4. Mock-System: Änderungen und entfernte Methoden
Das größte Migrationshindernis für viele Teams ist die Entfernung von withConsecutive(). Diese Methode erlaubte es, einen Mock so zu konfigurieren, dass er bei aufeinanderfolgenden Aufrufen unterschiedliche Werte zurückgibt. In PHPUnit 11 gibt es keinen direkten Ersatz – stattdessen muss man die Logik umstrukturieren. Der empfohlene Weg ist der Einsatz von willReturnCallback() mit einem internen Zähler oder die Verwendung von willReturnOnConsecutiveCalls(), das aber nur für Rückgabewerte ohne Argument-Prüfung funktioniert.
Ebenfalls geändert: getMockBuilder() akzeptiert keine Strings für Methodennamen mehr, die nicht existieren. PHPUnit 11 ist strenger bei der Mock-Konfiguration – wenn man eine nicht existierende Methode mockt, wirft PHPUnit eine Exception statt den Mock still zu erstellen. Das deckt falsch konfigurierte Mocks auf, die in PHPUnit 10 unbemerkt fehlerhafte Tests erzeugten.
<?php
// ENTFERNT in PHPUnit 11: withConsecutive()
$mock->method('find')
->withConsecutive([1], [2], [3])
->willReturnOnConsecutiveCalls($order1, $order2, $order3);
// ERSATZ mit willReturnCallback():
$callCount = 0;
$returns = [$order1, $order2, $order3];
$mock->method('find')
->willReturnCallback(function (int $id) use (&$callCount, $returns) {
return $returns[$callCount++] ?? null;
});
// ALTERNATIV für einfache Fälle ohne Argument-Prüfung:
$mock->method('find')
->willReturnOnConsecutiveCalls($order1, $order2, $order3);
// Neu: createMockForIntersectionOfInterfaces() für Intersection Types
$mock = $this->createMockForIntersectionOfInterfaces([
\Countable::class,
\Iterator::class,
]);
5. Neue und entfernte Assertion-Methoden
PHPUnit 11 entfernt mehrere Assertion-Methoden, die in früheren Versionen als deprecated markiert waren. assertFileNotExists() heißt jetzt assertFileDoesNotExist(). assertNotEmpty() ist noch vorhanden, aber die negierenden Varianten mit "Not"-Präfix sind konsistenter geworden. Die Methodennamen folgen jetzt durchgängig dem Schema assertXxx() für positive und assertXxxDoesNotExist() für negative Assertions – lesbarer und eindeutiger als die alten assertNotXxx()-Formen.
Neu hinzugekommen sind Assertions für moderne PHP-Features. assertIsEnum() prüft, ob ein Wert ein PHP-8.1-Enum-Instanz ist. assertObjectHasProperty() ersetzt den umständlichen Workaround mit assertObjectHasAttribute(), das in PHP 8.2 deprecated wurde. Teams, die PHP 8.1-Features wie Enums, Fibers und Readonly-Properties testen, finden in PHPUnit 11 dafür passende Assertions, statt sich mit generischen Assertions behelfen zu müssen.
6. phpunit.xml: Schema-Änderungen und neue Direktiven
Die phpunit.xml hat sich in PHPUnit 11 strukturell verändert. Das XSD-Schema ist strenger – Elemente und Attribute, die in früheren Versionen toleriert wurden, erzeugen jetzt Validierungsfehler. Der einfachste Weg, die Konfiguration zu migrieren, ist der Befehl vendor/bin/phpunit --migrate-configuration, der die bestehende phpunit.xml automatisch auf die neue Struktur anpasst. Das deckt die meisten syntaktischen Änderungen ab, aber nicht inhaltliche Anpassungen wie die Coverage-Direktiven.
Neue Direktiven in PHPUnit 11: executionOrder="depends,defects" führt Tests mit Abhängigkeiten zuerst aus und priorisiert zuletzt fehlgeschlagene Tests. displayDetailsOnTestsThatTriggerWarnings="true" zeigt detaillierte Informationen, wenn Tests PHP-Warnings auslösen. Das ist besonders für PHP 8.4-Projekte relevant, wo viele veraltete Patterns nun Deprecation-Warnings erzeugen – PHPUnit 11 macht diese sichtbar, statt sie zu ignorieren.
<?xml version="1.0" encoding="UTF-8"?>
<!-- phpunit.xml — PHPUnit 11 Konfiguration -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory=".phpunit.cache"
executionOrder="depends,defects"
requireCoverageMetadata="false"
beStrictAboutCoverageMetadata="true"
displayDetailsOnTestsThatTriggerWarnings="true"
displayDetailsOnTestsThatTriggerDeprecations="true">
<!-- NEU: <source> statt <whitelist> -->
<source>
<include>
<directory suffix=".php">src</directory>
</include>
<exclude>
<directory>src/Migrations</directory>
<file>src/Kernel.php</file>
</exclude>
</source>
<coverage>
<report>
<html outputDirectory="build/coverage/html"/>
<clover outputFile="build/coverage/clover.xml"/>
<text outputFile="php://stdout" showUncoveredFiles="false"/>
</report>
</coverage>
</phpunit>
7. PHP-Anforderungen und Typen-Strenge
PHPUnit 11 setzt PHP 8.1 als Mindestanforderung. Das bedeutet: Projekte, die noch PHP 7.4 oder PHP 8.0 unterstützen, können nicht auf PHPUnit 11 migrieren, ohne gleichzeitig die PHP-Mindestversion anzuheben. Für die meisten aktiven Projekte ist PHP 8.1 inzwischen selbstverständlich, aber in Legacy-Systemen und Magento-Projekten mit älterer Infrastruktur muss man die PHP-Version zuerst anheben.
Intern nutzt PHPUnit 11 konsequent PHP-8.1-Features: Enums für Test-Status und Result-Typen, readonly Properties für unveränderliche Konfigurationsobjekte und Intersection Types für Mock-Factories. Das macht PHPUnit 11 intern wartbarer und stabiler, hat aber keine direkten Auswirkungen auf den Test-Code – außer dass man diese Features jetzt auch in eigenen Tests sorglos verwenden kann und PHPUnit entsprechende Assertions dafür mitbringt.
8. Schritt-für-Schritt-Migration für bestehende Projekte
Der empfohlene Migrationsweg beginnt mit PHPUnit 10 als Zwischenstufe: Zuerst alle Deprecation-Warnungen in PHPUnit 10 beseitigen – das sind genau die Änderungen, die in PHPUnit 11 zu Fehlern werden. Annotations durch Attribute ersetzen, whitelist durch source, withConsecutive() durch willReturnCallback(). Erst wenn die Test-Suite in PHPUnit 10 ohne Deprecation-Warnungen läuft, aktualisiert man auf PHPUnit 11.
Für große Test-Suites empfiehlt sich eine automatisierte Migration der Annotations. Das Composer-Paket rector/rector mit der Ruleset-Konfiguration für PHPUnit-Attribute kann die meisten Annotations automatisch in Attribute umwandeln. Manuelle Nacharbeit bleibt für withConsecutive() und für Tests mit komplexen @covers-Mustern, die nicht direkt auf Klassen-Attribute übertragbar sind.
| Feature | PHPUnit 10 | PHPUnit 11 | Migrationsaufwand |
|---|---|---|---|
| Test-Markierung | /** @test */ (deprecated) |
#[Test] |
Rector automatisierbar |
| Coverage-Config | <whitelist> (deprecated) |
<source> |
--migrate-configuration |
| Konsekutive Mocks | withConsecutive() (deprecated) |
willReturnCallback() |
Manuell, aufwändig |
| Data Provider | @dataProvider (deprecated) |
#[DataProvider('...')] |
Rector automatisierbar |
| PHP-Mindestversion | PHP 8.1 | PHP 8.1 | Kein Unterschied |
10. Zusammenfassung
PHPUnit 11 ist ein bedeutendes Update, das konsequent mit Legacy-Mustern bricht. Die Umstellung von Docblock-Annotations auf PHP-Attribute macht Tests lesbarer und IDE-freundlicher. Die neuen Coverage-Direktiven in phpunit.xml sind klarer strukturiert. Das striktere Mock-System deckt falsch konfigurierte Tests auf, die früher stille Bugs erzeugten. Der Migrationsaufwand ist real, aber beherrschbar – besonders wenn man mit Rector die Annotation-Migration automatisiert.
Teams, die auf PHP 8.1 oder höher sind, sollten die Migration auf PHPUnit 11 aktiv angehen. Die Investition zahlt sich durch bessere Coverage-Qualität, präzisere Fehlermeldungen und ein Test-Framework aus, das moderne PHP-Features vollständig unterstützt. Der empfohlene Weg: Erst alle PHPUnit-10-Deprecations beseitigen, dann auf 11 upgraden, --migrate-configuration ausführen und manuell die Mock-Refactorings durchführen.
Mironsoft
PHPUnit-Migration, Test-Infrastruktur und Code-Qualität für PHP-Teams
PHPUnit-11-Migration für euer Projekt?
Wir analysieren eure bestehende Test-Suite, identifizieren alle Breaking Changes und führen die Migration von PHPUnit 10 auf 11 durch – mit Rector-Automatisierung und manueller Mock-Refactoring-Unterstützung.
Analyse
Alle Deprecations und Breaking Changes in der bestehenden Test-Suite identifizieren
Automatisierung
Rector-Regeln für Annotation-zu-Attribut-Migration konfigurieren und ausführen
Manuelle Migration
withConsecutive()-Refactorings und Coverage-Direktiven manuell anpassen
PHPUnit 11 Migration — Das Wichtigste auf einen Blick
Annotations → Attribute
@test → #[Test], @dataProvider → #[DataProvider('...')]. Rector automatisiert die meisten Umwandlungen.
Coverage-Konfiguration
<whitelist> → <source> mit <include> und <exclude>. Befehl: --migrate-configuration.
withConsecutive() weg
Ersetzen durch willReturnCallback() mit internem Zähler oder willReturnOnConsecutiveCalls() ohne Argument-Prüfung.
Migrationsstrategie
Erst PHPUnit-10-Deprecations beseitigen, dann auf 11 upgraden. Rector → manuelle Mocks → phpunit.xml → CI-Test.