Das Bild zeigt eine Nahaufnahme von gedruckten Finanzdaten auf Papier, mit mehreren vertikalen Spalten mit Zahlen in schwarzer, grüner und roter Tinte, die Werte wie Preise oder Veränderungen darstellen. Ein metallischer Stift liegt diagonal über dem Blatt und deutet auf eine Analyse oder Überprüfung der Zahlenangaben hin.

In einem früheren Artikel haben wir das Test Oracle kennengelernt. Dabei handelt es sich um eine Instanz, die uns dabei hilft, die Wahrheit in Unit-, Integrations- und End-to-End-Tests zu finden.

Doch was passiert, wenn wir nicht nur die Zukunft vorhersagen (also testen, ob neuer Code funktioniert), sondern die gesamte Vergangenheit auf die Probe stellen wollen?

Willkommen in der Welt des Replay Testings! Dieser Ansatz eignet sich fast ausschließlich für Systeme mit Event Sourcing. Hier begegnen wir einem Orakel, das nicht in die Kristallkugel, sondern in den Rückspiegel schaut. Und was es dort sieht, ist oft gnadenloser als jeder Unit Test.

Das Zeitreise-Paradoxon

Stell dir vor, du könntest alle Transaktionen, Bestellungen und Nutzerinteraktionen der letzten fünf Jahre nehmen und sie durch deine neue Softwareversion jagen. In einer klassischen Datenbank-Architektur (CRUD) ist das fast unmöglich, da nur der aktuelle Zustand erfasst wird ("Kontostand: 50 €"). Wir wissen nicht, wie es dazu kam.

Ein Softwaresystem, das konsequent und konsistent auf Event Sourcing setzt, verfügt hingegen über ein perfektes Gedächtnis. Jedes Ereignis wurde gespeichert: "Konto eröffnet", "Geld eingezahlt", "Geld abgehoben", und so weiter.

Beim Replay Testing nutzen wir dieses Gedächtnis für ein Experiment: Wir spielen die gesamte Geschichte in einer Testumgebung ab, nutzen dabei aber den neuen Code. Wir simulieren also eine alternative Zeitlinie. Die spannende Frage für unser Test Oracle lautet nun: Überlebt die neue Software ihre eigene Geschichte?

Schweigen ist Gold

Die einfachste Form dieses Tests ist: Wir lassen alle Events durchlaufen und definieren Erfolg als "Es ist kein Fehler aufgetreten".

Wir nennen das ein implizites Test Oracle. Es ist wie ein Leibwächter, der nicht viel redet. Solange niemand schreit (Exception), brennt (Fatal Error) oder stirbt (Segfault), nickt der Leibwächter: "Alles okay".

Dieses Test Oracle beantwortet eine sehr spezifische, aber kritische Frage:

Ist mein neuer Code rückwärtskompatibel mit jedem verrückten Edge Case, der in den letzten 5 Jahren in der Produktion aufgetreten ist?

Das ist äußerst wertvoll, denn kein synthetischer Testdatensatz kann mit der Kreativität und dem Chaos der Realität mithalten. Wenn dein Replay Test mit zehn Millionen echten Events durchläuft, kannst du mit einer Sicherheit deployen, von der andere Teams nur träumen können.

Aber Vorsicht: Dieses Orakel ist blind für Logikfehler. Wenn dein neuer Code 1 + 1 = 3 berechnet, stürzt er nicht ab. Er liegt "nur" falsch. Während das implizite Orakel dir applaudiert, geht deine Buchhaltung in Flammen auf.

Reality Check

Um wirklich sicher zu sein, benötigen wir ein explizites Orakel, das nicht nur auf Abstürze, sondern auch auf die Wahrheit achtet.

Hier kommt der eigentliche Zauber ins Spiel: Da wir die Ereignisse aus der Produktion haben, haben wir in der Regel auch die Ergebnisse aus der Produktion, beispielsweise Snapshots der Aggregate oder den Zustand der Read Models.

Der Testablauf ändert sich nun:

  1. Wir nehmen den Zustand eines ausgewählten Kunden aus der Produktion (Kontostand: 50€)
  2. Wir nehmen alle Events dieses Kunden und jagen sie im Test durch die neue Implementierung
  3. Das Orakel vergleicht am Ende: Hat der Kunde im Test auch 50€?

Tritt hier eine Diskrepanz auf, haben wir entweder einen Bug in der neuen Version gefunden, oder, und darin liegt der Clou, einen Bug in der alten Version, den die neue Version korrigiert hat. In beiden Fällen liefert uns dieses Test Oracle eine tiefgreifende Erkenntnis über das Verhalten unseres Systems.

Replay Testing kann auch für A/B Testing auf Basis von historischen Daten verwendet werden: Du spielst alle realen Ereignisse der letzten Jahre mit neuer Geschäftslogik noch einmal durch und beobachtest, wie sich das System verhalten hätte. Statt nur zu prüfen, ob nichts abstürzt, kannst du Fragen beantworten wie "Hätten mehr Kreditanträge abgelehnt werden müssen?". So wird das Event Log zum Experimentierfeld für neue Regeln: ohne Risiko für die Produktion, aber mit maximaler Nähe zur Realität.

Das Orakel zum Schweigen bringen

Wenn wir die Bestellhistorie der letzten fünf Jahre durchspielen, wollen wir nicht, dass das System heute 50.000 "Ihre Bestellung wurde versandt"-E-Mails an echte Kunden verschickt. Das wäre eine Katastrophe.

In meinem vorherigen Artikel habe ich erklärt, dass End-to-End-Tests Seiteneffekte lieben, da diese eine ultimative Bestätigung darstellen: "Die E-Mail ist wirklich angekommen!". Beim Replay Testing müssen wir diese Test Oracles jedoch aktiv stummschalten.

Alle Adapter, die die Systemgrenze verlassen, beispielsweise für den Versand von E-Mails oder die Kommunikation mit Payment Gateways, müssen durch Stubs ersetzt werden.

Hier lauert jedoch eine Falle für eventbasierte Systeme: Wenn eine Komponente unserer Software auf ein Event reagiert, dann setzt sie oft ein Kommando ab, das wiederum ein neues Event erzeugt.

Wenn wir das im Replay Test zulassen, verfälschen wir die Geschichte. Wir würden Events erzeugen, die in der Realität nie passiert sind. Unser Test Oracle wäre verwirrt, da der Event Stream im Test plötzlich anders aussähe als in der Produktion.

Die Regel für das Replay-Orakel lautet daher: Es darf beobachten, rechnen und urteilen, aber niemals handeln.

Herausforderungen in der Praxis

Die weiter oben beschriebene "alternative Zeitlinie" funktioniert nur, wenn der Code deterministisch ist, doch in der Realität lauert überall Nicht-Determinismus. Da, wo die Software auf die Uhr schaut, bekommt sie bei jedem Abspielen für Replay Testing andere Werte, die kritische Geschäftslogik verfälschen können. Noch tückischer sind externe API-Aufrufe: Ein Wechselkurs, ein Lagerbestand oder ein Pricing-Service antwortet heute anders als vor fünf Jahren. Hier hilft nur eines: Alle nicht deterministischen Operationen müssen durch Stubs ersetzt werden. Die Zeit muss gesteuert werden, entweder durch einen Stub mit fest codierten Werten oder durch eine aufgezeichnete, historische Antwort. Zufallswerte und UUIDs müssen aus den Event-Logs rekonstruiert werden, um eine echte Reproduzierbarkeit zu erreichen. Dies kann beispielsweise durch Adapter erreichen, die jeden Aufruf einer API zusammen mit der erhaltenen Antwort protokollieren, sodass beim Replay Testing die exakte Antwort verwendet werden kann.

Kaum ein Softwaresystem bleibt über Jahre hinweg unverändert. Irgendwann müssen auch die Events eines eventbasierten Systems ihre Struktur ändern. Das Hinzufügen eines neuen Feldes oder die Umbenennung eines Feldes erfordert Versionierung und Transformationen. Die gängige Strategie ist das sogenannte Upcasting: Dabei werden alte Events beim Laden in ihre moderne Form transformiert. Das funktioniert, kann aber Replays signifikant verlangsamen. Eine Alternative können In-Place-Transformationen sein, bei denen das Event Log selbst migriert wird. Dabei geht allerdings die Unveränderlichkeit des Event Logs verloren.

Beim Replay Testing wird ein oft übersehenes Problem offenkundig: Die Unveränderlichkeit von Events steht in fundamentalem Widerspruch zur DSGVO und dem Recht auf Vergessenwerden. Werden personenbezogene Daten (wie Namen, E-Mail-Adressen oder Kontonummern) direkt in Events gespeichert, können sie bei einem Replay nicht einfach entfernt werden, da dies die historische Integrität zerstören würde. Eine Lösung besteht darin, personenbezogene Daten gar nicht erst in Events zu speichern, sondern nur Referenzen, wie beispielsweise User-IDs, zu verwenden und die eigentlichen Daten in separaten, traditionellen Datenbanken zu speichern, in denen Löschungen möglich sind. Alternativ können Pseudonymisierungstechniken wie Crypto Shredding eingesetzt werden. Dabei werden sensible Felder verschlüsselt und bei Löschanfragen wird einfach der Verschlüsselungsschlüssel gelöscht: das Datum bleibt unlesbar, ohne dass das Event selbst modifiziert wird. Beim Replay Testing sollten solche Daten vor der Ausführung anonymisiert werden, um Compliance-Risiken auszuschließen.

Der ultimative Regressionstest

Replay Testing ist keine Zauberei, fühlt sich aber so an. Es ist die einzige Testart, die mit 100-prozentig realistischen Daten arbeitet, ohne die Risiken eines Tests in der Produktion einzugehen.

Es ersetzt weder Unit Tests, die uns sagen, warum etwas kaputt ist, noch End-to-End-Tests, die uns sagen, ob das Gesamtsystem jetzt funktioniert. Als Sicherheitsnetz für Refactorings in komplexen Domänen ist es jedoch unschlagbar.

Wenn du das nächste Mal tiefgreifende Änderungen an deinem Event-Sourced System vornimmst, frage nicht nur das Orakel, ob deine Tests grün sind. Frag die Geschichte. Lass sie abspielen. Wenn sie ohne Fehler durchläuft und am Ende das gleiche Ergebnis liefert wie die Realität, dann weißt du, dass du die Wahrheit gefunden hast.