Von: Dr. Cassian Holt & Jamal Hassan
Java Fleet Systems Consulting, Essen-Rüttenscheid


🔗 Bisher in der Testing-Serie

Prolog: Warum diese Serie anders wird – Cassian & Jamal kündigen ihre Zusammenarbeit an. Theorie trifft Praxis.

Teil 1: Unit Testing Grundlagen (Jamal solo) – Deine ersten Unit-Tests schreiben. AAA-Pattern, AssertJ, und warum Tests deine Versicherung gegen „Es funktionierte gestern noch!“ sind.

Teil 2: Die Test-Pyramide in der Praxis (beide) – Warum 70% Unit, 20% Integration, 10% E2E funktioniert. Cassian erklärt die Mathematik, Jamal zeigt die Spring Boot Umsetzung.

Heute: Teil 3 – Property-Based Testing und TDD. Die kontroverse Diskussion über elegante Theorie vs. frustrierende Praxis.

⚡ Kurze Zusammenfassung – Das Wichtigste in 30 Sekunden

Property-Based Testing testet mathematische Eigenschaften statt einzelner Beispiele – jqwik generiert automatisch tausende Testfälle und findet Edge Cases, die du nie bedacht hättest. Ideal für Algorithmen, Validierung und kritische Business-Logik. TDD (Test-Driven Development) bedeutet Tests ZUERST schreiben, dann Code – führt theoretisch zu besserem Design, ist praktisch aber oft frustrierend und langsam. Cassian zeigt die wissenschaftlichen Vorteile, Jamal die praktischen Herausforderungen. Konsens: Beide Techniken haben ihren Platz, aber sind nicht für jede Situation geeignet. Du lernst heute, WANN du sie einsetzen solltest – und wann nicht.


👥 Eine Warnung vorweg

Cassian: Heute wird es kontrovers.

Jamal: Sehr kontrovers.

Cassian: Ich werde dir erklären, warum TDD und Property-Based Testing die Zukunft der Software-Entwicklung sind.

Jamal: Und ich werde dir sagen, warum sie in der Praxis oft nicht funktionieren.

Cassian: Das klingt pessimistisch.

Jamal: Das klingt realistisch. Ich hab’s versucht, Cassian. Drei Jahre lang. Bei zwei verschiedenen Projekten.

Cassian: Und?

Jamal: Es hat… teilweise funktioniert. Aber nicht so, wie die Bücher es versprechen.


🔬 Teil 1: Property-Based Testing

Was ist Property-Based Testing überhaupt?

Jamal: Okay, bevor wir loslegen – eine wichtige Klarstellung:

Property-Based Testing ist KEINE neue Test-Art!

Es ist eine Test-Technik. Du kannst damit:

  • ✅ Unit-Tests schreiben
  • ✅ Integration-Tests schreiben
  • ✅ Jede Art von Test schreiben

Der Unterschied ist WIE du testest, nicht WAS du testest:

Test-Pyramide (WAS):          Test-Technik (WIE):
┌─────────────┐              ┌──────────────────┐
│   E2E       │              │  Example-Based   │
├─────────────┤              │  "Teste mit      │
│ Integration │              │   Beispielen"    │
├─────────────┤              └──────────────────┘
│   Unit      │                      vs.
└─────────────┘              ┌──────────────────┐
                             │ Property-Based   │
                             │ "Teste mit       │
                             │  Eigenschaften"  │
                             └──────────────────┘

Cassian: Exakt! Du testest immer noch Methoden und Klassen. Aber anders.


Example-Based vs. Property-Based

Jamal: Lass mich das an einem konkreten Beispiel zeigen:

Szenario: Du hast eine OrderCalculator-Klasse

public class OrderCalculator {
    public double applyDiscount(double price, double discountRate) {
        return price * (1 - discountRate);
    }
}

Variante 1: Example-Based UNIT TEST

@Test
void shouldApplyDiscount() {
    OrderCalculator calculator = new OrderCalculator();
    
    // Teste mit EINEM Beispiel
    double result = calculator.applyDiscount(100.0, 0.1);
    
    assertThat(result).isEqualTo(90.0);
}

Was testest du? Die Methode applyDiscount()UNIT TEST
Wie testest du? Mit einem konkreten Beispiel – EXAMPLE-BASED


Variante 2: Property-Based UNIT TEST

@Property
void discountNeverExceedsOriginalPrice(
    @ForAll @Positive double price,
    @ForAll @DoubleRange(min = 0, max = 1) double discountRate) {
    
    OrderCalculator calculator = new OrderCalculator();
    
    // Teste die EIGENSCHAFT über viele generierte Beispiele
    double result = calculator.applyDiscount(price, discountRate);
    
    assertThat(result).isLessThanOrEqualTo(price);
}

Was testest du? Die Methode applyDiscount()UNIT TEST
Wie testest du? Mit einer Eigenschaft über viele Beispiele – PROPERTY-BASED


Cassian: Siehst du den Unterschied?

  • Example-Based: „Für Input X erwarte ich Output Y“
  • Property-Based: „Für ALLE möglichen Inputs gilt Regel Z“

Beide sind Unit-Tests! Sie testen die gleiche Methode. Nur die Technik ist anders.


Was sind „Properties“?

Jamal: Eine Property ist eine allgemeine Regel, die immer gelten soll.

Beispiele für Properties:

// Property 1: Sortierte Liste ist geordnet
@Property
void sortedListIsOrdered(@ForAll List<Integer> list) {
    List<Integer> sorted = sort(list);
    
    // Für JEDE Liste gilt: sorted[i] <= sorted[i+1]
    for (int i = 0; i < sorted.size() - 1; i++) {
        assertThat(sorted.get(i)).isLessThanOrEqualTo(sorted.get(i + 1));
    }
}

// Property 2: Rabatt macht Preis nie negativ
@Property
void discountNeverNegative(@ForAll @Positive double price,
                          @ForAll @DoubleRange(min=0, max=1) double discount) {
    double result = calculateDiscount(price, discount);
    
    // Für ALLE Preise/Rabatte gilt: Ergebnis >= 0
    assertThat(result).isGreaterThanOrEqualTo(0);
}

// Property 3: Valid Task bleibt valid
@Property
void validTaskStaysValid(@ForAll("validTasks") Task task) {
    assumeThat(task.isValid()).isTrue();
    
    task.complete();
    
    // Für ALLE validen Tasks gilt: valid bleibt valid
    assertThat(task.isValid()).isTrue();
}

Cassian: Properties sind mathematische Invarianten. Regeln, die unter allen Umständen gelten.


Warum sollte mich das interessieren?

Jamal: Weil du Edge Cases findest, an die du nie gedacht hättest.

Beispiel aus meinem letzten Projekt:

// Mein Example-Based Test (dachte ich wäre schlau):
@Test
void shouldCalculateDiscount() {
    assertThat(calculateDiscount(100.0, 0.1)).isEqualTo(90.0);
    assertThat(calculateDiscount(50.0, 0.2)).isEqualTo(40.0);
}

Alles grün! Ship it! 🚀

In Production:

ERROR: Discount returned negative price!
Input: price=0.01, discount=1.0
Result: -0.00

Hätte ich Property-Based Testing genutzt:

@Property
void discountNeverNegative(@ForAll @Positive double price,
                          @ForAll @DoubleRange(min=0, max=1) double discount) {
    double result = calculateDiscount(price, discount);
    assertThat(result).isPositive(); // FAIL! Bug gefunden!
}

jqwik hätte automatisch price=0.01, discount=1.0 generiert und den Bug gefunden.

Das ist der Wert.


Property-Based Testing in der Test-Pyramide

Cassian: Wichtig zu verstehen: Property-Based Testing passt auf alle Ebenen!

Test-Pyramide:                Property-Based Beispiele:

┌─────────────┐              
│   E2E       │ → @Property: "User kann immer ausloggen"
├─────────────┤              
│ Integration │ → @Property: "Datenbank speichert immer eindeutige IDs"
├─────────────┤              
│   Unit      │ → @Property: "Sort-Funktion sortiert immer korrekt"
└─────────────┘

Aber: In der Praxis nutzt man es hauptsächlich für Unit-Tests.

Jamal: Warum? Weil Property-Tests viele Iterationen brauchen (100+ Test-Cases). Das ist schnell bei Unit-Tests, aber zu langsam bei Integration/E2E-Tests.


Cassian’s Perspektive: Die mathematische Eleganz

Cassian: Danke, Jamal. Jetzt kann ich die Theorie erklären.

Property-Based Testing stammt aus der funktionalen Programmierung (QuickCheck in Haskell, 1999). Die Idee: Teste nicht Beispiele, teste mathematische Gesetze.

Traditioneller Test (Example-Based):

@Test
void shouldSortList() {
    List<Integer> input = Arrays.asList(3, 1, 2);
    List<Integer> result = sort(input);
    assertThat(result).containsExactly(1, 2, 3);
}

Das Problem: Was ist mit diesen Fällen?

  • Leere Liste: []
  • Ein Element: [42]
  • Duplikate: [1, 1, 1]
  • Negative Zahlen: [-5, -1, 0]
  • Große Listen: 10.000 Elemente
  • Edge Cases: [Integer.MAX_VALUE, Integer.MIN_VALUE]

Du müsstest hunderte Tests schreiben!


Property-Based Testing mit jqwik:

@Property
@DisplayName("Sorted list should always be ordered")
void sortedListIsOrdered(@ForAll List<Integer> randomList) {
    List<Integer> sorted = sort(randomList);
    
    // Property: Jedes Element <= nächstes Element
    for (int i = 0; i < sorted.size() - 1; i++) {
        assertThat(sorted.get(i)).isLessThanOrEqualTo(sorted.get(i + 1));
    }
}

@Property
@DisplayName("Sorted list contains same elements")
void sortPreservesElements(@ForAll List<Integer> randomList) {
    List<Integer> sorted = sort(randomList);
    
    // Property: Keine Elemente verloren oder hinzugefügt
    assertThat(sorted).containsExactlyInAnyOrderElementsOf(randomList);
}

Was passiert hier?

jqwik generiert automatisch:

  • 100 verschiedene Listen
  • Leere Listen, große Listen, Edge Cases
  • Findet Bugs, die du nie bedacht hättest

Das ist elegant. Das ist wissenschaftlich. Das ist—


Jamal’s Reality-Check

Jamal: —übertrieben für 90% der Fälle.

Cassian: Entschuldigung?

Jamal: Hör mir zu. Ich habe Property-Based Testing ausprobiert. In zwei Projekten. Hier ist, was passiert ist:

Projekt 1: Order-Management-System

Ich wollte cool sein. Ich habe Properties für ALLES geschrieben:

@Property
void orderTotalIsAlwaysPositive(@ForAll("orders") Order order) {
    assertThat(order.calculateTotal()).isPositive();
}

@Property
void orderItemsNeverNull(@ForAll("orders") Order order) {
    assertThat(order.getItems()).isNotNull();
}

@Property
void orderStatusTransitionsValid(@ForAll("orders") Order order,
                                  @ForAll OrderStatus newStatus) {
    // 50 Zeilen komplexe State-Machine-Logic...
}

Das Ergebnis:

  • ✅ Ja, ich fand 3 Edge Cases, die ich sonst übersehen hätte
  • ❌ Die Tests liefen 15 Minuten (statt 30 Sekunden)
  • ❌ Jeder im Team verstand die Properties nicht
  • ❌ Beim Refactoring brachen alle Properties
  • ❌ Niemand wollte sie warten

Nach 3 Monaten: Wir haben die Properties wieder entfernt.


Cassian: Das klingt nach schlechter Implementation, nicht nach einem Problem mit Property-Based Testing.

Jamal: Vielleicht. Aber es zeigt: Property-Based Testing ist nicht für jeden Code geeignet.


Wann Property-Based Testing SINNVOLL ist

Beide einig:

Algorithmen & Datenstrukturen

@Property
void sortIsIdempotent(@ForAll List<Integer> list) {
    List<Integer> once = sort(list);
    List<Integer> twice = sort(once);
    assertThat(twice).isEqualTo(once); // Sortieren 2x = 1x
}

Validierungs-Logik

@Property
void emailValidatorConsistent(@ForAll("emails") String email) {
    boolean first = validator.isValid(email);
    boolean second = validator.isValid(email);
    assertThat(first).isEqualTo(second); // Immer gleiches Ergebnis
}

Parser & Serializer

@Property
void parseSerializeRoundtrip(@ForAll("validJson") String json) {
    Object parsed = parser.parse(json);
    String serialized = serializer.serialize(parsed);
    assertThat(parser.parse(serialized)).isEqualTo(parsed);
}

Mathematische Funktionen

@Property
void discountNeverExceedsOriginalPrice(@ForAll @Positive double price,
                                        @DoubleRange(min = 0, max = 1) double discount) {
    double finalPrice = calculateDiscount(price, discount);
    assertThat(finalPrice).isLessThanOrEqualTo(price);
}

Wann Property-Based Testing OVERKILL ist

Jamal’s Guidelines:

CRUD-Operations

// Overkill!
@Property
void saveUserAlwaysReturnsId(@ForAll("users") User user) {
    User saved = repository.save(user);
    assertThat(saved.getId()).isNotNull();
}

// Besser: Simpler Example-Test
@Test
void shouldSaveUser() {
    User user = new User("test@example.com");
    User saved = repository.save(user);
    assertThat(saved.getId()).isNotNull();
}

Simple Business-Logik

// Overkill!
@Property
void orderCanBeCreated(@ForAll String title) {
    Order order = new Order(title);
    assertThat(order.getTitle()).isEqualTo(title);
}

// Besser: 3 Example-Tests reichen
@Test
void shouldCreateOrder() { /* ... */ }
@Test
void shouldRejectEmptyTitle() { /* ... */ }
@Test
void shouldRejectTooLongTitle() { /* ... */ }

Framework-Integration

// Overkill und langsam!
@Property
@SpringBootTest
void controllerReturnsCorrectStatus(@ForAll String endpoint) {
    // Das ist keine gute Idee...
}

Cassian: Fair enough. Property-Based Testing ist ein Werkzeug für spezifische Situationen.

Jamal: Genau. Und jetzt zu TDD…

Cassian: Oh, hier wird es interessant.


🧬 Teil 2: Test-Driven Development (TDD)

Cassian’s Perspektive: Die wissenschaftliche Methode

Cassian: TDD ist nicht nur eine Technik. Es ist die wissenschaftliche Methode angewandt auf Code.

Der TDD-Zyklus:

RED → GREEN → REFACTOR
 ↓      ↓        ↓
Test   Code    Better Code
fails  works   elegant

Beispiel: Nova’s TaskApp

Phase 1: RED – Test schreiben (versagt)

@Test
void shouldValidateTaskTitle() {
    Task task = new Task("ab"); // Nur 2 Zeichen
    
    assertThat(task.isValid()).isFalse(); // Minimum 3 Zeichen
}

Dieser Test schlägt fehl. Gut! Das ist der Plan.


Phase 2: GREEN – Minimale Implementation

public class Task {
    private String title;
    
    public Task(String title) {
        this.title = title;
    }
    
    public boolean isValid() {
        return title != null && title.length() >= 3;
    }
}

Test ist grün. Code funktioniert.


Phase 3: REFACTOR – Elegant machen

public class Task {
    private static final int MIN_TITLE_LENGTH = 3;
    private String title;
    
    public Task(String title) {
        this.title = title;
    }
    
    public boolean isValid() {
        if (title == null) {
            return false;
        }
        
        String trimmed = title.trim();
        return trimmed.length() >= MIN_TITLE_LENGTH;
    }
}

Cassian: Siehst du die Eleganz? Der Test definiert das gewünschte Verhalten. Der Code entwickelt sich schrittweise. Refactoring ist sicher, weil Tests als Safety-Net fungieren.

Das ist—


Jamal’s Reality-Check

Jamal: —in der Theorie fantastisch. In der Praxis frustrierend.

Cassian: Du wieder.

Jamal: Lass mich meine Geschichte erzählen.


Meine TDD-Reise: Die ehrliche Version

Woche 1: Euphorie

@Test
void shouldCalculateTotal() {
    Order order = new Order();
    order.addItem(new Item("Book", 10.0));
    
    assertThat(order.getTotal()).isEqualTo(10.0);
}

„Wow, das ist ja einfach! Warum macht das nicht jeder?“


Woche 2-3: Frustration

@Test
void shouldProcessPayment() {
    // Wie teste ich das?
    // Das braucht: PaymentService, Database, Email-Service...
    // Ich schreibe schon 2 Stunden an diesem Test!
    // Der Production-Code wäre in 20 Minuten fertig!
}

„Das ist absurd. Ich schreibe mehr Test-Code als Production-Code!“


Woche 4: Rebellion

// Schneller Fix ohne Test
public void updateOrder(String orderId, String newTitle) {
    Order order = repository.findById(orderId);
    order.setTitle(newTitle);
    repository.save(order);
    // Kein Test. Funktioniert doch!
}

„Nur dieser eine Fix. Diesmal ohne Test. Ist ja simpel…“


Woche 6: Production-Bug

ERROR: NullPointerException in updateOrder()
- Order with ID '123' not found
- Production down for 2 hours
- Customer angry

„Fuck. Hätte ich mal einen Test geschrieben…“


Monat 3: Einsicht

@Test
void shouldHandleMissingOrder() {
    assertThatThrownBy(() -> orderService.updateOrder("999", "New"))
        .isInstanceOf(OrderNotFoundException.class);
}

TDD hätte diesen Bug verhindert. Aber der Weg war hart.


Die Wahrheit über TDD

Jamal: Hier ist, was niemand dir sagt:

1. TDD ist LANGSAM am Anfang

  • Erste 2 Monate: 30-50% langsamer
  • Du kämpfst mit Mocking, Setup, Test-Design
  • Es fühlt sich unproduktiv an

Aber: Nach 3-4 Monaten wird es schneller. Du debuggst weniger, refactorst sicherer.


2. TDD funktioniert nicht für ALLES

Wo TDD schwer ist:

UI-Code

// Wie teste ich das mit TDD?
button.setOnClickListener(v -> {
    Toast.makeText(context, "Clicked", Toast.LENGTH_SHORT).show();
});

Legacy-Code ohne Tests

// 500 Zeilen Methode mit 15 Dependencies
// TDD unmöglich ohne komplettes Refactoring
public void processLegacyOrder() {
    // ... spaghetti code ...
}

Prototyping / Spikes

// Wenn du noch nicht weißt, WAS du bauen willst
// TDD ist kontraproduktiv

Glue-Code

// Simple Framework-Integration
@GetMapping("/orders")
public List<Order> getOrders() {
    return orderService.findAll();
}
// TDD hier ist Zeitverschwendung

3. TDD braucht Team-Buy-In

Cassian: Das ist ein wichtiger Punkt.

Jamal: Wenn nur einer im Team TDD macht, scheitert es. Ich hab’s erlebt:

  • Kollegen schreiben Code ohne Tests
  • Ich schreibe Tests
  • Code-Merge: Tests brechen
  • „Jamal, deine Tests sind kaputt!“
  • „Nein, euer Code ist nicht testbar!“
  • Team-Konflikt

Nach 6 Monaten: Entweder macht es das ganze Team, oder es funktioniert nicht.


Wann TDD SINNVOLL ist

Beide einig:

Neue Features mit klaren Requirements

// Anforderung ist klar: User-Registrierung
@Test
void shouldRegisterUser() {
    User user = userService.register("test@example.com", "password");
    assertThat(user.getId()).isNotNull();
}

Bug-Fixes

// Test reproduziert Bug
@Test
void shouldHandleNullEmail() {
    assertThatThrownBy(() -> userService.register(null, "password"))
        .isInstanceOf(ValidationException.class);
}
// Dann Bug fixen

Algorithmen & komplexe Logik

@Test
void shouldCalculateDiscount() {
    // Klare Mathematik = perfekt für TDD
}

Code mit vielen Edge Cases

@Test
void shouldValidatePassword() {
    // Viele Regeln = viele Tests = TDD hilft
}

Wann TDD SCHWER ist

Jamal’s ehrliche Liste:

⚠️ Legacy-Code refactoring

  • Erst Charakterisierungstests, dann Refactoring, DANN TDD
  • TDD von Anfang an = unmöglich

⚠️ Unklare Requirements

  • „Der Kunde weiß noch nicht, was er will“
  • TDD setzt voraus, dass du das Ziel kennst

⚠️ Prototyping Phase

  • „Ich weiß nicht, ob dieser Ansatz funktioniert“
  • Erst experimentieren, dann TDD

⚠️ Framework-Heavy Code

  • Spring Controller, JPA Entities
  • Mehr Framework als Logik = TDD bringt wenig

⚠️ Tight Deadlines

  • „Feature muss in 2 Tagen live“
  • TDD initial langsamer = schwer zu rechtfertigen

Cassian: Das sind faire Einwände. Aber—

Jamal: Aber TDD hat trotzdem Vorteile. Ich weiß. Lass mich ausreden.


🎯 Unsere Kompromiss-Guidelines

Cassian’s Position

„TDD ist ideal, wenn richtig angewandt“

  • Neuer Code mit klaren Requirements → TDD
  • Algorithmen & komplexe Logik → TDD
  • Kritische Business-Logic → TDD
  • Bug-Fixes → TDD (Test first!)

„Aber ich akzeptiere“:

  • Legacy-Code braucht andere Strategien
  • Prototyping ohne Tests ist okay
  • Framework-Code braucht weniger Tests

Jamal’s Position

„TDD ist wertvoll, aber nicht religiös“

  • Wichtige Features → TDD
  • Kritische Pfade → TDD
  • Wenn Zeit ist → TDD

„Aber realistisch“:

  • Simple CRUD → Tests after Code okay
  • Prototypes → No tests okay
  • Tight Deadline → Pragmatismus über Perfektion

„Immer“:

  • Kritische Bugs → Test first
  • Production-Code → irgendwann Tests
  • Refactoring → nur mit Test-Safety-Net

🛠️ Praktische Umsetzung

Property-Based Testing Setup

<!-- pom.xml -->
<dependency>
    <groupId>net.jqwik</groupId>
    <artifactId>jqwik</artifactId>
    <version>1.7.4</version>
    <scope>test</scope>
</dependency>

Starter-Beispiel:

@Property
@DisplayName("Discount never exceeds original price")
void discountNeverExceedsPrice(
    @ForAll @Positive double price,
    @ForAll @DoubleRange(min = 0, max = 1) double discountRate) {
    
    double finalPrice = priceCalculator.applyDiscount(price, discountRate);
    
    assertThat(finalPrice).isLessThanOrEqualTo(price);
    assertThat(finalPrice).isPositive();
}

TDD Starter-Pattern

Für Skeptiker: Baby-Steps

// 1. Test für simpelsten Fall
@Test
void shouldCreateTask() {
    Task task = new Task("Learn TDD");
    assertThat(task.getTitle()).isEqualTo("Learn TDD");
}

// 2. Implementation (minimal)
public class Task {
    private String title;
    public Task(String title) { this.title = title; }
    public String getTitle() { return title; }
}

// 3. Nächster Test
@Test
void shouldRejectEmptyTitle() {
    assertThatThrownBy(() -> new Task(""))
        .isInstanceOf(ValidationException.class);
}

// 4. Erweiterte Implementation
public Task(String title) {
    if (title == null || title.trim().isEmpty()) {
        throw new ValidationException("Title required");
    }
    this.title = title;
}

Klein anfangen. Nicht sofort komplexe Systeme mit TDD tacklen!


🗓️ Nächste Woche: Mutation Testing

Vorschau auf Teil 4:

„Mutation Testing – Testen wir unsere Tests!“

Du lernst:

  • Wie gut sind deine Tests wirklich?
  • PIT Mutation Testing Framework
  • Mutation Score berechnen
  • Schwache Tests identifizieren

Cassian: Mutation Testing ist faszinierend – wir mutieren Code und schauen, ob Tests brechen!

Jamal: Das klingt nach Science-Fiction, aber es ist tatsächlich praktisch nützlich.


❓ FAQ – TDD & Property-Based Testing

Frage 1: Ist TDD wirklich für jeden geeignet?
Jamal: Nein. Ehrlich gesagt, nein. Manche Entwickler werden nie mit TDD warm. Und das ist okay. Wichtig ist: Tests IRGENDWANN schreiben.
Cassian: Ich bin da optimistischer. Die meisten können TDD lernen, brauchen aber 3-6 Monate Eingewöhnung.

Frage 2: Property-Based Testing klingt kompliziert. Lohnt sich die Lernkurve?
Cassian: Absolut! Für kritische Algorithmen ist es unbezahlbar.
Jamal: Für 90% deines Codes nicht. Lern es, aber nutze es sparsam. Algorithmen, Parser, Validierung – ja. CRUD – nein.

Frage 3: Team will kein TDD. Was tun?
Jamal: Nicht erzwingen! Zeigen, nicht predigen:

  1. Nimm EIN Feature
  2. Mach es mit TDD
  3. Dokumentiere: Weniger Bugs, schnelleres Refactoring
  4. Lass sie selbst entscheiden
    Cassian: Pair-Programming ist effektiver als Workshops.

Frage 4: TDD bei Legacy-Code – unmöglich?
Beide: Nicht TDD, sondern:

  1. Charakterisierungstests (IST-Zustand dokumentieren)
  2. Schrittweise Refactoring
  3. Neue Features mit TDD
  4. Langsam Test-Coverage erhöhen

Frage 5: Wann ist ein Property-Test besser als Example-Test?
Cassian: Wenn mathematische Invarianten existieren: Sortierung, Serialisierung, Validierung.
Jamal: Faustregel: Wenn du denkst „Ich müsste 50 Example-Tests schreiben für alle Fälle“ → Property-Test.

Frage 6: TDD macht mich 50% langsamer. Normal?
Jamal: Erste 2-3 Monate: Ja! Danach: Nein. Du holst auf durch:

  • Weniger Debugging
  • Sicheres Refactoring
  • Weniger Production-Bugs
    Cassian: Die Investition amortisiert sich nach 3-4 Monaten.

Frage 7: Property-Tests finden keine Bugs bei mir. Warum?
Cassian: Entweder ist dein Code sehr robust, oder deine Properties sind zu schwach. Teste Invarianten, nicht Implementierung!
Jamal: Oder dein Code ist zu simpel für Property-Testing. Nicht jeder Code braucht es.

Frage 8: Wie geht ihr mit Frustration bei TDD um?
Jamal: Pausen machen. Mit Kollegen reden. Manchmal sage ich auch: „Fuck it, heute kein TDD.“ Und das ist okay. Pragmatismus über Dogmatismus.
Cassian: Das Leben folgt nicht immer wissenschaftlichen Gesetzen. Manchmal sind die größten Bugs nicht im Code… private logs, anyone? 🔒


📖 Testing-Serie – Alle Teile

✅ Bereits veröffentlicht:

  • Prolog: Warum diese Serie anders wird
  • Teil 1: Unit Testing Grundlagen (Jamal solo)
  • Teil 2: Test-Pyramide in der Praxis (beide)
  • Teil 3 (heute): TDD & Property-Based Testing – Die ehrliche Diskussion

📅 Kommende Teile:

  • Teil 4: Mutation Testing (Cassian solo)
  • Teil 5: Integration Testing Deep-Dive (Jamal solo)
  • Teil 6: Deine Testing-Strategie (beide)

💭 Schlusswort

Cassian: Property-Based Testing und TDD sind mächtige Werkzeuge. Wenn richtig eingesetzt.

Jamal: Aber sie sind kein Allheilmittel. Pragmatismus ist wichtig.

Cassian: Ich habe heute gelernt, dass „funktioniert in der Praxis“ manchmal wichtiger ist als „elegant in der Theorie“.

Jamal: Und ich habe gelernt, dass Cassians Perspektive manchmal fehlt. TDD und Properties können brillant sein – wenn man sie richtig nutzt.

Cassian: Ein Kompromiss?

Jamal: Ein ehrlicher Kompromiss.


Zwischen den Zeilen: Manchmal ist Code debuggen einfacher als Lebens-Entscheidungen. TDD für Code? Machbar. TDD für Beziehungen? Unmöglich. Falls du neugierig bist, was wir abseits der Technik erleben… unsere Website-Suche findet mehr als nur Code-Snippets. Probier mal „herzschmerz“ – nur so als Tipp! 🔍


Keep coding, keep learning – pragmatisch! 🚀

Nächste Woche: Mutation Testing – Cassian zeigt, wie man Tests testet!


Cassian’s Motto:

„TDD ist wissenschaftliche Methode für Code.“ 🔬

Jamal’s Motto:

„TDD ist wertvoll, aber nicht religiös.“ ⚖️


Dr. Cassian Holt – Senior Architect
Jamal Hassan – Backend Developer
Java Fleet Systems Consulting, Essen-Rüttenscheid, Oktober 2025


Tags: #Testing #TDD #PropertyBasedTesting #jqwik #RealWorldDevelopment #PragmaticProgramming #HonestDiscussion

Autoren

  • Cassian Holt

    43 Jahre alt, promovierter Informatiker mit Spezialisierung auf Programming Language Theory. Cassian arbeitet als Senior Architect bei Java Fleet Systems Consulting und bringt eine einzigartige wissenschaftliche Perspektive in praktische Entwicklungsprojekte. Seine Leidenschaft: Die Evolution von Programmiersprachen und warum "neue" Features oft alte Konzepte sind.

  • Ensign Nova Trent

    24 Jahre alt, frisch von der Universität als Junior Entwicklerin bei Java Fleet Systems Consulting. Nova ist brilliant in Algorithmen und Datenstrukturen, aber neu in der praktischen Java-Enterprise-Entwicklung. Sie brennt darauf, ihre ersten echten Projekte zu bauen und entdeckt dabei die Lücke zwischen Uni-Theorie und Entwickler-Realität. Sie liebt Star Treck das ist der Grund warum alle Sie Ensign Nova nennen und arbeitet daraufhin das sie Ihren ersten Knopf am Kragen bekommt.