Im entsprechenden Kapitel behandelt

TDD-First Architecture – Gesamtwerk

Der vollständige testgetriebene Entwicklungsweg von der ersten HTTP-Route bis zu Qualität, Mutation und Architektur-Fitness.

Java 21TDDArchUnitMutation

Das vollständige TDD-First-Werk

Alle fachlichen Slices, technischen Adapter und Abschlussrefactorings sind in dieser einen offlinefähigen Datei zusammengeführt.

Projektportal
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

  1. Ein gemeinsames Akzeptanzszenario auswählen und in einen ausführbaren äußeren Test übersetzen.
  2. Den Test rot ausführen und den fachlich relevanten Fehler beobachten.
  3. Einen schmalen Use-Case-Test formulieren, der genau den nächsten notwendigen Entwurfsschritt fordert.
  4. Nur die kleinste Implementierung ergänzen, die den neuen Test grün macht.
  5. Duplikate, unklare Namen und falsche Verantwortlichkeiten unter grünem Testschutz refaktorieren.
  6. Benötigte Ports erst dann einführen, wenn eine echte äußere Abhängigkeit sichtbar wird.
  7. Adapter über Contract Tests an dieselbe Port-Bedeutung binden.
  8. 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 DRAFT wird genau einmal zu PLACED
  • 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
  • OrderPlaced wird 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, PaymentAuthorized entsteht.
  • DECLINEDfachliche Ablehnung, PaymentDeclined, kein technischer Retry.
  • DEFERREDtechnische Ausfälle bleiben nach erlaubten Sofortversuchen bestehen, PaymentAuthorizationDeferred entsteht.

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.
  • InvoiceIssued wird 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

  1. Domain kennt weder Application noch Adapter, API oder technische Bibliotheken.
  2. Application kennt Domain und Ports, aber keine konkreten Adapter.
  3. Technische Adapter dürfen nur nach innen abhängen.
  4. Ports bleiben Schnittstellen oder klar benannte Result Types.
  5. Produktionspakete bleiben zyklusfrei.
  6. Akzeptanztests verwenden kein Mocking der Application-Grenze.
  7. 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

  1. Fachkern und testgetriebene Slices
  2. technische Adapter
  3. 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.

Darstellung

Design
Text
Dichte
⌂ Cockpit