Tests in ein bestehendes Laravel-Projekt nachrüsten

Ein Laravel-Projekt ohne Tests umbauen ist Blindflug. So rüsten Sie Laravel Tests nachträglich nach, ohne den Code vorher komplett umzuschreiben.

Sie haben ein Laravel-Projekt geerbt oder über Jahre gewachsen lassen. Es läuft, es verdient Geld, es bildet echte Geschäftsprozesse ab. Nur Tests gibt es keine. Und jetzt sollen Sie ein Feature ändern oder auf eine neuere Laravel-Version heben und wissen nicht, ob dabei etwas kaputtgeht. Genau hier wird das Thema Laravel Tests nachrüsten vom Nice-to-have zur Notwendigkeit.

Vorweg das Wichtigste: Ein System, das seit Jahren im Produktivbetrieb läuft, ist kein Müll. Es ist der lebende Beweis, dass die Fachlogik funktioniert. Fehlende Tests sagen nichts über die Qualität der Geschäftslogik aus, sondern nur darüber, dass niemand sie bisher abgesichert hat. Das lässt sich nachholen, und zwar deutlich günstiger als jede Neuentwicklung.

Warum sich Laravel Tests nachrüsten fast immer lohnt

Tests nachträglich einzuziehen ist kein Selbstzweck. Es geht um genau eine Sache: Sie wollen den Code ändern können, ohne Angst zu haben. Ohne Tests ist jede Anpassung an einem alten System ein Blindflug. Sie deployen und hoffen. Mit einem Sicherheitsnetz aus Tests deployen Sie und wissen.

Der zweite Grund ist die Vorbereitung auf Upgrades. Wer von Laravel 8 auf 11 will oder PHP 7.4 hinter sich lassen muss, braucht eine Instanz, die ihm sagt, ob nach dem Upgrade noch alles tut. Diese Instanz sind Tests. Ohne sie wird jedes Framework-Upgrade zum manuellen Durchklicken der halben Anwendung.

Wo fange ich an, wenn das Projekt gar keine Tests hat?

Nicht bei den Unit-Tests. Das ist der häufigste Fehler. Bei Legacy-Code liegt die Logik selten sauber gekapselt in kleinen, testbaren Klassen vor. Sie steckt in fetten Controllern, in Models mit 800 Zeilen, in globalen Helpern. Wer hier mit isolierten Unit-Tests beginnt, muss den Code erst umbauen, um ihn testbar zu machen, und ändert damit genau das, was er eigentlich absichern wollte.

Der Einstieg sind stattdessen Feature-Tests, in der Testpyramide oft auch als End-to-End- oder HTTP-Tests bezeichnet. Sie feuern eine echte Anfrage in die Anwendung und prüfen die Antwort. Sie interessieren sich nicht dafür, wie der Code intern aufgebaut ist. Genau das brauchen Sie bei Legacy: Absicherung des Verhaltens, ohne die Struktur anzufassen.

Die Reihenfolge lautet: erst das Verhalten absichern, dann den Code aufräumen. Nie umgekehrt.

Ein konkreter erster Feature-Test

Nehmen wir eine Bestellstrecke. Ein solcher Test prüft nicht eine einzelne Methode, sondern den kompletten Durchlauf über die Route:

public function test_bestellung_wird_gespeichert(): void
{
    $user = User::factory()->create();

    $response = $this->actingAs($user)->post('/bestellung', [
        'artikel_id' => 42,
        'menge' => 3,
    ]);

    $response->assertRedirect('/bestellung/erfolg');
    $this->assertDatabaseHas('bestellungen', [
        'user_id' => $user->id,
        'artikel_id' => 42,
        'menge' => 3,
    ]);
}

Dieser Test sagt nichts darüber aus, ob der Controller schön ist. Er sagt: Wenn ein Nutzer bestellt, landet die Bestellung in der Datenbank. Das ist die fachliche Wahrheit, die Sie schützen wollen. Ob dahinter ein Service, ein Job oder 200 Zeilen im Controller stehen, ist dem Test egal.

Charakterisierungs-Tests: das Verhalten festschreiben, wie es ist

Bei altem Code kennen Sie oft das Soll-Verhalten nicht mehr. Niemand weiß, warum eine Rabattberechnung genau so rundet, wie sie rundet. Hier hilft ein Ansatz aus Michael Feathers' Klassiker Working Effectively with Legacy Code: der Charakterisierungs-Test.

Die Idee ist einfach. Sie schreiben keinen Test, der prüft, was der Code tun sollte, sondern einen, der festhält, was er tatsächlich tut. Sie rufen die Funktion mit realistischen Eingaben auf, lesen das Ergebnis ab und schreiben genau dieses Ergebnis als Erwartung in den Test. Ein grüner Test bedeutet dann: Das Verhalten ist unverändert. Ob es fachlich korrekt ist, ist eine zweite Frage, die Sie danach in Ruhe klären.

Die Datenbank ist meistens die eigentliche Hürde

In der Theorie sind Tests einfach. In der Praxis scheitert das Nachrüsten oft an der Datenbank. Alte Projekte haben häufig keine Factories, teils nicht einmal saubere Migrations, sondern nur einen produktiven SQL-Dump. Drei Dinge helfen:

  • Eine eigene Test-Datenbank, niemals die Entwicklungs- oder gar Produktionsdatenbank. In der phpunit.xml setzen Sie die Verbindung explizit, idealerweise auf SQLite in-memory oder eine dedizierte MySQL-Instanz, die zur Produktion passt.
  • Das Trait RefreshDatabase, damit jeder Test in einem sauberen Zustand startet und Tests sich nicht gegenseitig beeinflussen.
  • Factories schrittweise nachziehen. Sie brauchen nicht für jedes der 60 Models sofort eine Factory. Bauen Sie sie genau dann, wenn ein Test sie braucht.

Ein ehrlicher Hinweis zu SQLite: Es macht Tests schnell, verhält sich aber bei bestimmten Datentypen, JSON-Funktionen und strikten SQL-Modi anders als MySQL oder PostgreSQL. Wenn Ihre Anwendung datenbanknahe Features nutzt, testen Sie zumindest die kritischen Pfade gegen dieselbe Engine wie in Produktion. Sonst ist der Test grün und die Realität rot.

Wie viel Abdeckung ist genug beim Nachrüsten?

Jagen Sie keiner Prozentzahl hinterher. 100 Prozent Coverage auf einem Altsystem ist weder realistisch noch sinnvoll. Setzen Sie stattdessen dort an, wo Schmerz und Risiko am größten sind:

  1. Die Bereiche, die Sie als Nächstes ändern müssen. Testen Sie kurz vor dem Umbau, nicht auf Vorrat quer durchs System.
  2. Die geschäftskritischen Pfade: Bezahlung, Registrierung, alles, was direkt Geld oder Vertrauen kostet, wenn es bricht.
  3. Die Stellen, an denen es in der Vergangenheit schon gebrannt hat. Bugs kommen erfahrungsgemäß gern an denselben Stellen wieder.

Diese risikoorientierte Reihenfolge bringt Ihnen mit dem ersten Dutzend Tests bereits den Großteil der Sicherheit. Der Rest wächst mit jedem Feature, das Sie ohnehin anfassen.

In die CI-Pipeline heben, sonst verwaist die Suite

Eine Testsuite, die nur manuell läuft, ist nach drei Wochen kaputt und niemand merkt es. Lassen Sie die Tests bei jedem Push automatisch laufen, etwa über GitHub Actions oder GitLab CI. Ein roter Build muss ein Grund sein, nicht zu deployen. Erst dann wird aus einer Sammlung von Testdateien ein echtes Sicherheitsnetz, auf das sich das Team verlässt.

Ein ehrlicher Praxis-Tipp zum Schluss

Fangen Sie klein und heute an. Ein einziger Feature-Test für den wichtigsten Ablauf Ihrer Anwendung ist mehr wert als ein perfekter Testplan, der nie umgesetzt wird. Schreiben Sie diesen einen Test, lassen Sie ihn in der CI laufen, und bauen Sie von dort aus weiter, immer entlang der Änderungen, die ohnehin anstehen.

Genau bei diesem Vorgehen, Tests nachrüsten und Altsysteme wieder änderbar machen, unterstützt LegacyWerk, wenn intern die Zeit oder die Erfahrung mit Legacy-Code fehlt.

Reihenfolge beim Nachrüsten 1. Feature-Tests Verhalten absichern 2. Charakterisierung Ist-Zustand festschreiben 3. Refactoring Code aufraeumen Priorisierung: nach Risiko, nicht nach Coverage-Prozent Naechste Aenderung - Geschaeftskritische Pfade - Fruehere Bugs Jeder Test laeuft automatisch in der CI

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