Theorie 07 · Nachweis
„Es läuft“ ist
kein Testergebnis
Tests zeigen, ob sich ein System unter definierten Bedingungen erwartungsgemäß verhält. Observability hilft, unbekannte Fehler zu untersuchen. Beides ergänzt sich – und beides braucht konkrete Identitäten, Messpunkte und erwartete Resultate.
Ein Test braucht Ausgangslage, Aktion und Erwartung
„Ich habe auf den Button geklickt und nichts Rotes gesehen“ ist eine Beobachtung, aber kein belastbarer Test. Ein reproduzierbarer Test beschreibt zuerst den Startzustand, führt eine klar definierte Aktion aus und prüft anschließend sichtbare Wirkungen. Bei Integrationen gehören dazu nicht nur HTTP-Statuscodes, sondern auch Zielobjekte, Queue-Zustände, Duplikate und Protokolle.
Ein guter Test scheitert verständlich. Er sagt nicht nur „false“, sondern beispielsweise: „Erwartet Jobstatus completed nach höchstens 10 Sekunden; erhalten retrying mit Versuch 3 und Fehlerklasse upstream_unavailable.“ Dadurch wird der Test zugleich zu einer ausführbaren Spezifikation.
Viele schnelle Tests unten, wenige realistische oben
Unit-Tests prüfen kleine Logikeinheiten ohne echte Nachbarsysteme. Contract-Tests prüfen, ob Request und Response einem vereinbarten Schema und Verhalten entsprechen. Integrationstests starten echte Komponenten wie HTTP-Server, SQLite oder Orthanc. End-to-End-Tests gehen durch das öffentlich sichtbare Gateway. Resilienztests verändern gezielt die Umgebung.
| Ebene | Beispiel im Spital-Lab | Typische Lücke |
|---|---|---|
| Unit | Event ohne StudyInstanceUID wird abgelehnt | Kein echter Netzwerk-/Serialisierungseffekt |
| Contract | POST /api/spital/jobs liefert 202 und Job-URL | Consumer kann semantisch trotzdem falsch handeln |
| Integration | Worker liest internen Orthanc-Status | Reverse Proxy und TLS fehlen |
| End-to-End | Öffentliche Domain → Gateway → API → Job | Teurer und empfindlicher, Diagnose schwieriger |
| Resilienz | Orthanc-Ausfall erzeugt Retry und Recovery | Störung kann unrealistisch simuliert sein |
Syntax, Semantik und Verhalten testen
Ein JSON-Schema kann Typen, Pflichtfelder und erlaubte Werte prüfen. Das ist notwendig, aber nicht ausreichend. Eine syntaktisch gültige Modalität XX kann fachlich unzulässig sein. Ein korrektes Datum kann in der Vergangenheit liegen. Contract-Tests verbinden deshalb Struktur mit beispielhaftem Verhalten: gültige Anfrage, fehlendes Feld, falscher Typ, unbekannter Wert, zu großer Body und unerlaubte Methode.
Auch Fehler sind Teil des Vertrags. Eine API sollte vorhersehbare Statuscodes und maschinenlesbare Fehler liefern. 400 steht für eine ungültige Anfrage, 404 für eine nicht gefundene Ressource, 409 für einen Konflikt und 429 für ein Limit. Ein pauschales 500 für jeden Fall erschwert Clients, Betrieb und Unterricht.
# Happy Path
curl -i -X POST https://teko.algorithma.app/api/spital/jobs \
-H 'Content-Type: application/json' \
-d '{"studyInstanceUid":"1.2.826.0.1.3680043.10.543.42",
"accessionNumber":"DEMO-0042","modality":"CT"}'
# Negativtest: Patientendaten werden nicht akzeptiert
curl -i -X POST https://teko.algorithma.app/api/spital/jobs \
-H 'Content-Type: application/json' \
-d '{"patientName":"Nicht erlaubt"}'
2xx zurückkommt. Die Response enthält aber keine Job-ID, der Job wird nie verarbeitet, und dieselbe Anfrage erzeugt bei Wiederholung drei Datensätze. Der Vertrag war zu schwach.Asynchrone Tests warten auf Zustände, nicht auf Glück
Ein asynchroner POST antwortet häufig mit 202 Accepted. Das bedeutet: Die Anfrage wurde angenommen, nicht abgeschlossen. Die Response liefert eine Job-ID oder einen Location-Header. Der Test fragt den Status wiederholt ab, bis ein Endzustand erreicht ist oder ein festes Zeitlimit abläuft.
Ein statisches sleep 10 macht Tests langsam und unzuverlässig. Besser ist Polling mit kurzem Intervall, globalem Timeout und Ausgabe des letzten Zustands. Der Test muss mögliche Übergänge kennen, etwa queued → processing → completed oder queued → retrying → dead-letter. Unerlaubte Übergänge sind selbst Fehler.
Idempotenz wird mit zwei gleichen Requests geprüft. Beide dürfen dieselbe Jobrepräsentation liefern oder der zweite darf einen klaren Konflikt melden; entscheidend ist, dass nur eine fachliche Zielwirkung entsteht. Zusätzlich wird ein gleicher fachlicher Schlüssel mit einer neuen Event-ID getestet: Je nach Vertrag kann das ein neues Ereignis, aber weiterhin dieselbe Studie sein.
202 allein beweist nur Annahme.Fehler werden absichtlich erzeugt
Resilienz lässt sich nicht aus dem Quellcode ablesen. Das Zielsystem wird gestoppt oder ein kontrollierter Fehlerpfad aktiviert. Dann prüft der Test Versuchszähler, Backoff, Status, Logkontext und Wiederaufnahme. Nach dem Neustart muss der Job ohne manuellen Datenverlust fertig werden.
Technische und fachliche Fehler werden getrennt: Ein unerreichbares Orthanc ist wahrscheinlich temporär und retrybar. Eine ungültige StudyInstanceUID oder nicht erlaubte Modalität wird durch zehn Wiederholungen nicht gültig. Sie gehört in eine Quarantäne/DLQ mit erklärbarem Grund. Ein Replay darf erst nach Korrektur oder bewusster Freigabe erfolgen.
- Timeout nach Wirkung: Ziel hat gespeichert, Antwort ging verloren – Deduplikation prüfen.
- Neustart während Verarbeitung: Job bleibt dauerhaft nachvollziehbar und wird nicht vergessen.
- Queue-Rückstau: Annahme wird begrenzt; Speicher wächst nicht unendlich.
- Poison Message: Ein ungültiges Event blockiert nicht alle folgenden.
- Doppelzustellung: Ein fachlicher Effekt bleibt einmalig.
Logs erzählen Ereignisse, Metriken zeigen Muster, Traces verbinden Wege
Monitoring beantwortet bekannte Fragen mit vorbereiteten Signalen: Ist die Fehlerrate erhöht? Wächst die Queue? Observability zielt darauf, aus den erzeugten Signalen auch unbekannte Fragen untersuchen zu können. Drei klassische Signale sind Logs, Metriken und Traces.
Strukturierte Logs enthalten Zeitpunkt, Level, Dienst, Eventname, Job-ID, Correlation-ID, Versuch und Fehlerklasse. Metriken zählen Jobs, Fehler, Retry, DLQ und Laufzeiten. Ein Trace zeigt zusammengehörige Spans über Gateway, API, Worker und Orthanc. Für das kleine Labor übernimmt die Correlation-ID einen Teil dieser Verbindung; sie wird in Response und Jobstatus sichtbar.
| Signal | Stärke | Beispielfrage |
|---|---|---|
| Log | Detail eines Ereignisses | Warum scheiterte corr-imaging-0042 im Versuch 2? |
| Metrik | Aggregation über Zeit | Wie viele Jobs gingen in den letzten 15 Minuten in Retry? |
| Trace | Weg und Dauer über Dienste | Welcher Hop verbrauchte das Latenzbudget? |
Mehr Daten sind nicht automatisch besser. Patientennamen, komplette Payloads oder Secrets gehören nicht in Logs. Hohe Kardinalität wie eine eigene Metrikserie pro Study UID kann Metriksysteme überlasten. Correlation-IDs passen in Logs und Traces, nicht zwingend als Metriklabel.
Ein Testprotokoll macht Ergebnisse prüfbar
Für Unterricht und Projektprüfung reicht ein Screenshot ohne Kontext nicht. Ein kurzes Protokoll nennt Version/Commit, Umgebung, Zeitpunkt, Befehl, erwartetes Ergebnis, tatsächliches Ergebnis und Beleg. Bei Abweichung folgt eine Interpretation, kein nachträgliches Umschreiben der Erwartung.
Test-ID: E2E-ASYNC-03
Umgebung: lokal, Compose Release 2026.08
Startzustand: alle Healthchecks grün, keine Job-ID vorhanden
Aktion: POST synthetische CT-Studie DEMO-0042
Erwartung: 202; Endzustand completed < 10 s; attemptCount = 1
Ergebnis: erfüllt; Job job-…; correlationId corr-…
Beleg: curl-Output + API-Logzeilen + Orthanc-Health
Kontrollfragen
- Warum beweist ein
202nicht die erfolgreiche Verarbeitung? - Welche Fehlerfälle gehören in einen Contract-Test?
- Wie testest du Idempotenz fachlich statt nur technisch?
- Wann hilft eine Correlation-ID, wann reicht sie nicht?
- Welche Informationen müssen aus Gesundheitsdaten-Logs entfernt bleiben?
Der Stack besitzt automatisierte API-, Async-, Fehler- und Sicherheits-Smoke-Tests. Die genauen lokalen und öffentlichen Befehle stehen in der Docker-Anleitung.