Test-Coverage als Sicherheitsnetz beim Symfony-Upgrade

Ein Symfony-Upgrade ohne Tests ist ein Blindflug. Wie Sie mit gezielter Test-Coverage genau die Stellen absichern, an denen ein Upgrade wirklich bricht — bevor Sie eine einzige Zeile Framework-Code anfassen.

Sie kennen die Situation: Eine Symfony-Anwendung läuft seit Jahren stabil, verdient Geld, bildet Ihr Geschäft ab. Und dann steht ein Upgrade an — von 4.4 auf 5.4, von 5.4 auf 6, oder der Sprung auf 7. Der erste Reflex ist oft Panik. Der zweite, gefährlichere, ist Übermut: einfach die Composer-Constraints hochziehen und schauen, was passiert.

Beides ist falsch. Ein System, das jahrelang produktiv läuft, ist kein Sanierungsfall — es ist der Beweis, dass Ihre Fachlogik funktioniert. Das Problem bei einem Upgrade ist fast nie diese Logik. Es ist der Code drumherum: die Framework-Kopplung, die deprecated APIs, die Annotations, die Ihr Team vor sechs Jahren gegen die damalige Symfony-Version geschrieben hat. Und genau hier ist Test-Coverage nicht optional, sondern Ihr wichtigstes Werkzeug.

Warum Tests beim Symfony Tests Upgrade das eigentliche Sicherheitsnetz sind

Ein Upgrade verändert Verhalten an tausend kleinen Stellen. Der Container kompiliert anders, Services werden anders aufgelöst, die Form-Komponente behandelt leere Werte anders, die Security-Konfiguration wandert von security.yaml in ein neues Format. Vieles davon fängt der Compiler ab. Vieles aber auch nicht.

Deprecations sind das perfide Beispiel. In Symfony 5 gibt eine deprecated Methode nur eine Warnung aus — der Code läuft weiter. In Symfony 6 ist dieselbe Methode entfernt, und Ihre Anwendung stirbt mit einem Fatal Error. Wenn Sie kein Test durch diese Code-Pfad schickt, merken Sie das erst in Produktion, wenn ein Kunde einen bestimmten Button drückt.

Tests machen das Unsichtbare sichtbar. Sie sind das Netz, das Sie auffängt, wenn Sie beim Umbau der Framework-Schicht ausrutschen. Ohne dieses Netz upgraden Sie auf Hoffnung. Mit ihm upgraden Sie auf Basis von Fakten.

Erst messen, dann handeln: Wo steht Ihre Coverage wirklich?

Bevor Sie irgendetwas hochziehen, verschaffen Sie sich einen ehrlichen Überblick. Lassen Sie PHPUnit mit Coverage laufen:

vendor/bin/phpunit --coverage-html var/coverage

Erwarten Sie kein schönes Bild. Bei den meisten gewachsenen Anwendungen liegt die Coverage im niedrigen zweistelligen Prozentbereich oder es gibt gar keine Tests. Das ist kein Grund zur Scham, sondern eine Ausgangslage. Der entscheidende Punkt: Sie brauchen für ein Upgrade keine 90-Prozent-Coverage. Sie brauchen Coverage an den richtigen Stellen.

Die richtigen Stellen sind nicht Ihre Getter und Setter. Es sind die Pfade, an denen Geld fließt, an denen Berechtigungen geprüft werden, an denen Daten in die Datenbank geschrieben werden. Priorisieren Sie nach Geschäftsrisiko, nicht nach Zeilenzahl.

Charakterisierungstests: die Fachlogik einfrieren

Wenn wenig Coverage existiert, ist der wirkungsvollste Schritt oft ein sogenannter Charakterisierungstest (Characterization Test). Die Idee: Sie schreiben keinen Test, der beschreibt, was der Code tun sollte. Sie schreiben einen Test, der festhält, was er tatsächlich tut — inklusive der Macken.

Nehmen Sie einen zentralen Service, etwa eine Rechnungsberechnung. Sie füttern ihn mit realistischen Eingaben und schreiben die aktuelle Ausgabe als erwarteten Wert fest. Ob diese Ausgabe „korrekt" ist, spielt keine Rolle — sie ist das seit Jahren gelebte Verhalten, auf das sich Ihr Geschäft verlässt. Nach dem Upgrade muss exakt dasselbe herauskommen. Weicht etwas ab, haben Sie einen Regression gefunden, bevor Ihr Kunde ihn findet.

Für die Fachlogik, die sich nicht ohne Weiteres isolieren lässt, sind Golden-Master-Ansätze nützlich: Sie serialisieren die komplette Ausgabe eines Prozesses (etwa ein generiertes PDF-Datenobjekt oder eine JSON-Response) und vergleichen sie nach dem Upgrade byteweise.

Funktionale Tests decken die Framework-Naht ab

Unit-Tests prüfen Ihre Logik in Isolation — aber ein Upgrade bricht meist genau an der Naht zwischen Ihrem Code und dem Framework. Deshalb sind funktionale Tests mit dem WebTestCase hier besonders wertvoll:

  • Routing und Controller: Ein einfacher Test, der eine Route aufruft und den Statuscode prüft, findet zerbrochene Argument-Resolver oder geänderte Response-Typen sofort.
  • Security: Testen Sie, dass geschützte Routen ohne Login mit 302 oder 403 antworten und mit Login mit 200. Die Security-Komponente hat sich zwischen den Major-Versionen stark verändert — hier lauern die teuersten Fehler.
  • Formulare: Ein Test, der ein Formular absendet und die Persistierung prüft, deckt geändertes Verhalten bei leeren Feldern, CSRF und Validierung ab.

Diese Tests laufen langsamer als Unit-Tests, aber sie prüfen genau die Integrationsschicht, die ein Upgrade anfasst. Für die Datenbank empfiehlt sich eine dedizierte Test-Datenbank mit Fixtures, sodass Sie reproduzierbar gegen echtes Doctrine-Verhalten testen — auch dort ändern sich zwischen Doctrine-Versionen Details bei Typ-Mappings und Lazy Loading.

Der praktische Ablauf

Bringen Sie es in eine Reihenfolge, die das Risiko minimiert:

  1. Grüne Basis: Alle vorhandenen Tests müssen auf der aktuellen Version grün sein. Ein rotes Testset ist als Referenz wertlos.
  2. Lücken schließen: Ergänzen Sie Charakterisierungs- und funktionale Tests für die geschäftskritischen Pfade — nicht für alles.
  3. Deprecations aufräumen: Aktivieren Sie den symfony/deprecation-contracts-Reporter und die Deprecation-Anzeige von PHPUnit. Beheben Sie Deprecations auf der alten Version, solange sie nur Warnungen sind. Das ist die Kernidee: Sie machen Ihren Code kompatibel, bevor Sie die Version wechseln.
  4. Version hochziehen: Erst jetzt ändern Sie die Composer-Constraints — idealerweise ein Major-Schritt nach dem anderen, nicht zwei auf einmal.
  5. Testset laufen lassen: Rot bedeutet nicht Katastrophe, sondern eine präzise Landkarte dessen, was noch zu tun ist.

Ein Werkzeug wie Rector mit dem Symfony-Regelset kann viele mechanische Änderungen automatisieren. Aber Rector ist nur so sicher wie Ihre Tests: Ohne Netz wissen Sie nicht, ob die automatische Umschreibung das Verhalten erhalten hat. Erst die Kombination aus Rector und Coverage macht ein Upgrade zügig und sicher.

Ein ehrlicher Praxis-Tipp

Fangen Sie nicht mit dem Framework an, fangen Sie mit dem Verstehen an. Bevor Sie eine Zeile Symfony-Code anfassen, sichern Sie mit ein paar Charakterisierungstests die drei, vier Prozesse ab, deren Ausfall Sie am meisten Geld kosten würde. Das sind oft nur ein bis zwei Tage Arbeit — und sie verwandeln ein nervöses Upgrade in einen kontrollierten, überprüfbaren Vorgang. Alles andere kommt danach.

Und wenn Ihre Anwendung so eng mit dem Framework verwoben ist, dass sich nichts isolieren lässt: Genau an dieser Entkopplung und dem Aufbau eines belastbaren Sicherheitsnetzes unterstützt LegacyWerk, ohne dass Sie neu bauen müssen.

Sicherer Upgrade-Ablauf 1. Grüne Basis 2. Lücken schließen 3. Deprecations aufräumen 4. Version hochziehen 5. Tests laufen lassen Ohne Netz Fehler zeigt sich erst in Produktion Mit Test-Coverage Regression sichtbar, bevor der Kunde sie findet

Ist Ihre PHP-Anwendung noch zu retten?

Im kostenlosen Kurz-Check schaue ich mit Ihnen auf Ihr System und sage Ihnen ehrlich, was möglich ist – unverbindlich und ohne Verkaufsdruck.

Kurz-Check vereinbaren