TDD-First Architecture – Gesamtwerk
Der vollständige testgetriebene Entwicklungsweg von der ersten HTTP-Route bis zu Qualität, Mutation und Architektur-Fitness.
Das vollständige TDD-First-Werk
Alle fachlichen Slices, technischen Adapter und Abschlussrefactorings sind in dieser einen offlinefähigen Datei zusammengeführt.
01 · TDD-Strategie
TDD-Strategie
Ziel und Abgrenzung
TDD-First ist in diesem Gesamtprojekt kein eigener Architekturstil. Es ist der verbindliche Entwicklungsweg, über den eine modulare Struktur aus beobachtbarem Verhalten, kleinen Entwurfsentscheidungen und kontinuierlichem Refactoring entsteht. Die externe Fachlichkeit bleibt identisch zur Layered- und späteren DDD-Variante.
legt ausschließlich Arbeitsregeln, Testgrenzen und Nachweise fest. Produktionscode beginnt bewusst erst mit dem Walking Skeleton im entsprechenden Kapitel.
Outside-in als Entwicklungsrichtung
Der Entwicklungsfluss beginnt an einem gemeinsamen Akzeptanzszenario und arbeitet sich nur so weit nach innen vor, wie der nächste grüne Schritt es verlangt. Äußere Verträge bleiben stabil; interne Typen und Module entstehen schrittweise.
Verbindliche Reihenfolge je Use Case
- Ein gemeinsames Akzeptanzszenario auswählen und in einen ausführbaren äußeren Test übersetzen.
- Den Test rot ausführen und den fachlich relevanten Fehler beobachten.
- Einen schmalen Use-Case-Test formulieren, der genau den nächsten notwendigen Entwurfsschritt fordert.
- Nur die kleinste Implementierung ergänzen, die den neuen Test grün macht.
- Duplikate, unklare Namen und falsche Verantwortlichkeiten unter grünem Testschutz refaktorieren.
- Benötigte Ports erst dann einführen, wenn eine echte äußere Abhängigkeit sichtbar wird.
- Adapter über Contract Tests an dieselbe Port-Bedeutung binden.
- Den Zyklus mit Test-, Refactoring- und Entscheidungsnachweis abschließen.
Red – Green – Refactor
Red
- Ein Test beschreibt ausschließlich ein beobachtbares Verhalten.
- Der Fehlschlag muss aus dem erwarteten Grund entstehen.
- Kompilierfehler sind nur für den allerersten Typentwurf zulässig; danach wird ein aussagekräftiger fachlicher Fehlschlag bevorzugt.
- Vor der Implementierung wird der erwartete Fehlertext oder die erwartete Abweichung festgehalten.
Green
- Nur der kleinste fachlich korrekte Schritt wird implementiert.
- Keine vorsorglichen Schnittstellen, Basisklassen oder generischen Frameworks.
- Neue Abstraktionen benötigen mindestens einen konkreten Änderungsdruck oder eine reale technische Grenze.
- Harte Kodierung ist nur als kurzfristiger Schritt erlaubt, wenn der unmittelbar folgende Test sie sichtbar aufbricht.
Refactor
- Verhalten bleibt unverändert und alle Tests bleiben grün.
- Refactoring kann Namen, Verantwortlichkeiten, Testdaten, Duplikate, Objektgrenzen oder Modulgrenzen betreffen.
- Größere Refactorings werden mit Vorher-/Nachher-Ausschnitt, Auslöser und Sicherheitsnetz dokumentiert.
- Architektur entsteht schrittweise; sie wird nicht aus Testklassen herauskopiert.
Testportfolio
Akzeptanztests
Prüfen die gemeinsamen Use Cases von außen. Sie verwenden die verbindlichen Verträge und bleiben frei von internen Klassennamen. Zunächst dürfen sie gegen einen In-Memory-Adapter laufen; ab wird derselbe Vertrag gegen reale Adapter geprüft.
Use-Case-Tests
Prüfen Orchestrierung, fachliche Ergebnisse, Fehler und ausgehende Interaktionen. Sie verwenden Fakes bevorzugt vor Mock-Kaskaden. Ein Use-Case-Test darf technische Details nur an expliziten Ports beobachten.
Domain-Tests
Prüfen Invarianten und Zustandsübergänge ohne Framework, Container oder Mocking. Entities, Value Objects und Collections werden als reale Objekte verwendet.
Port-Contract-Tests
Definieren dieselbe Erwartung für In-Memory-, Datenbank-, Messaging- und HTTP-Adapter. Jeder Adapter muss dieselbe Contract-Suite erfüllen.
Adapter-Integrationstests
Prüfen Serialisierung, Datenbankmapping, Transaktionen, Sperren, Broker-Header und technische Fehlerbehandlung. Sie ersetzen keine fachlichen Tests.
Architekturtests
Konservieren die tatsächlich entstandenen Modulgrenzen. Eine Regel wird erst hinzugefügt, wenn eine Grenze bewusst eingeführt wurde.
Regeln für Test Doubles
- Dummy: nur zum Füllen eines Parameters ohne Nutzung.
- Stub: liefert eine gezielte Antwort und prüft keine Interaktion.
- Fake: besitzt eine vereinfachte, aber echte Semantik und ist die bevorzugte Wahl für Repositories und Uhren in frühen Entwicklungsstufen.
- Spy: zeichnet relevante Interaktionen auf, ohne das gesamte Verhalten vorzugeben.
- Mock: nur für eine konkrete ausgehende Nachricht oder technische Grenze; keine Kaskaden und keine Spiegelung interner Aufrufreihenfolgen.
Nicht gemockt werden Value Objects, Entities, Collections, fachliche Policies oder der zu testende Use Case.
Zeit, IDs und Zufall
Zeit, Kennungserzeugung und Zufall werden nur bei echtem Determinismusbedarf über kleine Ports kontrolliert. Testdaten enthalten feste, lesbare Werte. Produktionscode liest keine globale Uhr direkt, sobald Zeit fachlich relevant ist.
Testdaten
- Test Data Builder für häufig variierte komplexe Objekte.
- Explizite Factory-Methoden für kleine, fachlich eindeutige Beispiele.
- Object Mother nur für wenige stabile Standardszenarien.
- Keine zentrale Mega-Fixture und keine versteckten Defaultregeln.
- Jeder Test überschreibt nur die für sein Verhalten relevanten Werte.
Benennung und Struktur
Testnamen folgen dem Muster aktion_bedingung_erwartung, zum Beispiel placeOrder_inactiveCustomer_rejectsOrder. Testkörper verwenden Arrange, Act und Assert beziehungsweise Given, When und Then. Pro Test wird ein primärer fachlicher Grund geprüft; mehrere Assertions sind erlaubt, wenn sie dasselbe Ergebnis beschreiben.
Definition of Done für einen TDD-Mikroschritt
- Der neue Test war nachweislich rot.
- Der Fehlschlag entstand aus dem erwarteten Grund.
- Die kleinste fachlich korrekte Implementierung ist grün.
- Bestehende Tests bleiben grün.
- Relevantes Refactoring ist abgeschlossen oder bewusst als Schuld dokumentiert.
- Neue Ports besitzen eine klare fachliche oder technische Begründung.
- Neue Adapter erfüllen den zugehörigen Contract Test.
- Entscheidungen und Refactorings wurden im fortlaufenden Verlauf oben ergänzt.
- Sichtbare Dokumentation enthält keine internen Arbeitsdateien oder Quellverweise.
Verbotene Abkürzungen
- Produktionscode vor dem ersten roten Test.
- Testen privater Methoden.
- Mocking interner Collaborators nur zur Abdeckung.
- Eine Testklasse pro Produktionsklasse als starre Regel.
- Konstruktion der späteren DDD-Struktur im Voraus.
- Prozentuale Testabdeckung als Ersatz für aussagekräftige Beispiele.
- Große Refactorings ohne grünen Ausgangs- und Endzustand.
- Überschreiben älterer Entscheidungs-, Prüf- oder Refactoring-Einträge.
Feingranulare Entwicklungsruns
Walking Skeleton
Ein äußerer Akzeptanztest, ein minimaler HTTP-Eingang, ein Use Case und ein In-Memory-Ausgang bilden den ersten durchgehenden Pfad.
Customer
Registrierung, Validierung, Identität, Builder und erster Repository-Fake entstehen testgetrieben.
Product Catalog
Preis, Aktivierung und Katalogregeln erweitern die Testdaten- und Value-Object-Strategie.
Place Order
Bestellzustände und Positionen entwickeln sich über kleine Verhaltensschritte; keine Aggregate-Struktur wird vorgegeben.
Inventory Reservation
Atomare Reservierung und Port Contracts machen Nebenläufigkeit und Adaptersemantik sichtbar.
Payment
Ablehnung, technischer Fehler, Retry und Idempotenz treiben Strategy- und Gateway-Grenzen.
Billing und Fulfillment
Prozessfortschritt, Ereignisse und Kompensationen werden als beobachtbares Verhalten entwickelt.
Technische Adapter
PostgreSQL, REST und Messaging erfüllen dieselben Verträge wie die frühen Fakes.
Refactoring und Qualitätsgrenzen
Test-Smells, Mutation Testing, Modulgrenzen und das TDD-Gesamtwerk schließen die Variante ab.
Nachweise je Fachschritt
Jeder TDD-Fachschritt ergänzt oben in den fortlaufenden Dokumenten
- gewähltes Akzeptanzszenario
- beobachteter roter Zustand
- kleinster grüner Schritt
- durchgeführtes Refactoring
- neu entstandene oder verworfene Abstraktion
- ausgeführte Tests und offen gebliebene Laufzeitprüfungen
- fachliche Rückverfolgbarkeit zur gemeinsamen Spezifikation
02 · Walking Skeleton
Walking Skeleton
Ziel
liefert den ersten echten, vollständig durchgehenden Pfad des TDD-First-Projekts. Eine reale HTTP-Anfrage wird angenommen, in einen Use-Case-Befehl übersetzt, fachlich verarbeitet, in einem zustandsbehafteten In-Memory-Repository gespeichert und als HTTP-201-Antwort zurückgegeben.
Durchgehender Weg
POST /api/v1/orders
↓
PlaceOrderHttpHandler
↓
PlaceOrderUseCase
↓
ProductPriceSource + OrderIdGenerator
↓
OrderRepository
↓
HTTP 201 + Location + X-Correlation-ID
Bewusst kleiner fachlicher Umfang
- genau eine Produktposition wird aus dem gemeinsamen Beispiel verarbeitet
- der Preis stammt aus einer In-Memory-Preisquelle
- der Gesamtbetrag wird fachlich berechnet
- die Bestellung wird im Zustand PLACED gespeichert
- Customer-Aktivität, Produktlebenszyklus und vollständige Idempotenz folgen in den dafür vorgesehenen Fachkapiteln
Diese Grenze verhindert, dass bereits die späteren Customer-, Catalog- oder Order-Phasen vorwegnimmt.
Beobachtbarer Vertrag
- Methode: POST
- Pfad: /api/v1/orders
- Erfolg: HTTP 201
- Antwortheader: Location und X-Correlation-ID
- Status: PLACED
- Gesamtbetrag für zwei Stück: 99,80 EUR
- Speicherwirkung: genau eine Bestellung
Struktur
Die erste Modulgrenze ist absichtlich klein. Der Maven-Reaktor enthält genau ein ausführbares Modul. Interne Packages machen Verantwortlichkeiten sichtbar, ohne vorzeitig mehrere Produktionsmodule zu erzwingen.
Starten
./mvnw -pl projects/02-tdd-first/tdd-walking-skeleton -am test
Für den lokalen Lauf kann die Startklasse auf Port 8080 ausgeführt werden. Der Endpunkt ist danach unter http://127.0.0.1:8080/api/v1/orders erreichbar.
03 · Customer
Customer TDD
Ziel
implementiert UC-01 „Kunde registrieren“ vollständig über den gemeinsamen HTTP-Vertrag. Die drei gemeinsamen Akzeptanzszenarien werden ohne Abweichung umgesetzt: erfolgreiche Registrierung, Ablehnung normalisierter Doppeladressen und Ablehnung ungültiger E-Mail-Adressen.
Outside-in-Schnitt
POST /api/v1/customers
↓
RegisterCustomerHttpHandler
↓
RegisterCustomerUseCase
↓
CustomerName + EmailAddress + CustomerId
↓
CustomerRepository + CustomerEventPublisher
↓
HTTP 201 / 400 / 409
Fachliche Regeln
- BR-CUS-001: Die normalisierte E-Mail-Adresse ist eindeutig.
- BR-CUS-002: Ein neuer Kunde wird im Status ACTIVE angelegt.
- BR-CUS-003: Name und E-Mail-Adresse werden vor Speicherung fachlich validiert.
Beobachtbares Verhalten
| Szenario | HTTP | Fehlercode | Speicherwirkung | Ereigniswirkung |
|---|---|---|---|---|
| gültige Registrierung | 201 | 1 Kunde | 1 CustomerRegistered | |
| doppelte normalisierte E-Mail | 409 | CUSTOMER_EMAIL_ALREADY_EXISTS | unverändert | unverändert |
| ungültige E-Mail | 400 | CUSTOMER_DATA_INVALID | unverändert | unverändert |
Entwurfsentwicklung
Der äußere Test verlangte zunächst nur einen erfolgreichen HTTP-201-Pfad. Die Eindeutigkeitsregel führte anschließend zu EmailAddress und CustomerRepository. Die Validierungsregel führte zu CustomerName und einer expliziten fachlichen Fehlerabbildung. Erst danach wurden wiederkehrende Testdaten in einem Test Data Builder zusammengeführt.
Ausführen
./mvnw -pl projects/02-tdd-first/tdd-walking-skeleton -am test
04 · Product Catalog
Product Catalog TDD
Ziel
implementiert UC-02 „Produkt registrieren“ vollständig über den gemeinsamen HTTP-Vertrag. Die drei Akzeptanzszenarien sind der erfolgreiche aktive Katalogeintrag, die Ablehnung einer normalisierten Doppel-SKU und die Ablehnung eines nicht positiven Preises.
Outside-in-Schnitt
POST /api/v1/products
↓
RegisterProductHttpHandler
↓
RegisterProductUseCase
↓
Sku + ProductName + Money + ProductId
↓
ProductRepository + ProductEventPublisher
↓
HTTP 201 / 400 / 409
Fachliche Regeln
- BR-CAT-001: Die normalisierte SKU ist dauerhaft eindeutig.
- BR-CAT-002: Der Verkaufspreis muss größer als null sein.
- BR-CAT-003: Neu registrierte Produkte sind ACTIVE und bestellbar.
Beobachtbares Verhalten
| Szenario | HTTP | Fehlercode | Speicherwirkung | Ereigniswirkung |
|---|---|---|---|---|
| gültige Registrierung | 201 | 1 Produkt | 1 ProductRegistered | |
| doppelte normalisierte SKU | 409 | PRODUCT_SKU_ALREADY_EXISTS | unverändert | unverändert |
| Preis null oder kleiner | 400 | PRODUCT_PRICE_INVALID | unverändert | unverändert |
Fachlicher Codeausschnitt
public static Product register(ProductId id, Sku sku, ProductName name, Money price) {
if (price.amount().signum() <= 0) {
throw new IllegalArgumentException("product price must be greater than zero");
}
return new Product(id, sku, name, price, ProductStatus.ACTIVE);
}
Entwicklungsgrenze
Der Katalog-Slice besitzt absichtlich noch keine Kopplung an den Bestell-Walking-Skeleton. verbindet den testgetriebenen Place OrderUse-Case mit aktiven Kunden, aktiven Produkten und Preis-Snapshots.
Ausführen
./mvnw -pl projects/02-tdd-first/tdd-walking-skeleton -am test
05 · Place Order
Place Order TDD
Ziel
UC-03 verbindet erstmals die zuvor unabhängig entwickelten Customer- und Product-Catalog-Slices. Eine Bestellung darf nur für einen aktiven Kunden und aktive Produkte entstehen. Jede Position übernimmt Produktbezeichnung und Preis als unveränderlichen Snapshot.
Fachliche Ergebnisse
- mindestens eine Position und ausschließlich positive Mengen
- aktiver Kunde als Vorbedingung
- aktive, bekannte Produkte als Vorbedingung
- Zustand
DRAFTwird genau einmal zuPLACED - Preis und Produktbezeichnung werden beim Anlegen eingefroren
- gleicher Idempotenzschlüssel mit gleichem Payload liefert dieselbe OrderId
- gleicher Schlüssel mit anderem Payload liefert
IDEMPOTENCY_CONFLICT OrderPlacedwird bei einer Wiederholung nicht erneut veröffentlicht
Durchgehender Ablauf
POST /api/v1/orders
-> PlaceOrderHttpHandler
-> PlaceOrderUseCase
-> CustomerOrderingSource
-> ProductOrderingSource
-> Order.draft / addLine / place
-> OrderRepository
-> OrderEventPublisher
Entstandene Grenze
Ordering liest weder Customer noch Product direkt. Zwei Anti-Corruption-Adapter übersetzen die bestehenden Repository-Modelle auf isActive beziehungsweise ProductSnapshot. Damit ist erstmals ein konkreter Grund für eigenständige fachliche Slices sichtbar, ohne bereits Maven-Module zu erzwingen.
Fehlervertrag
| Fehler | HTTP | Bedeutung |
|---|---|---|
| ORDER_LINES_INVALID | 400 | Positionen fehlen, Menge ist ungültig oder der Idempotenzschlüssel fehlt |
| IDEMPOTENCY_CONFLICT | 409 | derselbe Schlüssel wurde mit verändertem Payload verwendet |
| CUSTOMER_NOT_ACTIVE | 422 | Kunde ist unbekannt oder nicht aktiv |
| PRODUCT_NOT_ORDERABLE | 422 | Produkt ist unbekannt oder nicht aktiv |
06 · Inventory Reservation
Inventory Reservation TDD
Ziel
UC-04 reserviert Bestand für eine bereits platzierte Bestellung. Die Reservierung ist eine atomare Gesamtentscheidung: Entweder können alle aggregierten Produktmengen reserviert werden oder der Bestand bleibt vollständig unverändert.
Fachliche Ergebnisse
- gleiche Produktpositionen werden vor der Prüfung zusammengeführt
- ausschließlich positive Mengen sind zulässig
- unbekannte Bestandspositionen werden fachlich abgelehnt
- alle Positionen werden vor der ersten Mutation vollständig geprüft
- verfügbare Menge ergibt sich aus
onHand - reserved - identische Wiederholung liefert dieselbe ReservationId und kein zweites Ereignis
- veränderter Payload mit gleichem Schlüssel führt zu
IDEMPOTENCY_CONFLICT - Freigabe stellt alle reservierten Mengen genau einmal wieder her
- konkurrierende Reservierungen können den Bestand nicht überziehen
Durchgehender Ablauf
POST /api/v1/inventory/reservations
-> InventoryReservationHttpHandler
-> ReserveInventoryUseCase
-> InventoryTransactionRunner
-> StockRepository + vollständige Vorprüfung
-> StockItem.reserve
-> InventoryReservationRepository
-> InventoryEventPublisher
Atomare Gesamtentscheidung
Die Anwendung lädt alle benötigten StockItemEntitäten und prüft jede aggregierte Menge. Erst wenn keine Position fehlt und jede Position ausreichend verfügbar ist, beginnt die Mutation. Die gesamte Sequenz läuft innerhalb einer gemeinsamen Transaktionsgrenze. Im In-Memory-Fake wird sie fair serialisiert; ein späterer Datenbankadapter kann dieselbe Port-Semantik mit Sperren und Transaktionen umsetzen.
Freigabe
ReleaseInventoryReservationUseCase lädt den unveränderlichen Reservierungssnapshot, führt jede Menge auf das zugehörige StockItem zurück und setzt den Status von RESERVED auf RELEASED. Eine wiederholte Freigabe bleibt ohne zusätzliche Wirkung.
Fehlervertrag
| Fehler | HTTP | Bedeutung |
|---|---|---|
| INVENTORY_REQUEST_INVALID | 400 | Bestellung, Position, Menge oder Schlüssel ist ungültig |
| IDEMPOTENCY_CONFLICT | 409 | gleicher Schlüssel wurde mit verändertem Reservierungswunsch verwendet |
| INSUFFICIENT_STOCK | 409 | mindestens eine aggregierte Produktmenge ist nicht verfügbar |
| RESERVATION_NOT_FOUND | 404 | Freigabe verweist auf eine unbekannte ReservationId |
| INVENTORY_ITEM_NOT_FOUND | 422 | für ein Produkt existiert keine Bestandsposition |
07 · Payment
Payment TDD
Ziel
UC-05 autorisiert Zahlungen nur für reservierte Bestellungen und exakt passende offene Beträge. Erfolgreiche Wiederholungen dürfen den Provider nicht doppelt belasten.
Fachliche Ergebnisse
AUTHORIZEDProvider bestätigt, Payment wird gespeichert, Bestellung wird markiert,PaymentAuthorizedentsteht.DECLINEDfachliche Ablehnung,PaymentDeclined, kein technischer Retry.DEFERREDtechnische Ausfälle bleiben nach erlaubten Sofortversuchen bestehen,PaymentAuthorizationDeferredentsteht.
Abhängigkeitsgrenzen
Der Use Case kennt nur Repository, Order-View, Gateway, Retry-Strategie, Event-Publisher und Unit of Work. Der Provider wird als Port modelliert.
Idempotenz
PaymentId und Idempotenzschlüssel führen auf dasselbe gespeicherte Ergebnis. Ein SHA-256-Fingerprint verhindert veränderte Wiederholungen. Die In-Memory-Unit-of-Work serialisiert parallele identische Autorisierungen.
Test Doubles
ScriptedPaymentGateway kombiniert Stub und Spy. Erfolgs-, Ablehnungs- und technische Fehlerfolgen werden deterministisch vorgegeben; geprüft wird die fachlich relevante Aufrufzahl, nicht eine fragile interne Methodenreihenfolge.
08 · Billing
Billing TDD
Ziel
UC-06 erzeugt eine Rechnung nur für eine vollständig bezahlte Bestellung. Die Rechnungsnummer wird exakt einmal vergeben, identische Wiederholungen liefern dasselbe Ergebnis und veränderte Wiederholungen werden abgelehnt.
Fachliche Regeln
- Erlaubter Ausgangsstatus ist
PAYMENT_AUTHORIZED. - Rechnungsadresse und positiver Gesamtbetrag sind Pflicht.
- Pro Bestellung existiert höchstens eine Rechnung.
InvoiceIssuedwird erst nach erfolgreicher Speicherung veröffentlicht.- Nach Rechnungserzeugung wechselt die Bestellung auf
READY_FOR_SHIPMENT.
Testgetriebener Schnitt
Der Use Case kennt ausschließlich schmale Ports für Bestelldaten, Speicherung, ID, Rechnungsnummer, Ereignisse und Transaktion. JPA, Spring und Messaging bleiben außerhalb des Fachkerns.
09 · Fulfillment
Fulfillment TDD
Ziel
UC-07 bereitet den Versand erst nach Rechnungserzeugung vor. Erfolgreiche Carrierbuchung erzeugt eine Trackingnummer; permanente Adressablehnung löst eine fachliche Kompensation aus; technische Fehler werden begrenzt wiederholt.
Fachliche Regeln
- Erlaubter Ausgangsstatus ist
READY_FOR_SHIPMENT. - Versandadresse, Versandart und Idempotenzschlüssel sind Pflicht.
- Identische Wiederholung verursacht keine zweite Carrierbuchung.
- Permanente Adressablehnung fordert die Rechnungsstornierung an.
- Nach ausgeschöpften technischen Wiederholungen bleibt ein expliziter
DEFERREDStatus zurück.
Prozessübergang
PAYMENT_AUTHORIZED → GENERATED → READY_FOR_SHIPMENT → READY_FOR_HANDOVER
10 · Technische Adapter
Technische Adapter
PostgreSQL
Die Aggregate werden über explizite Data Mapper und Snapshot-Codecs gespeichert. SQL-Details bleiben außerhalb von Domain und Application Layer. JdbcContext stellt dieselbe Connection innerhalb einer Unit of Work bereit.
REST
Die bereits testgetrieben entstandenen HTTP-Handler bleiben der eingehende Adapter. Ihre Use-Case-Schnittstellen ändern sich durch PostgreSQL und Messaging nicht.
Messaging
Alle Event-Ports werden durch OutboxEventPublisher implementiert. Der Publisher schreibt in tdd_outbox; KafkaOutboxDispatcher liefert später at-least-once an Kafka oder Redpanda aus.
Externe Systeme
HttpPaymentGateway und HttpShippingGateway übersetzen HTTP-Antworten in fachliche Result Types. Netzwerkfehler werden als technische Port-Exceptions behandelt.
11 · Adapter Contract Tests
Adapter Contract Tests
Adapter Contract Tests prüfen nicht die innere Implementierung, sondern das Verhalten des Ports
- gespeicherte Entitäten sind über fachliche Schlüssel wieder auffindbar
- Geldwerte und normalisierte Identitäten bleiben erhalten
- Bestandsänderungen laufen innerhalb einer echten Datenbanktransaktion
- Outbox-Nachrichten werden atomar und transportneutral gespeichert
- Migrationen starten auf einer leeren PostgreSQL-Datenbank
Die Integrationstests verwenden eine reale PostgreSQL-Containerinstanz. Dadurch werden SQL-Dialekt, Constraints, Indizes und Sperrverhalten nicht durch ein In-Memory-Datenbanksurrogat verdeckt.
12 · Testqualität und Smells
Testqualität und Test Smells
Ziel
Die Tests sollen Fachverhalten beschreiben, reproduzierbar laufen und Refactorings ermöglichen. Eine hohe Anzahl an Tests genügt nicht, wenn sie technische Details spiegeln oder durch Zeit, Zufall und Netzwerk unzuverlässig werden.
Erkannte Risiken
| Risiko | Gegenmaßnahme | automatischer Nachweis |
|---|---|---|
| duplizierte Assertions | gemeinsame Assertion-Hilfe | Quellstrukturprüfung |
| duplizierte HTTP-Mechanik | HTTP-Test-Fassade | Quellstrukturprüfung |
| Mocking an der Akzeptanzgrenze | echte In-Memory-Adapter und HTTP-Smokes | Fitness Function |
| Wartezeiten | Latches, deterministische Fakes und virtuelle Threads | Test-Smell-Prüfung |
| Wall-Clock und Zufall | Sequenzgeneratoren und feste Eingaben | Test-Smell-Prüfung |
| fragile Implementierungsassertions | Zustände, Ergebnisse, Ereignisse und externe Verträge prüfen | Regression |
Testpyramide im Abschlussstand
- Domain-Tests schützen Invarianten und Zustandsübergänge.
- Use-Case-Tests prüfen Orchestrierung, Idempotenz, Retry und Kompensation.
- HTTP-Smoke-Tests prüfen die öffentliche Schnittstelle.
- Adapter Contract Tests prüfen PostgreSQL und technische Ports.
- Architekturtests schützen die Abhängigkeitsrichtung.
Refactoring-Regel
Gemeinsame Testhilfen kapseln ausschließlich technische Mechanik. Fachliche Erwartungen bleiben im jeweiligen Szenario sichtbar. Ein universelles Test-Framework mit versteckten Geschäftsannahmen wird bewusst vermieden.
13 · Architektur-Fitness-Functions
Architektur-Fitness-Functions
Verbindliche Regeln
- Domain kennt weder Application noch Adapter, API oder technische Bibliotheken.
- Application kennt Domain und Ports, aber keine konkreten Adapter.
- Technische Adapter dürfen nur nach innen abhängen.
- Ports bleiben Schnittstellen oder klar benannte Result Types.
- Produktionspakete bleiben zyklusfrei.
- Akzeptanztests verwenden kein Mocking der Application-Grenze.
- Testcode verwendet keine Sleeps, Wall-Clock-Zugriffe oder zufälligen Identitäten.
Maven-Gates
<profile>
<id>quality</id>
<properties>
<lineCoverage>0.80</lineCoverage>
<branchCoverage>0.70</branchCoverage>
</properties>
</profile>
Das normale test bleibt schnell. Der vollständige Qualitätslauf wird mit verify und dem Qualitätsprofil ausgeführt. Mutation Testing ist separat, da es bewusst rechenintensiver ist.
Mutation Testing
Die Mutation-Schwelle liegt bei 70 Prozent, die Abdeckungsschwelle bei 75 Prozent. Zusätzlich führt der paketinterne Abschlusscheck zwei deterministische Mutationssonden aus. Damit wird bereits ohne Maven nachgewiesen, dass zentrale Idempotenz- und Bestellregeln von der Regression erkannt werden.
14 · Refactoring und Qualitätsgrenzen
TDD-Refactoring und Qualitätsgrenzen
Entwicklungsweg
Der TDD-First-Entwicklungsweg begann mit einem HTTP-Walking-Skeleton. Danach wurden Customer, Product Catalog, Place Order, Inventory Reservation, Payment, Billing und Fulfillment jeweils über kleine Red–Green–Refactor-Zyklen ergänzt. Erst nach stabilen Ports wurden PostgreSQL, Outbox, Kafka/Redpanda und HTTP-Provider angeschlossen.
Abschlussrefactorings
- wiederkehrende Smoke-Assertions zentralisiert
- HTTP-Testmechanik aus Fachszenarien ausgelagert
- vollständige Regression als ein ausführbarer Einstieg gebündelt
- Qualität als separates Maven-Modul gekapselt
- Architektur- und Test-Suite-Regeln automatisiert
- Coverage- und Mutationsgates als getrennte Profile definiert
- zwei deterministische Mutationssonden ergänzt
Ergebnis
Das Projekt besitzt drei Maven-Module
- Fachkern und testgetriebene Slices
- technische Adapter
- Qualität und Architektur-Fitness
Der Fachkern bleibt Java-21-basiert und frameworkfrei. Technische Bibliotheken befinden sich außerhalb der Domain und Application. Die sichtbare Dokumentation ist vollständig offlinefähig und enthält ein einzelnes zusammengeführtes TDD-Gesamtwerk.
Bekannte Grenzen
- Ein echter PostgreSQL-/Redpanda-Lauf benötigt lokal einen Container-Daemon.
- Der vollständige PIT-Lauf benötigt Maven und die aufgelösten Buildabhängigkeiten.
- Die In-Memory-Smokes ersetzen keine Last-, Chaos- oder Langzeittests.
- Die TDD-Variante ist kein Architekturstandard, sondern dokumentiert den evolutionären Entwicklungsweg.