F Fakt Artikel

Beobachtbarkeit >> Vorhersagbarkeit

Ein vorab geschriebener Test ist eine Vorhersage. In einem verteilten System stößt Vorhersage an eine kombinatorische Wand, Beobachtung nicht. Ein Plädoyer dafür, sich vom laufenden System zeigen zu lassen, was zu testen ist.

Tests sind gut. Besonders gut geschriebene: Tests in Schichten, die echte Fehler finden und Ihnen die Sicherheit geben, auszuliefern.

Aber warum sind sie gut? Weil wir uns mit ihnen sicher fühlen? Wegen des grünen Häkchens?

Ein Test, den wir schreiben, bevor wir alle Fälle kennen, ist eine Vorhersage von Verhalten. Das funktioniert für geschlossene Algorithmen wie Sortierfunktionen, Parser und Berechnungen, deren Eingaben sich aufzählen und deren Korrektheit sich auf dem Papier begründen lässt. Funktioniert es auch für ein komplexes verteiltes System? Kann irgendjemand jedes Ergebnis vorhersehen, jede zeitabhängige Wechselwirkung, jede Art, auf die eine Abhängigkeit ausfallen kann, die man nicht kontrolliert?

Wer ehrlich zu sich ist, antwortet mit Nein. Die Informationstheorie legt einen anderen Ausgangspunkt nahe, wenn wir bereit sind, die Perspektive zu wechseln.

Vorhersage hat eine Obergrenze

In Shannons Begriffen trägt ein Ergebnis, das sich mit Gewissheit vorhersagen lässt, keine Information: Es zu beobachten, verrät nichts Neues. Für eine Weltraumrakete passt das. Ihre Flugbahn wird auf dem Papier entworfen und vor dem Start erschöpfend geprüft, denn ein Fehler im Flug lässt keinen zweiten Versuch zu.

Die meisten Systeme sind heute keine Raketen. Sie gleichen eher Organismen: Sie wachsen, nehmen neue Anforderungen auf, hängen von Diensten Dritter ab, die sie nicht kontrollieren, und laufen in Umgebungen, die sich unter ihnen verschieben. Sie müssen sich schnell anpassen, und Anpassung braucht neue Information aus dem laufenden System.

Testen durch Vorhersage setzt voraus, dass sich die relevanten Verhaltensweisen aufzählen lassen, bevor sie eintreten. Für eine einzelne reine Funktion geht das. Für ein verteiltes System aus n Services, von denen jeder in einem von s beobachtbaren Zuständen sein kann, kann das Gesamtsystem in bis zu sⁿ Kombinationen sein.

Nehmen Sie ein bescheidenes System, etwa Googles Demo Online Boutique: Frontend, Warenkorb, Checkout, Produktkatalog, Zahlung, Versand und Empfehlungen. Sieben Services mit je zehn sinnvollen Zuständen: gesund, eingeschränkt, überlastet, Connection-Pool erschöpft, Festplatte voll, DNS-Timeout, Zertifikat abgelaufen, Rate-Limit erreicht, Versionskonflikt und der eine, an den noch niemand gedacht hat.

sⁿ = 10⁷ = 10 000 000

Die Zahl hängt nicht davon ab, wie die Services verbunden sind: sⁿ zählt Kombinationen von Zuständen, keine Verbindungen. Ein Skeptiker könnte einwenden, dass seine Services gar nicht alle miteinander verbunden sind. Stimmt, und weniger Verbindungen bedeuten weniger direkte Wechselwirkungen. Leichter zu testen wird das System dadurch nicht: Die Wechselwirkungen, die es gibt, sind oft weniger offensichtlich, schlechter dokumentiert und überraschender.

Eine Testsuite zieht Stichproben aus diesem Raum. Selbst eine ausgezeichnete Integrationssuite deckt vielleicht einige Hundert Szenarien ab, wenige Tausendstel Prozent davon, und das ist ein kleines System. Kommen ein externer Zahlungsanbieter und ein Message Broker hinzu, erreicht die Zahl 10⁹.

Wie viele Zustände ein System annehmen kann, verglichen mit dem, was eine Testsuite erreicht. Jeder Balken ist eine Anzahl auf logarithmischer Skala: Jede Stufe ist das Zehnfache der vorigen.10⁰10¹⁰10²⁰10³⁰Eine gründliche Integrationssuiteetwa 3007 Services, je 10 Zustände10⁷plus Zahlungsanbieter und Broker10⁹7 Services, 6 Komponenten, 5 Zustände2,3 × 10²⁹mögliche Systemzustände, logarithmisch
Wie viele Zustände ein System annehmen kann, verglichen mit dem, was eine Testsuite erreicht. Jeder Balken ist eine Anzahl auf logarithmischer Skala: Jede Stufe ist das Zehnfache der vorigen.

Das ist keine Schwäche Ihres Test-Frameworks. Es ist eine kombinatorische Wand.

Der Graph im Graphen

Und sⁿ ist noch optimistisch: Es behandelt jeden Service als einen Knoten mit s Zuständen. In Wirklichkeit ist jeder Service selbst ein Graph. Der Checkout-Service hat einen Request-Handler, Bestelllogik, Clients für Zahlung, Versand und Warenkorb und eine Datenbankverbindung, jede mit eigenen Zuständen. Der Zahlungs-Client kann gesund sein, im Timeout hängen oder ausgebremst werden; der Connection-Pool kann voll, eingeschränkt oder in Erholung sein; der Handler kann sich stauen. Diese inneren Zustände setzen sich zusammen.

Hat jeder Service m Komponenten mit je s Zuständen, kann schon ein einzelner Service in sᵐ Zuständen sein, und das ganze System in:

(sᵐ)ⁿ = s^(m·n)

Der Exponent ist selbst ein Produkt: die Komponenten in jedem Service mal die Services im System. Sieben Services mit je sechs Komponenten mit je fünf Zuständen:

5^(6·7) = 5⁴² ≈ 2.3 × 10²⁹

Eine Testsuite deckt einige Hundert davon ab. Das Verhältnis auszurechnen lohnt sich nicht.

Deshalb verfehlen Integrationstests, die das Innere eines Service mocken, was in Produktion ausfällt. Sie haben das Zusammenspiel der Services getestet, aber der Fehler passierte in einem von ihnen: ein Connection-Pool, der unter Last erschöpft war, ein Cache, der im falschen Moment ablief, eine Retry-Schleife, die ein Problem verstärkte, statt es zu dämpfen. Im Graphen im Graphen wohnen die Überraschungen.

Das Blackbox-Problem

Es gibt eine Dimension, die Vorhersage überhaupt nicht erreicht: Abhängigkeiten, die Sie nicht kontrollieren. Fast jedes nicht triviale System mockt externe Dienste: Stripe, AWS, Auth0, PostgreSQL. Doch ein Mock ist ein Modell der Abhängigkeit, nicht die Abhängigkeit selbst. Ändert der Zahlungsanbieter ein Fehlerformat oder führt ein Rate-Limit ein, von dem Sie nichts wussten, liefert Ihr Mock weiter das alte Verhalten. Der Test besteht. Die Produktion fällt aus.

Das Verhalten eines Systems, das Ihnen nicht gehört, können Sie nicht vollständig vorhersagen. Bei einer Blackbox hilft Beobachtung: sehen, was die Abhängigkeit tatsächlich tut, und auf das reagieren, was man dabei lernt.

Die Verifikationsrekursion

Sind Ihre Tests komplex genug, um ein komplexes System zu prüfen, sind sie auch komplex genug, eigene Fehler zu enthalten. Ein fehlerhafter Test kann schlechter sein als keiner: Er gibt falsche Sicherheit und macht Ihr Bild vom System zugleich ungenauer.

Also prüfen Sie die Tests. Aber auch der Prüfer kann Fehler haben:

test(system)           — does the system work?
test(test(system))     — does the test verify it correctly?
test(test(test(…)))    — turtles all the way down

Das ist mehr als eine technische Unannehmlichkeit. Der Satz von Rice (1953) zeigt, dass jede nicht triviale Eigenschaft dessen, was ein Programm tut, im Allgemeinen unentscheidbar ist. Ob ein Test ein System korrekt prüft, ist eine solche Frage: Kein Algorithmus beantwortet sie für jeden Test und jedes System, also beendet kein allgemeines Verfahren die Rekursion. Das macht Tests nicht nutzlos. Es heißt nur, dass weitere Tests nicht ein für alle Mal klären können, ob die Tests stimmen.

In der Praxis zeigt sich das an Tests, die monatelang bestehen, während der Fehler, den sie finden sollten, die ganze Zeit da ist. Wir haben es in unserer eigenen Codebasis gesehen: Als wir Tests in großer Zahl generierten, erwiesen sich etwa 19 % als oberflächlich. Sie erhöhten die Abdeckung, ohne echte Fehler zu finden.

Beobachtung durchbricht die Rekursion

Was, wenn wir die Richtung umkehren?

Statt vorherzusagen, was schiefgehen könnte, und Tests zu schreiben, die es verhindern, beobachten wir, was tatsächlich schiefgeht, und schreiben Tests, die verhindern, dass es wiederkommt.

Das ist beobachtungsgetriebenes Testen, Observation-Driven Testing (ODT):

procedure ODT(system)
    require: observability(system) ≥ threshold
    require: error_handling(system) = graceful

    loop
        observe(system)
        if error detected then
            test ← write_regression_test(error)
            -- the error is real, not transient
            assert test.fails()
            repeat
                fix(code, error)
            until test.passes()
            deploy(system)
        end if
    end loop
end procedure
Vorfall 4 von 5 · Regression: Ein Test reproduziert den Fehler und schlägt fehl Tests aus der Realität: 4 Tests aus Vermutungen: 24

In der Zeichnung setzt sich jeder Test dort ab, wo eine Anfrage tatsächlich fehlschlug: rot, solange er fehlschlägt, hell, sobald die Korrektur ihn bestehen lässt, und dann bleibt er. Die vorab geschriebenen Tests sind gleichmäßig verteilt, wie eine Vermutung es ist. Mehr als ein Drittel sitzt dort, wo nie eine Anfrage vorbeikommt.

Beachten Sie, was das bringt:

Keine Verifikationsrekursion. Der beobachtete Fehler ist echt: Er ist in Produktion oder Staging passiert, nicht in Ihrer Vorstellung. Der Regressionstest zeigt, dass es den Fehler gibt, denn er schlägt vor der Korrektur fehl; die Korrektur lässt ihn bestehen. Der Test gründet auf der Realität, nicht auf einer Vorhersage über sie, und braucht deshalb keinen eigenen Test. Das System selbst war das Orakel.

Keine Explosion des Zustandsraums. Sie zählen nicht jeden möglichen Zustand auf. Sie decken die Pfade ab, die tatsächlich laufen und fehlschlagen. Das laufende System zieht die Stichproben für Sie, und zwar aus den Zuständen, auf die es ankommt, gewichtet danach, wie das System wirklich genutzt wird.

Keine Blindheit gegenüber Blackboxes. Verhält sich eine Abhängigkeit falsch, etwa ein Zahlungsanbieter mit geändertem Fehlerformat oder eine Datenbank, die sich unter Last anders verhält, bemerkt Beobachtung es, sobald es sich im laufenden System zeigt. Sie müssen nicht vorhersagen, was der Anbieter tun könnte. Sie sehen, was er getan hat.

Weniger falsche Sicherheit. Jeder Test in der Suite entspricht einem Fehler, der wirklich passiert ist. Die Suite wird zu einem Fossilbericht der tatsächlichen Fehlerarten des Systems, nicht zu einer Vorhersage hypothetischer.

Wo TDD weiterhin gewinnt

Das ist keine pauschale Absage an testgetriebene Entwicklung. Es gibt Bereiche, in denen Vorhersage funktioniert:

Reine Algorithmen

Sortieren, Parsen, kryptografische Primitive: Der Zustandsraum ist begrenzt, und Korrektheit lässt sich begründen. Schreiben Sie die Tests zuerst.

Vertragsgrenzen

API-Schemas, Protocol Buffers, Serialisierungsformate: Die Spezifikation ist der Test. Schreiben Sie ihn zuerst.

Sicherheitskritische Systeme

Avionik, Medizingeräte, Reaktorsteuerung: Der Preis des Lernens durch Beobachtung, ein Absturz oder ein Todesfall, ist inakzeptabel. Sagen Sie erschöpfend voraus.

Die Trennlinie ist, ob sich der relevante Zustandsraum aufzählen lässt. Wenn ja, sagen Sie voraus. Wenn nicht, und bei den meisten verteilten Systemen ist das so, beobachten Sie.

Die Pflanze und der Bauplan

Verfrühte Vorhersage hält in der wirklichen Welt vieles von dem auf, was gut ist.

Eine Pflanze wächst aus einem kleinen Samen. Sie plant nicht, wohin ihre Wurzeln gehen. Eine Wurzelspitze wächst, trifft auf einen Stein und biegt ab; trifft auf Wasser und verzweigt sich. Das Wurzelwerk ist vom tatsächlichen Boden geformt, nicht von einem Bodenmodell, das sich beim Stein in dreißig Zentimetern Tiefe geirrt hat.

Diese Schleife betreiben wir schon an vielen Stellen. CI/CD ist beobachtungsgetriebene Auslieferung. Feature-Flags sind beobachtungsgetriebener Rollout. A/B-Tests sind beobachtungsgetriebenes Produktdesign. Canary-Releases sind beobachtungsgetriebene Zuverlässigkeit. Warum nicht dieselbe Schleife in die Art bringen, wie wir testen?

Warum Beobachtung skaliert und Vorhersage nicht

Das Argument ruht auf drei Beobachtungen. Die Zustände nebenläufiger Komponenten multiplizieren sich: die Zustandsraumexplosion, gegen die das Model Checking seit Clarke, Emerson und Sistla 1986 arbeitet. Nach dem Satz von Rice entscheidet kein allgemeines Verfahren, ob ein Test ein System prüft, also hat die Rekursion kein Ende. Und eine Blackbox ist per Definition undurchsichtig: Was man nicht von innen sieht, kann man nicht von innen testen.

Zusammen:

  • Testen durch Vorhersage wächst mit der möglichen Komplexität des Systems: sich aufschaukelnd, nie vollständig geprüft, blind für Blackboxes.
  • Testen durch Beobachtung wächst mit seiner verwirklichten Komplexität: den Pfaden, die tatsächlich laufen, einschließlich dessen, wie sich Abhängigkeiten wirklich verhalten. Echte Lasten sind stark ungleich verteilt: Wenige Pfade tragen den Großteil des Verkehrs.

KI ändert die Testphilosophie, nicht die Mathematik

KI kann Tests schneller erzeugen als Menschen. Ein Sprachmodell liest eine Funktion und erzeugt in Sekunden Dutzende Testfälle. Das klingt, als löse es das Problem des Zustandsraums.

Tut es nicht. Ein Modell erzeugt Tests aus Mustern: was so aussieht, als müsste es getestet werden, gemessen an den Tests, die es gesehen hat. Das ist immer noch Vorhersage, nur schneller. Der kombinatorischen Wand ist egal, wie schnell Sie Stichproben ziehen: Wenige Tausendstel Prozent, in einer Sekunde gezogen, bleiben wenige Tausendstel Prozent.

Wirklich hilft KI innerhalb der Schleife:

  • Beobachten: KI kann Traces lesen, Logs korrelieren und Anomalien über Services hinweg schneller erkennen, als ein Mensch es kann.
  • Regressionstests schreiben: Zu einem beobachteten Fehler kann KI den Test in Sekunden entwerfen.
  • Muster erkennen: KI kann Fehlerarten erkennen, die über Vorfälle hinweg wiederkehren, und strukturelle Korrekturen vorschlagen.

KI beschleunigt die Beobachtungsschleife. Vorhersage macht sie für komplexe Systeme nicht tragfähig.

Kochbuch: ODT in der Praxis

So können Sie heute anfangen: kein Neubau, keine Migration, fünf Schritte.

1. Wählen Sie einen Observability-Stack

Sie brauchen drei Dinge: Metriken, Logs und Traces. Grafana mit Prometheus, Loki und Tempo ist ein verbreiteter Open-Source-Stack; Datadog, Honeycomb oder jedes APM-Werkzeug funktionieren ebenso. Auf das Werkzeug kommt es nicht an. Es kommt darauf an, dass Sie sehen, was Ihr System tut, während es läuft.

Haben Sie noch nichts, beginnen Sie mit einem gehosteten kostenlosen Angebot. Einen Service instrumentieren Sie an einem Nachmittag.

2. Instrumentieren Sie einen kritischen Ablauf

Instrumentieren Sie nicht alles. Wählen Sie den Ablauf, der am meisten zählt: Checkout, Onboarding, die API, die Ihr größter Kunde aufruft. Ergänzen Sie strukturierte Logs an Entscheidungspunkten, Traces über Service-Grenzen hinweg und ein Dashboard, das den Zustand des Ablaufs zeigt.

Läuft die Anwendung schon, ergänzen Sie den bestehenden Ablauf. Die automatische Instrumentierung von OpenTelemetry bringt Sie ein gutes Stück weit, ohne die Geschäftslogik anzufassen.

3. Bauen Sie ein Agenten-Harness

Das ist der Schritt, der am meisten verändert. Statt Testfälle von Hand zu schreiben, lassen Sie einen KI-Agenten Ihre Anwendung so erkunden, wie es ein echter Nutzer täte.

Richten Sie eine Pipeline ein, die ein Sprachmodell mit Playwright ausführt. Geben Sie dem Agenten eine Persona, kein Testskript. Eine Persona hat Absicht, Kontext und Gefühl:

Sie sind ein neuer Nutzer und haben sich gerade registriert. Sie wollen ein Produkt kaufen, sind aber ungeduldig: Dauert etwas länger als ein paar Sekunden, klicken Sie weg und versuchen einen anderen Weg. Anleitungen lesen Sie nicht.
Sie sind ein erfahrener Power-User. Sie kennen jedes Tastenkürzel. Sie öffnen mehrere Tabs. Sie arbeiten schnell und erwarten, dass das System mithält.
Sie sind frustriert. Ihre letzte Bestellung war falsch. Sie suchen den Support, finden ihn aber nicht, also klicken Sie auf alles.

Jede Persona erkundet eine andere Region des Zustandsraums. Die ungeduldige findet Timeouts. Der Power-User findet Race Conditions. Die frustrierte findet kaputte Navigation. Eine Suite, geschrieben von jemandem, der weiß, wo jeder Knopf ist, findet davon selten etwas.

4. Verknüpfen Sie Agenten-Sitzungen mit Traces

Hier zahlt sich der Observability-Stack aus. Jede Playwright-Sitzung bekommt eine Trace-ID, und die ID wandert durch Ihre Services. Geht beim Klicken des Agenten etwas schief, etwa ein 500er, ein Timeout oder eine Seite, die hängt, sehen Sie, was im System passiert ist: welcher Service ausfiel, welche Abfrage langsam war, welche Retry-Schleife das Problem verstärkte.

Nehmen Sie die Agenten-Sitzungen selbst in den Stack auf. Dann zeigt ein Dashboard: diese Persona, dieser Ablauf, dieser Fehler, dieser Trace.

5. Schreiben Sie Regressionstests nur für das, was kaputtging

Findet der Agent einen echten Fehler, keine wackelige Assertion und keinen vorhergesagten Grenzfall, sondern etwas, das im laufenden System tatsächlich kaputtging, schreiben Sie einen Regressionstest. Er schlägt vor der Korrektur fehl; die Korrektur lässt ihn bestehen. Der Test kommt als Fossil in die Suite: als Beleg, dass dieser Fehler passiert ist, damit er nicht unbemerkt zurückkehrt.

Ihre Suite wächst aus der Realität, nicht aus der Vorstellung. Jeder Test hat eine Geschichte; keiner ist spekulativ.

Das ist die ODT-Schleife, konkret gemacht. Der Agent sagt nicht vorher, was schiefgehen könnte; er erzeugt realistischen Verkehr, den Ihr System verkraften muss. Der Observability-Stack beobachtet, was tatsächlich passiert, und Sie schreiben Tests nur für das, was tatsächlich fehlschlägt. Das System lehrt Sie, was zu testen ist.

Der Rhythmus, fortgesetzt

Der erste Artikel hat dafür argumentiert, dass jedes wachsende System strukturelle Unordnung ansammelt und dass die Antwort nicht darin liegt, sie zu verhindern, sondern einen Rhythmus zu haben, sie aufzulösen: den Refactoring-Zyklus.

Dieser Artikel wendet dieselbe Idee auf Korrektheit an. Sie können nicht jeden Fehler eines komplexen Systems vorhersagen. Die Antwort ist nicht, härter zu testen, sondern besser zu beobachten und sich vom System zeigen zu lassen, was zu testen ist.

Vorhersage setzt voraus, dass Sie die Antwort kennen, bevor die Frage gestellt wird. Beobachtung setzt voraus, dass Sie sie nicht kennen, und baut auf, was es braucht, um schnell zu lernen, wenn die Frage kommt.

Der Entropiezyklus atmet. Beobachtungsgetriebenes Testen hört zu.

Beides sind Rhythmen, die ein System lebendig halten.

Die Grundlagen: Shannon (1948), Rice (1953), Clarke, Emerson und Sistla (1986). Der Ansatz baut auf Charity Majors' Observability-Driven Development, auf Netflix' Testen in Produktion und auf den Prinzipien des Chaos Engineering auf und schreibt die Rückkopplungsschleife als Algorithmus statt als Haltung auf. Die 19 % stammen aus einem produktiven Microservice-System, in dem wir diesen Anteil generierter Tests als oberflächlich beurteilt haben: Sie erhöhten die Abdeckung, ohne echte Fehler zu finden.

Soll Ihr System Ihnen zeigen, was zu testen ist?

Wir helfen Teams, die Beobachtungsschleife aufzubauen: Instrumentierung, Agenten-Harnesses und die Gewohnheit, das zu testen, was kaputtging. Vereinbaren Sie gern einen Termin mit uns.

Bleiben Sie auf dem Laufenden

Gelegentliche Notizen zu Agenten, Software und dazu, wie wir bei Aquiilo entwickeln. Kein Spam, jederzeit abbestellbar.

Mit dem Abonnieren stimmen Sie zu, dass wir Ihnen unseren Newsletter senden dürfen. Sie können sich mit einem Klick aus jeder E-Mail abmelden.

War das hilfreich?