Von PHPUnit 9 auf PHPUnit 11+: Was uns die Umstellung unserer WordPress-Unittests wirklich gekostet hat

Start » Blog » Von PHPUnit 9 auf PHPUnit 11+: Was uns die Umstellung unserer WordPress-Unittests wirklich gekostet hat

Wer WordPress-Plugins oder Themes über Jahre pflegt, kennt das Problem: Irgendwann läuft der Server auf PHP 8.2 oder 8.3, aber die Test-Suite hängt noch an PHPUnit 9 fest – weil genau dort das Mocking-Framework der Wahl, WP_Mock, seit Ewigkeiten Kompatibilitätsprobleme mit neueren PHPUnit-Versionen hat. Irgendwann ist der Punkt erreicht, an dem man nicht mehr drumherumkommt. Bei uns war das dieses Jahr der Fall, bei zwei ganz unterschiedlichen Projekten. Hier ist, was dabei tatsächlich passiert ist inklusive der Sackgassen.

PHPUnit-Migration 9->11

Der Ausgangspunkt: Warum PHPUnit 9 überhaupt zum Problem wurde

Unsere Test-Suiten liefen jahrelang stabil mit PHPUnit 9 und WP_Mock, das WordPress-Core-Funktionen wie get_option() oder wp_insert_post() mockt, ohne dass eine echte WordPress-Installation oder Datenbank im Hintergrund laufen muss. Für reine Unit-Tests – also Tests, die Logik isoliert prüfen, statt das Zusammenspiel mit dem WordPress-Core zu testen – ist das nach wie vor der pragmatischste Ansatz.

Das Problem kam nicht aus der eigenen Codebasis, sondern von außen: Mit PHP 8.1 und aufwärts häuften sich Deprecation-Warnings und PHPUnit 9 wird von den PHPUnit-Maintainern längst nicht mehr unterstützt. Composer-Updates von anderen Dependencies zogen zunehmend PHPUnit 10 oder 11 als Mindestanforderung nach sich, wodurch wir in Versionskonflikte liefen, sobald wir irgendein anderes Paket aktualisieren wollten. Der Testlauf selbst funktionierte noch, aber jedes composer update wurde zur Zitterpartie.

Kurz: Es war keine Frage von „wollen wir migrieren“, sondern „wann bricht uns die Suite ohne Update komplett weg“.

Zwei Projekte, zwei unterschiedliche Lösungen

Wir haben die Migration nicht einheitlich angepackt, sondern projektspezifisch entschieden – aus gutem Grund, wie sich zeigen sollte.

Projekt A: Der komplette Wechsel zu Brain Monkey

Für das erste Projekt haben wir uns entschieden, WP_Mock komplett durch Brain Monkey zu ersetzen. Der Hauptgrund: Brain Monkey wird aktiv gepflegt und ist vollständig mit aktuellen PHP- und PHPUnit-Versionen kompatibel – anders als WP_Mock, dessen Hauptzweig zum Zeitpunkt der Migration noch spürbar hinterherhinkte.

Das Grundprinzip von Brain Monkey ist WP_Mock sehr ähnlich: WordPress-Funktionen werden über Patchwork „gepatcht“ und lassen sich per Mockery-Syntax mocken, ohne eine echte WordPress-Umgebung zu laden. Der Umstieg bedeutete für uns vor allem, bestehende Testfälle syntaktisch zu übersetzen:

php

// Vorher: WP_Mock
WP_Mock::userFunction('get_option', [
    'args'   => ['tax_rate'],
    'return' => 19,
]);

// Nachher: Brain Monkey
Brain\Monkey\Functions\when('get_option')
    ->justReturn(19);

Und im Setup der Testklasse:

php

use Brain\Monkey;
use PHPUnit\Framework\TestCase;

class PriceCalculatorTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        Monkey\setUp();
    }

    protected function tearDown(): void
    {
        Monkey\tearDown();
        parent::tearDown();
    }

    public function test_price_calculation_with_tax(): void
    {
        Monkey\Functions\when('get_option')->justReturn(19);

        $result = MyPlugin::calculate(100);

        $this->assertEquals(119, $result);
    }
}

In der composer.json sah das dann so aus:

json

"require-dev": {
    "phpunit/phpunit": "^11.5.56",
    "brain/monkey": "^2.7.0"
}

Der eigentliche Aufwand steckte nicht in der Framework-Umstellung selbst – die Mockery-Syntax von Brain Monkey ist gut dokumentiert und die Umstellung darauf lief weitgehend mechanisch ab. Der Aufwand steckte darin, alte WP_Mock-Eigenheiten aufzuspüren, die stillschweigend falsch mitgetestet wurden (z. B. Tests, die implizit von einer bestimmten Aufruf-Reihenfolge ausgingen), weil Brain Monkey an manchen Stellen strenger prüft, ob erwartete Funktionsaufrufe tatsächlich stattfinden.

Projekt B: Der pragmatische Weg über einen kompatiblen WP_Mock-Branch

Beim zweiten Projekt war ein kompletter Wechsel des Mocking-Frameworks aus Zeit- und Aufwandsgründen keine Option – die Test-Suite war umfangreicher, und ein 1:1-Rewrite hätte den Rahmen gesprengt. Hier haben wir stattdessen recherchiert, ob es im WP_Mock-Repository selbst schon Fortschritte in Richtung PHPUnit-11-Kompatibilität gab.

Fündig wurden wir in einem Feature-Branch des offiziellen 10up/wp_mock-Repositories, der genau das adressiert: dev-feature/phpunit-9-13-compatibility. Statt der stabilen, aber veralteten Release-Version haben wir diesen Branch direkt per Composer eingebunden:

json

"require-dev": {
    "phpunit/phpunit": "^11.5.56",
    "10up/wp_mock": "dev-feature/phpunit-9-13-compatibility"
}

Der große Vorteil dieses Ansatzes: Die bestehenden Testfälle mussten so gut wie nicht angefasst werden. WP_Mock::userFunction() blieb WP_Mock::userFunction(), die komplette Suite lief nach dem Composer-Update und ein paar kleineren Anpassungen an internen Assertions praktisch unverändert durch.

Der Nachteil liegt genauso klar auf der Hand: Wir hängen jetzt an einem Feature-Branch statt an einem versionierten Release. Das ist für ein Projekt, das ohnehin schon aktiv weiterentwickelt wird, ein akzeptabler Kompromiss – für eine Bibliothek, die andere als Dependency einbinden, wäre uns das zu unsicher gewesen, weil sich Branches jederzeit ändern oder verschwinden können. Wir haben deshalb den Commit-Hash des Branches zum Zeitpunkt der Migration dokumentiert, um im Zweifel zurückverfolgen zu können, welcher Stand tatsächlich lief.

Zwei Wege, eine Erkenntnis

Rückblickend würden wir sagen: Es gibt hier keine pauschal „richtige“ Lösung, sondern eine Abwägung zwischen zwei Achsen.

  • Brain Monkey lohnt sich, wenn ein Projekt langfristig gepflegt wird, die Test-Suite überschaubar ist oder ohnehin ein Refactoring ansteht. Man kauft sich damit ein aktiv gepflegtes Framework und vermeidet die nächste Migration in ein, zwei Jahren.
  • Ein kompatibler WP_Mock-Branch ist der pragmatische Weg, wenn die Suite groß ist, wenig Zeit für einen Rewrite bleibt und man den bestehenden Testcode so weit wie möglich erhalten will – im Wissen, dass man damit an einer weniger stabilen Quelle hängt und diese Entscheidung regelmäßig überprüfen sollte.

In beiden Fällen war der eigentliche Umstellungsaufwand kleiner als befürchtet, sobald einmal klar war, welcher Weg für das jeweilige Projekt der richtige ist. Die meiste Zeit ging nicht in das Ersetzen von Mock-Aufrufen, sondern ins Aufspüren von Tests, die sich stillschweigend auf altes, zu nachsichtiges Verhalten verlassen hatten.

Fazit

Die Umstellung von PHPUnit 9 auf PHPUnit 11+ ist für WordPress-Projekte selten nur ein composer update. Sie ist der Moment, in dem man sich entscheiden muss, ob man das Mocking-Framework wechselt oder auf einen weniger offiziellen, aber funktionierenden Zwischenweg setzt. Beide Wege sind für uns aufgegangen – wichtig war vor allem, die Entscheidung bewusst pro Projekt zu treffen, statt eine Lösung überzustülpen, die für das eine Projekt passt und für das andere unnötigen Mehraufwand bedeutet hätte.

Wer selbst vor dieser Migration steht und sich unsicher ist, welcher Weg für das eigene Projekt sinnvoller ist, kann uns gerne ansprechen – wir unterstützen bei der Analyse der bestehenden Test-Suite und der konkreten Umsetzung.

Heinrich Franz