@test
assert
PHPUnit 11 · Migration · Breaking Changes · PHP 8.1+
PHPUnit 11: Was sich geändert hat
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.

14 Min. Lesezeit Annotations → Attribute · Coverage · Mocks · phpunit.xml PHPUnit 10 → 11 · PHP 8.1+

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.

11. FAQ: PHPUnit 11 Migration

1Welche PHP-Version benötigt PHPUnit 11?
PHP 8.1 mindestens. PHPUnit 10 noch auf PHP 8.0 als Zwischenstufe nutzbar. PHP upgraden zuerst, dann PHPUnit migrieren.
2Kann ich Annotations und Attribute gleichzeitig verwenden?
In PHPUnit 10 ja, in PHPUnit 11 nicht mehr standardmäßig. Empfehlung: vollständig migrieren, nicht mischen.
3Wie ersetze ich withConsecutive() in PHPUnit 11?
Mit willReturnCallback() und internem Zähler. Für einfache Fälle ohne Argument-Prüfung: willReturnOnConsecutiveCalls().
4Was macht --migrate-configuration?
Konvertiert phpunit.xml automatisch: whitelist → source, deprecated Attribute entfernen, XSD aktualisieren. Direkt zurückgespeichert.
5requireCoverageMetadata vs. beStrictAboutCoverageMetadata?
require schlägt fehl, beStrict gibt Warnung. Bei Migration: erst beStrict aktivieren, nach vollständiger Migration auf require wechseln.
6Kann Rector die Annotation-Migration automatisieren?
Ja. Rector wandelt @test, @dataProvider, @depends, @covers automatisch in PHP-Attribute um. withConsecutive() muss manuell refactort werden.
7Wie ändert sich der DataProvider in PHPUnit 11?
DataProvider-Methoden müssen jetzt static sein: public static function provideData(). Nicht-statische Methoden erzeugen einen Fehler.
8Gibt es neue Assertions in PHPUnit 11?
assertIsEnum() für PHP-8.1-Enums, assertObjectHasProperty() als Ersatz für deprecated assertObjectHasAttribute().
9Was ist der empfohlene Migrationsweg?
PHPUnit 10 → Deprecations beseitigen → Rector → withConsecutive() manuell → --migrate-configuration → PHPUnit 11 → CI durchlaufen.
10Warum ist withConsecutive() entfernt worden?
Design-Problem: Argument-Erwartungen wurden nicht klar validiert. Falsch konfigurierte Mocks schlugen nie fehl. Die Entfernung erzwingt expliziteres Mock-Design.