Test-Driven Development (TDD) | Enjyn Gruppe
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

235 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

Enjix Beta

Enjyn AI Agent

Hallo 👋 Ich bin Enjix — wie kann ich dir helfen?
120