Test-Coverage für Legacy-Projekte: Wie man nachträglich Tests einführt

Eine Änderung im Code, und niemand weiß, was noch alles kaputtgeht. Kommt Ihnen das bekannt vor? Bei den meisten Legacy-Projekten gibt es keine einzige automatisierte Prüfung. Jede Anpassung ist ein Sprung ins Ungewisse. Wer jetzt test coverage legacy nachrüsten will, merkt schnell: Der Code wehrt sich dagegen, weil niemand ihn testbar gebaut hat.
Das ist kein Grund, es zu lassen. Es ist ein Grund, klug vorzugehen.
Was passiert, wenn niemand testet?
Ohne Tests bleibt jede Änderung ein Risiko. Ein Entwickler ändert eine Funktion und weiß nicht, welche anderen Stellen im System davon abhängen. Fehler fallen erst beim Kunden auf, nicht vorher.
Mit der Zeit trauen sich Entwickler immer weniger an den Code heran. Sie kopieren lieber eine bestehende Funktion und ändern eine Kleinigkeit, statt die alte zu verändern. So wachsen die technischen Schulden weiter, während die Anwendung von innen immer unübersichtlicher wird.
Am Ende kostet jede kleine Änderung mehr Zeit als nötig, weil sie manuell durchgetestet werden muss. Das treibt die Kosten in die Höhe und verlangsamt jede Weiterentwicklung.
Warum ist Testabdeckung bei Legacy-Code so schwer?
Tests brauchen testbaren Code. Legacy-Anwendungen sind oft anders aufgebaut: Datenbankzugriffe, Geschäftslogik und Ausgabe stecken in derselben Funktion. Eine einzelne Methode kann tausend Zeilen lang sein und ein Dutzend Aufgaben gleichzeitig erledigen.
Um so etwas zu testen, müsste man fast das ganze System gleichzeitig starten. Genau das macht klassische Unit-Tests fast unmöglich. Wer trotzdem versucht, jede Zeile abzudecken, verliert sich in einem Projekt ohne Ende.
Deshalb scheitern viele gut gemeinte Versuche, test coverage legacy von null auf hundert zu bringen. Der Anspruch ist zu groß, die Zeit zu knapp, die Motivation verpufft nach ein paar Wochen.
Wo Sie wirklich anfangen sollten
Nicht jede Codezeile verdient einen Test. Manche Bereiche ändern sich nie wieder, manche sind unkritisch, wenn sie mal einen Fehler haben. Die Priorität sollte woanders liegen.
Kritische Geschäftslogik zuerst
Fangen Sie bei den Funktionen an, die am meisten Schaden anrichten, wenn sie falsch rechnen oder falsch entscheiden. Zahlungsabwicklung, Preisberechnung, Rechteprüfung. Genau dort, wo ein stiller Fehler Geld kostet oder Vertrauen zerstört.
Bereiche mit häufigen Änderungen
Code, der ständig angefasst wird, profitiert am meisten von Tests. Jede neue Änderung lässt sich sofort prüfen, statt jedes Mal von Hand durchzuklicken. Hier zahlt sich der Aufwand am schnellsten aus.
Bekannte Fehlerquellen
Wenn ein Bereich in der Vergangenheit wiederholt Probleme gemacht hat, ist das ein klares Signal. Ein Test verhindert, dass derselbe Fehler ein zweites oder drittes Mal auftaucht.
Charakterisierungstests: der pragmatische Einstieg
Bevor Sie Legacy-Code umbauen, brauchen Sie eine Absicherung, die zeigt, ob sich das Verhalten verändert hat. Genau dafür gibt es Charakterisierungstests. Sie prüfen nicht, ob der Code richtig ist, sondern nur, ob er sich nach einer Änderung gleich verhält wie vorher.
Der Vorteil: Sie müssen die Geschäftslogik nicht komplett verstehen, um einen solchen Test zu schreiben. Sie beobachten das aktuelle Verhalten, schreiben es als Test fest und haben ab diesem Moment ein Warnsystem für ungewollte Änderungen.
Diese Tests sind der erste Schritt, bevor Sie mit einer Refactoring-Strategie tiefer in den Code eingreifen. Ohne dieses Sicherheitsnetz wird jedes Refactoring zum Blindflug.
Integrationstests statt perfekter Unit-Tests
Reine Unit-Tests setzen voraus, dass sich einzelne Funktionen isoliert testen lassen. Bei stark verwobenem Legacy-Code ist das oft nicht praktikabel, zumindest nicht am Anfang.
Ein pragmatischerer Weg sind Integrationstests, die einen ganzen Ablauf prüfen: Eingabe rein, Ergebnis raus. Sie decken weniger Details ab, dafür lassen sie sich schneller schreiben und liefern trotzdem echten Schutz vor Regressionen.
Erst wenn ein Bereich später isoliert und aufgeräumt wird, lohnt sich der Wechsel zu feingranularen Unit-Tests.
Welche Werkzeuge tatsächlich helfen
Für PHP-Projekte hat sich PHPUnit als Standard etabliert, auch für alten Code. Für Java-Anwendungen ist JUnit die naheliegende Wahl. Beide Werkzeuge sind seit Jahren stabil und lassen sich auch in Systeme einbauen, die sonst wenig modern wirken.
Für Charakterisierungstests eignen sich zusätzlich sogenannte Approval-Tests. Dabei speichert das Testwerkzeug die Ausgabe einer Funktion einmal als Referenz. Jeder spätere Testlauf vergleicht das aktuelle Ergebnis mit dieser Referenz. Weicht etwas ab, schlägt der Test Alarm, ganz ohne dass jemand vorher jede Regel von Hand aufschreiben musste.
Wichtig ist dabei weniger das gewählte Werkzeug als die Disziplin, es tatsächlich in den Arbeitsalltag einzubauen. Ein Testwerkzeug, das einmal eingerichtet und danach ignoriert wird, bringt nichts.
Wann sich Refactoring für Tests nicht lohnt
Nicht jeder Codeabschnitt sollte testbar gemacht werden. Wenn eine Funktion seit Jahren unverändert läuft und niemand plant, sie anzufassen, bringt ein aufwendiger Umbau für Tests wenig.
Der Aufwand für Refactoring plus Tests muss in einem vernünftigen Verhältnis zum Nutzen stehen. Wer hier übertreibt, bindet Zeit und Budget, die an anderer Stelle mehr bewirken würden. Diese Abwägung gehört zu jedem Plan, um technische Schulden abzubauen.
Ein realistischer Fahrplan
Ein Vorschlag, der sich in der Praxis bewährt hat: Beginnen Sie mit einer Bestandsaufnahme der kritischsten Bereiche. Schreiben Sie für diese Bereiche zuerst Charakterisierungstests. Erst danach starten Sie mit vorsichtigem Refactoring, abgesichert durch genau diese Tests.
Neuer Code bekommt von Anfang an Tests, auch wenn der Rest der Anwendung noch ungetestet ist. So wächst die Testabdeckung mit jeder Änderung, statt in einem einmaligen Großprojekt zu versanden. Das gilt besonders, wenn Teile der Anwendung mit KI-Unterstützung entstehen. Auch schnell generierter Code muss professionell abgesichert werden, bevor er produktiv läuft.
Setzen Sie sich außerdem ein realistisches Ziel für die Testabdeckung. Hundert Prozent sind für ein gewachsenes System selten sinnvoll und kaum erreichbar. Schon eine Abdeckung der kritischsten zwanzig Prozent des Codes senkt das Risiko spürbar, weil genau dort die meisten Fehler mit hohem Schaden entstehen.
Fazit: Test-Coverage ist ein Prozess, kein Projekt
Test coverage legacy einzuführen bedeutet nicht, alles auf einmal zu testen. Es bedeutet, dort anzufangen, wo Fehler am teuersten wären, und von dort aus schrittweise weiterzumachen.
Diese Arbeit lässt sich mit der laufenden Software-Wartung verbinden, statt sie als separates Großprojekt zu behandeln. So sinkt das Risiko bei jeder künftigen Änderung, ohne dass der laufende Betrieb stillsteht.
Sprechen Sie uns an. Das Erstgespräch ist kostenlos. Wir schauen uns Ihren Code an und sagen Ihnen ehrlich, wo eine Testabsicherung den meisten Unterschied macht.


