NCA Social Media
Aktualisiert:
Autor:
Roland Golla
Grünes Sicherheitsnetz Characterization Tests fängt schwebenden Codeblock

Characterization Tests: Definition

Ein Characterization Test prüft nicht, ob Code richtig arbeitet. Er hält fest, was der Code heute tatsächlich tut, inklusive aller Merkwürdigkeiten. Damit entsteht ein Sicherheitsnetz für Legacy Code, den niemand mehr vollständig versteht.

Der Begriff stammt von Michael Feathers. Unter dem Namen Golden Master Testing ist dieselbe Technik ebenfalls verbreitet, dort wird die Ausgabe einmal aufgezeichnet und bei jedem Testlauf mit dem gespeicherten Original verglichen. Der Unterschied zum klassischen Unit Test ist die Blickrichtung: Ein Unit Test beschreibt die Absicht, ein Characterization Test beschreibt die Realität. Wenn eine Berechnung seit acht Jahren falsch rundet, wird genau dieses Runden zum Testfall. Erst danach entscheidet das Team, ob es das Verhalten behalten oder bewusst ändern will.

Characterization Tests mit NCA: Schnelle Hilfe vom Experten

Testabdeckung für ungetesteten Bestand aufzubauen ist unsere Kernkompetenz. Roland Golla ist Cypress Ambassador und arbeitet seit über 20 Jahren an Softwarequalität in PHP Projekten. Wir setzen PHPUnit, PHPStan und Cypress täglich in Produktion ein, nicht nur im Training.

Teams begleiten wir im PHP Consulting, beim Refactoring Workshop und im Cypress Testing. Die Werkzeuge dafür stehen im Glossar: PHPUnit für Unit und Functional Tests, Functional Tests mit Symfony, Infection für Mutation Testing und Patch Coverage für die richtige Messung im CI. Bestehende Hotspots findet Churn PHP.

Lass uns sprechen

Finde das passende Angebot für dein Projekt

Anfrage-Konfiguration

Starten Sie Ihre Anfrage

Projektart
Infos
Nachricht

Eliminierung technischer Schulden mit PHPStan, Rector PHP und PHPUnit. Über 20 Jahre Praxiserfahrung in skalierbaren Backends.

CORE EXPERTISE

Gesetzliche Konformität & Inklusion. Optimierung von Performance und Conversion durch radikal nutzerzentriertes, universelles Design.

BFSG COMPLIANT

Skalierbare KI-Systeme mit echtem Code Ownership. CI/CD, Backup-Strategien und Infrastruktur, die mit deinem Team wächst.

ENTERPRISE READY

Das Henne Ei Problem im Legacy Code

Refactoring braucht Tests. Tests brauchen testbaren Code. Legacy Code ist selten testbar. Aus dieser Schleife kommt man nicht heraus, indem man wartet.

Der Characterization Test bricht sie auf, weil er eine andere Frage stellt. Nicht: Was soll dieser Code tun? Sondern: Was kommt heraus, wenn ich ihn mit diesen Werten aufrufe? Diese Frage lässt sich beantworten, auch ohne Dokumentation und ohne den ursprünglichen Autor.

Das Vorgehen ist bewusst mechanisch. Man ruft die Funktion auf, behauptet ein beliebiges Ergebnis, lässt den Test scheitern und liest im Fehler ab, was wirklich zurückkommt. Dieser echte Wert wandert dann in den Test. Der Test ist grün und dokumentiert ab jetzt das Ist Verhalten.

Was zunächst wie ein Trick wirkt, ist das eigentliche Werkzeug: Jeder rote Test bringt neue Information über das System. Nach ein paar Runden versteht das Team den Code besser als jede vorhandene Dokumentation es beschreiben könnte.

Vorgehen in vier Schritten

Der Ablauf ist immer derselbe, egal ob es um eine einzelne Methode oder einen ganzen Controller geht.

Erstens: Einstiegspunkt finden. Meist ist das die äußerste Stelle, die sich noch ohne Umbau aufrufen lässt. Bei Symfony Anwendungen ist das oft ein Request über den WebTestCase, weil dort keine internen Abhängigkeiten aufgebrochen werden müssen.

Zweitens: Aufrufen und scheitern lassen. Eine bewusst falsche Erwartung im Test liefert im Fehlertext das echte Ergebnis. Das ist schneller als jede Analyse im Debugger.

Drittens: Ist Wert einsetzen. Der Test wird grün und hält das Verhalten fest. Der Testname beschreibt zu diesem Zeitpunkt oft noch nichts Fachliches. Das ist in Ordnung, er wird umbenannt, sobald klar ist, was der Fall bedeutet.

Viertens: Abdeckung erweitern. Ein Coverage Report zeigt, welche Zweige noch nie durchlaufen wurden. Jeder unbelichtete Zweig wird zur nächsten Frage: Welche Eingabe führt hier hinein? So wächst das Netz gezielt statt zufällig.

Sobald die kritischen Pfade abgedeckt sind, beginnt das eigentliche Refactoring. Vorher nicht. Wer beides gleichzeitig macht, weiß bei einem roten Test nie, ob der Umbau kaputt war oder der Test falsch.

Golden Master für komplexe Ausgaben

Bei großen Ausgaben wird das Einsetzen einzelner Werte unpraktisch. Wenn eine Rechnung ein PDF erzeugt oder ein Export mehrere hundert Zeilen liefert, will niemand hunderte Assertions schreiben.

Hier hilft der Golden Master. Die Ausgabe wird einmal in eine Datei geschrieben und geprüft. Bei jedem weiteren Lauf vergleicht der Test die aktuelle Ausgabe mit dieser Datei. Weicht sie ab, schlägt der Test fehl und zeigt den Unterschied.

Code:
          

public function testExportStaysUnchanged(): void
{
    $result = $this->exporter->generate($this->sampleData());

    $reference = __DIR__ . '/reference/export.txt';

    // Create the file once on the first run, compare from then on
    self::assertStringEqualsFile($reference, $result);
}

Zwei Dinge sind dabei entscheidend. Erstens muss die Ausgabe reproduzierbar sein. Zeitstempel, Zufallswerte und IDs aus der Datenbank machen jeden Vergleich wertlos. Sie werden vorher normalisiert, etwa durch feste Werte im Test. Wer diesen Normalizer erst nach zwanzig Referenzdateien einbaut, darf alle neu erzeugen.

Zweitens braucht jede Änderung an einer Referenzdatei ein Review. Ein grüner Balken nach dem Überschreiben bedeutet nichts. Die Frage im Merge Request lautet immer: Ist dieser Unterschied gewollt?

Unit Test und Characterization Test im Vergleich

Merkmal Unit Test Characterization Test
Frage Tut der Code das Richtige? Was tut der Code heute?
Erwartung Aus der Fachlichkeit abgeleitet Aus dem laufenden System abgelesen
Bei einem Fehler im Code Der Test schlägt fehl Der Fehler wird mit festgeschrieben
Lebensdauer Dauerhaft Teil der Suite Übergangsnetz, wird später ersetzt

Grenzen und was KI beitragen kann

Der wichtigste Vorbehalt steht in der Tabelle oben: Ein Characterization Test schreibt auch Bugs fest. Er weiß nicht, dass die Rundung falsch ist, er weiß nur, dass sie so passiert. Deshalb gehört zu jedem solchen Test eine Notiz, was gerade konserviert wird und warum. Später, wenn das Verhalten geklärt ist, wird der Test durch einen echten Unit Test mit fachlicher Erwartung ersetzt.

Ein zweiter Punkt ist die Ebene. Characterization Tests auf Unit Ebene erfordern oft, dass zuerst Abhängigkeiten aufgebrochen werden, etwa Singletons oder statische Aufrufe. Das ist bereits ein Eingriff in den Code, den man eigentlich absichern wollte. Deshalb starten wir meist eine Ebene höher, über Functional Tests oder über die Oberfläche mit Cypress. Dort verändert sich nichts am Produktivcode, und der Test überlebt selbst einen kompletten Austausch der Bibliotheken darunter.

KI Modelle sind bei dieser Arbeit eine echte Hilfe. Sie erklären Bestandscode, listen mögliche Sonderfälle auf und schlagen Testfälle vor. Genau das ist die mühsame Seite von Characterization Testing.

Die Reihenfolge bleibt trotzdem unverändert: erst das Netz, dann der Umbau, und der Umbau läuft geführt mit Review, nicht als autonomer Agent über die Codebasis. Wichtig ist außerdem der Datenschutz. Coding Modelle, die nicht auf eigener Infrastruktur laufen, arbeiten bei uns immer gegen lokale Entwicklungsumgebungen mit Fake Daten. Wie das praktisch aussieht, steht in unseren Vibe Coding Best Practices.

To me, legacy code is simply code without tests.

Michael Feathers, Autor von Working Effectively with Legacy Code – Working Effectively with Legacy Code

Die NCA Erfahrung mit ungetestetem Bestand

Fast jedes Projekt, das bei uns landet, beginnt an derselben Stelle: Es gibt Code, der Geld verdient, und niemand traut sich, ihn anzufassen. Der Weg heraus führt immer über Tests, nie über Mut.

Geschrieben werden sie mit PHPUnit, ergänzt um Functional Tests, Symfony KernelTestCase und HttpClient MockResponse für externe Aufrufe. Wie gut das Netz wirklich hält, misst Infection über Mutation Testing, im CI achtet Patch Coverage darauf, dass neue Zeilen abgedeckt sind. Bei großen Suiten beschleunigt Paratest die Laufzeit, phpcov führt Coverage Daten zusammen. Wo man anfängt, zeigen Churn PHP und PHP Metrics. Über die Oberfläche prüfen wir mit Cypress, dazu passt BackstopJS für visuelle Regression.

CYPRESS.IO Ambassador und IT Consultant für QA Engenieering und Qualität in PHP Projekten.

Erreichen Sie unsere PHP Consultant Spezialisten

Wir sind Experten für PHP und helfen Ihnen, Ihre digitalen Herausforderungen zu meistern. Unser erfahrenes Team unterstützt Sie bei PHP Updates, PHP Refactoring und berät Sie remote zu allen Fragen rund um PHP. Mit unseren vollautomatischen CI/CD Deployments und einer robusten Docker-Infrastruktur bringen wir Ihre PHP-Projekte auf das nächste Level. Vertrauen Sie auf unsere Expertise für zuverlässige und skalierbare PHP-Lösungen.

Häufige Fragen zu Characterization Tests

Diese Fragen kommen bei fast jedem Refactoring Workshop auf.

Was ist ein Characterization Test 2026 in einem Satz?

Ein Test, der nicht das gewünschte, sondern das aktuelle Verhalten von Code festhält. Er entsteht, indem man den Code aufruft, das tatsächliche Ergebnis abliest und als Erwartung einsetzt. Damit lässt sich Legacy Code absichern, ohne dass jemand seine ursprüngliche Absicht kennen muss.

Was ist 2026 der Unterschied zum Golden Master Test?

Es sind zwei Namen für dieselbe Idee mit unterschiedlicher Umsetzung. Beim klassischen Characterization Test stehen die erwarteten Werte im Testcode. Beim Golden Master wird die gesamte Ausgabe in eine Referenzdatei geschrieben und bei jedem Lauf verglichen. Golden Master lohnt sich bei großen Ausgaben wie Exporten oder Dokumenten.

Schreibe ich damit 2026 nicht auch Bugs fest?

Ja, und das ist beabsichtigt. Der Test hält das Ist Verhalten fest, damit ein Refactoring nichts unbemerkt verändert. Ob ein Verhalten fachlich richtig ist, wird danach mit dem Fachbereich geklärt. Deshalb gehört zu jedem solchen Test eine Notiz, dass hier konserviert und nicht bewertet wurde.

Wie viele Tests brauche ich 2026, bevor ich refactorn darf?

Nicht hundert Prozent. Wer erst die ganze Anwendung abdecken will, kommt nie zum Umbau. Sinnvoll ist, die Pfade abzusichern, die tatsächlich angefasst werden, und die Abdeckung während der Arbeit auszuweiten. Ein Coverage Report zeigt zuverlässig, welche Zweige noch offen sind.

Funktioniert das 2026 auch bei Code mit Singletons?

Auf Unit Ebene wird es schwierig, weil statische Aufrufe und Singletons sich schlecht ersetzen lassen. Zwei Wege helfen: den Aufruf in eine geschützte Methode auslegen und in einer Testklasse überschreiben, oder eine Ebene höher testen, etwa über einen Request. Der zweite Weg verändert den Produktivcode gar nicht.

Wo lege ich diese Tests im Projekt ab?

Am besten in einem eigenen Verzeichnis, klar getrennt von den regulären Tests. So sieht jeder sofort, dass es sich um ein Übergangsnetz handelt und nicht um die fachliche Wahrheit. Wenn ein Bereich fertig umgebaut ist, wandern die Tests entweder als echte Unit Tests weiter oder werden entfernt.

Wie gehe ich mit Zeitstempeln und Zufallswerten um?

Sie müssen vor dem Vergleich neutralisiert werden, sonst schlägt jeder Lauf fehl. Üblich sind eine feste Uhrzeit über einen Clock Service, feste Seeds für Zufallsgeneratoren und eine Ersetzung dynamischer IDs vor dem Vergleich. Diesen Normalizer plant man am besten vor dem ersten Golden Master ein.

Wann ersetze ich die Tests durch echte Unit Tests?

Sobald klar ist, was ein Codeabschnitt fachlich leisten soll. Dann wird die abgelesene Erwartung durch eine fachlich begründete ersetzt. In der Praxis passiert das schrittweise: Was verstanden und umgebaut ist, bekommt richtige Tests, der Rest bleibt zunächst durch das Netz gesichert.

Kann KI diese Tests für mich schreiben?

Sie kann sehr gut zuarbeiten. Modelle erklären Bestandscode, listen mögliche Sonderfälle auf und formulieren Testgerüste. Der Ablauf bleibt aber derselbe: Test laufen lassen, echtes Ergebnis ablesen, einsetzen. Ein Modell, das die Erwartung errät statt sie zu messen, erzeugt Tests, die nichts absichern.

Reichen E2E Tests mit Cypress nicht aus?

Sie sind ein ausgezeichneter Startpunkt, weil sie über die Oberfläche laufen und einen Technologiewechsel darunter unverändert überstehen. Für gezielte Umbauten an einzelnen Klassen sind sie zu grob und zu langsam. In der Praxis kombinieren wir beides: E2E als äußeres Netz, Characterization Tests dort, wo konkret gearbeitet wird.

Wie lange dauert es, ein Legacy Projekt so abzusichern?

Das hängt von Größe und Zustand ab, eine seriöse Zahl gibt es erst nach einem Blick in den Code. Was sich sagen lässt: Der erste abgesicherte Bereich sollte in Tagen stehen, nicht in Monaten. Wir schätzen den Aufwand nach einem kostenlosen Kennenlernen und rechnen minutengenau ab.

Wie hilft Never Code Alone konkret dabei?

Wir setzen uns mit dem Team an den Bestand, wählen den ersten Bereich aus und bauen das Netz gemeinsam auf. Danach richten wir die Quality Gates in der Pipeline ein, damit die Absicherung nicht verfällt. Kein Festpreis, keine Pakete, transparente Abrechnung nach Minuten.