Hallo Welt
Hallo Welt
Original Lingva Deutsch
Übersetzung wird vorbereitet...
Dieser Vorgang kann bis zu 60 Sekunden dauern.
Diese Seite wird erstmalig übersetzt und dann für alle Besucher gespeichert.
0%
DE Zurück zu Deutsch
Übersetzung durch Lingva Translate

234 Dokumentationen verfügbar

Wissensdatenbank

Test Driven Development TDD

Zuletzt aktualisiert: 20.01.2026 um 11:25 Uhr

Test-Driven Development (TDD)

TDD dreht die Entwicklung um: Erst Tests schreiben, dann Code. Lernen Sie den Red-Green-Refactor Zyklus und wie TDD zu besserem Code führt.

Der TDD-Zyklus

┌─────────────────────────────────────────────────────────────┐
│                   TDD: RED-GREEN-REFACTOR                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                      ┌─────────┐                            │
│                      │   RED   │                            │
│                      │ (Test   │                            │
│                      │ fails)  │                            │
│                      └────┬────┘                            │
│                           │                                 │
│                           │ Write minimal                   │
│                           │ code to pass                    │
│                           ▼                                 │
│   ┌─────────┐       ┌─────────┐                            │
│   │REFACTOR │◄──────│  GREEN  │                            │
│   │(Improve │       │ (Test   │                            │
│   │ code)   │       │ passes) │                            │
│   └────┬────┘       └─────────┘                            │
│        │                                                    │
│        │ Write next                                         │
│        │ failing test                                       │
│        └────────────────────────────────────────►           │
│                                                             │
│   1. RED:      Schreibe einen fehlschlagenden Test         │
│   2. GREEN:    Schreibe minimalen Code zum Bestehen        │
│   3. REFACTOR: Verbessere Code ohne Funktionalität ändern  │
│   4. Wiederhole                                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Beispiel: String Calculator (JavaScript)

// SCHRITT 1: RED - Erster fehlschlagender Test
// stringCalculator.test.js

describe('StringCalculator', () => {
    it('should return 0 for empty string', () => {
        expect(add('')).toBe(0);
    });
});

// Test ausführen: FAIL ❌
// ReferenceError: add is not defined
// SCHRITT 2: GREEN - Minimaler Code zum Bestehen
// stringCalculator.js

function add(numbers) {
    return 0;
}

module.exports = { add };

// Test ausführen: PASS ✅
// SCHRITT 3: RED - Nächster Test
describe('StringCalculator', () => {
    it('should return 0 for empty string', () => {
        expect(add('')).toBe(0);
    });

    it('should return the number for single number', () => {
        expect(add('1')).toBe(1);
        expect(add('5')).toBe(5);
    });
});

// Test ausführen: FAIL ❌
// Expected: 1, Received: 0
// SCHRITT 4: GREEN - Code erweitern
function add(numbers) {
    if (numbers === '') return 0;
    return parseInt(numbers, 10);
}

// Test ausführen: PASS ✅
// SCHRITT 5: RED - Nächster Test
it('should return sum of two numbers', () => {
    expect(add('1,2')).toBe(3);
    expect(add('3,5')).toBe(8);
});

// Test ausführen: FAIL ❌
// SCHRITT 6: GREEN
function add(numbers) {
    if (numbers === '') return 0;

    const nums = numbers.split(',');
    return nums.reduce((sum, n) => sum + parseInt(n, 10), 0);
}

// Test ausführen: PASS ✅
// SCHRITT 7: REFACTOR - Code verbessern
function add(numbers) {
    if (!numbers) return 0;

    return numbers
        .split(',')
        .map(Number)
        .reduce((sum, n) => sum + n, 0);
}

// Tests erneut ausführen: PASS ✅
// Keine Funktionalität geändert, nur Code verbessert

Beispiel: User Service (PHP)

// tests/UserServiceTest.php

class UserServiceTest extends TestCase
{
    // RED: Test für User-Erstellung
    public function test_creates_user_with_valid_data(): void
    {
        $repository = $this->createMock(UserRepository::class);
        $repository->expects($this->once())
            ->method('save')
            ->willReturn(new User(1, 'John', 'john@example.com'));

        $service = new UserService($repository);

        $user = $service->createUser('John', 'john@example.com');

        $this->assertEquals('John', $user->getName());
        $this->assertEquals('john@example.com', $user->getEmail());
    }
}

// phpunit: FAIL ❌ - UserService existiert nicht
// src/UserService.php

// GREEN: Minimale Implementation
class UserService
{
    public function __construct(private UserRepository $repository) {}

    public function createUser(string $name, string $email): User
    {
        $user = new User(null, $name, $email);
        return $this->repository->save($user);
    }
}

// phpunit: PASS ✅
// RED: Test für Validierung
public function test_throws_exception_for_invalid_email(): void
{
    $repository = $this->createMock(UserRepository::class);
    $service = new UserService($repository);

    $this->expectException(InvalidArgumentException::class);
    $this->expectExceptionMessage('Invalid email format');

    $service->createUser('John', 'invalid-email');
}

// phpunit: FAIL ❌
// GREEN: Validierung hinzufügen
class UserService
{
    public function __construct(private UserRepository $repository) {}

    public function createUser(string $name, string $email): User
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Invalid email format');
        }

        $user = new User(null, $name, $email);
        return $this->repository->save($user);
    }
}

// phpunit: PASS ✅
// REFACTOR: Validierung extrahieren
class UserService
{
    public function __construct(
        private UserRepository $repository,
        private UserValidator $validator
    ) {}

    public function createUser(string $name, string $email): User
    {
        $this->validator->validate($name, $email);

        $user = new User(null, $name, $email);
        return $this->repository->save($user);
    }
}

class UserValidator
{
    public function validate(string $name, string $email): void
    {
        $this->validateName($name);
        $this->validateEmail($email);
    }

    private function validateEmail(string $email): void
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Invalid email format');
        }
    }

    private function validateName(string $name): void
    {
        if (strlen($name) < 2) {
            throw new InvalidArgumentException('Name too short');
        }
    }
}

// Alle Tests erneut ausführen: PASS ✅

TDD Best Practices

1. TESTS ZUERST
   ❌ Code schreiben → Tests schreiben
   ✅ Test schreiben → Code schreiben → Refactor

2. KLEINE SCHRITTE
   ❌ Kompletten Feature-Test schreiben
   ✅ Kleinste testbare Einheit zuerst

3. NUR MINIMALER CODE
   ❌ Vorausschauend "nützlichen" Code schreiben
   ✅ Nur genau so viel Code, dass Test grün wird

4. EIN TEST NACH DEM ANDEREN
   ❌ Alle Tests auf einmal schreiben
   ✅ Red → Green → Refactor für jeden Test

5. TESTS MÜSSEN ERST FEHLSCHLAGEN
   ❌ Test schreiben, der sofort grün ist
   ✅ Sicherstellen, dass Test wirklich testet

Was TDD fördert

Vorteil Erklärung
Besseres Design Code wird testbar geschrieben → Loose Coupling
Dokumentation Tests dokumentieren erwartetes Verhalten
Confidence Refactoring ist sicher dank Tests
Weniger Bugs Bugs werden früh gefunden
Fokus Nur nötiger Code wird geschrieben

Test-Struktur: AAA Pattern

// Arrange - Act - Assert

describe('ShoppingCart', () => {
    it('should calculate total with discount', () => {
        // ARRANGE: Setup
        const cart = new ShoppingCart();
        cart.addItem({ name: 'Book', price: 20 });
        cart.addItem({ name: 'Pen', price: 5 });
        cart.applyDiscount(10); // 10%

        // ACT: Ausführen
        const total = cart.getTotal();

        // ASSERT: Prüfen
        expect(total).toBe(22.50); // 25 - 10% = 22.50
    });
});
// Given - When - Then (BDD-Style)

describe('User Authentication', () => {
    it('should lock account after 3 failed attempts', () => {
        // GIVEN: Initial state
        const auth = new AuthService();
        const user = auth.createUser('test@example.com', 'password123');

        // WHEN: Actions
        auth.login('test@example.com', 'wrong1');
        auth.login('test@example.com', 'wrong2');
        auth.login('test@example.com', 'wrong3');

        // THEN: Expected outcome
        expect(user.isLocked()).toBe(true);
        expect(() => auth.login('test@example.com', 'password123'))
            .toThrow('Account is locked');
    });
});

Mocking in TDD

// JavaScript mit Jest

describe('OrderService', () => {
    it('should send confirmation email after order', async () => {
        // Mock erstellen
        const emailService = {
            send: jest.fn().mockResolvedValue(true)
        };
        const orderRepository = {
            save: jest.fn().mockResolvedValue({ id: 1 })
        };

        const orderService = new OrderService(orderRepository, emailService);

        // Act
        await orderService.placeOrder({
            userId: 1,
            items: [{ productId: 1, quantity: 2 }]
        });

        // Assert: Mock wurde aufgerufen
        expect(emailService.send).toHaveBeenCalledTimes(1);
        expect(emailService.send).toHaveBeenCalledWith(
            expect.objectContaining({
                type: 'order_confirmation',
                orderId: 1
            })
        );
    });
});
// PHP mit PHPUnit

class OrderServiceTest extends TestCase
{
    public function test_sends_confirmation_email(): void
    {
        // Mock erstellen
        $emailService = $this->createMock(EmailService::class);
        $emailService->expects($this->once())
            ->method('send')
            ->with($this->callback(function ($email) {
                return $email->getType() === 'order_confirmation';
            }));

        $repository = $this->createMock(OrderRepository::class);
        $repository->method('save')
            ->willReturn(new Order(1));

        $service = new OrderService($repository, $emailService);

        // Act
        $service->placeOrder(new OrderRequest(
            userId: 1,
            items: [new OrderItem(productId: 1, quantity: 2)]
        ));

        // Assert erfolgt durch expects() oben
    }
}

Wann TDD schwierig ist

TDD IST SCHWIERIGER BEI:

1. UI/Frontend Code
   → Stattdessen: Logik extrahieren, diese testen

2. Legacy Code ohne Tests
   → Erst charakterisierungs-Tests, dann Refactoring

3. Explorative Prototypen
   → Erst Prototyp, dann mit Tests stabilisieren

4. Externe Abhängigkeiten (APIs, DBs)
   → Mocking, Integration Tests separat

5. Zeitdruck / Deadlines
   → TDD zahlt sich langfristig aus

LÖSUNG: TDD für Geschäftslogik, andere Strategien für Rest
💡 TDD Tipps: 1. Kleinste mögliche Schritte machen
2. Test muss erst ROT sein, dann GRÜN
3. Nur Code schreiben, der Tests bestehen lässt
4. Regelmäßig refactoren
5. Bei Schwierigkeiten: Noch kleinere Schritte

Weitere Informationen