--- marp: true theme: gaia paginate: true backgroundColor: #fff header: "Web Engineering – DHBW Stuttgart" footer: "Michael Czechowski – SoSe 2026" title: Testing --- # Kapitel 5 — Testing ## Unit · Integration · E2E · TDD --- # Warum testen? | Vorteil | Was bedeutet das | |---------|------------------| | **Confidence** | Refactor ohne Angst | | **Regression-Schutz** | alter Bug taucht nicht wieder auf | | **Living Documentation** | Tests zeigen, was Code soll | | **CI/CD-Voraussetzung** | automatisch vor jedem Deploy | **„Untested Code is broken by design."** --- # Test-Pyramide ``` /\ /E2E\ wenige (langsam, fragil) /-----\ / Integ \ einige (mittel) /---------\ / Unit \ viele (schnell, isoliert) /-------------\ ``` **Faustregel:** 70 % Unit, 20 % Integration, 10 % E2E. --- # Vitest — modernes JS-Testing ```sh npm install -D vitest ``` ```javascript // utils.test.js import { describe, it, expect } from 'vitest'; import { greet } from './utils.js'; describe('greet', () => { it('returns greeting with name', () => { expect(greet('Ada')).toBe('Hallo Ada'); }); it('handles empty string', () => { expect(greet('')).toBe('Hallo '); }); }); ``` ```sh npx vitest npx vitest --coverage ``` --- # Test-Struktur (AAA) ```javascript it('adds two numbers', () => { // ARRANGE — Setup const a = 2; const b = 3; // ACT — die Operation const result = add(a, b); // ASSERT — Erwartung expect(result).toBe(5); }); ``` **Eine Erwartung pro Test** (Faustregel). --- # Vitest-Matchers | Matcher | Was | |---------|-----| | `.toBe(x)` | strict equal (`===`) | | `.toEqual(x)` | deep equal (Objects, Arrays) | | `.toBeNull()` / `.toBeUndefined()` | | | `.toBeTruthy()` / `.toBeFalsy()` | | | `.toContain(item)` | Array enthält | | `.toHaveLength(n)` | | | `.toThrow()` | wirft Fehler | | `.toMatchSnapshot()` | Snapshot-Test | --- # TDD — Test-Driven Development --- # TDD-Cycle: Red → Green → Refactor 1. **Red:** Test schreiben (schlägt fehl) 2. **Green:** minimaler Code, damit Test besteht 3. **Refactor:** Code säubern, Test bleibt grün **Kent Beck, 2002.** Steile Lernkurve, aber stark fokussiert. ```javascript // 1. Red — Test ohne Implementierung it('isEven returns true for even', () => { expect(isEven(4)).toBe(true); }); // → ReferenceError: isEven is not defined // 2. Green — minimale Lösung function isEven(n) { return n % 2 === 0; } // 3. Refactor — Edge Cases, Lesbarkeit ``` --- # Integration-Tests --- # API testen mit Supertest ```javascript import request from 'supertest'; import { app } from './app.js'; describe('GET /users', () => { it('returns users array', async () => { const res = await request(app).get('/users'); expect(res.status).toBe(200); expect(res.body).toBeInstanceOf(Array); }); }); describe('POST /users', () => { it('creates a user', async () => { const res = await request(app) .post('/users') .send({ name: 'Ada' }); expect(res.status).toBe(201); expect(res.body.name).toBe('Ada'); }); }); ``` --- # Test-Datenbank ```javascript import { beforeAll, afterEach, afterAll } from 'vitest'; import Database from 'better-sqlite3'; let db; beforeAll(() => { db = new Database(':memory:'); // in-RAM, super schnell db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)'); }); afterEach(() => { db.exec('DELETE FROM users'); // sauberer Start }); afterAll(() => db.close()); ``` `:memory:`-DB = jeder Test isoliert, blitzschnell. --- # Mocking ```javascript import { vi, it, expect } from 'vitest'; it('calls fetch with right URL', async () => { const mockFetch = vi.spyOn(global, 'fetch').mockResolvedValue({ ok: true, json: async () => ({ name: 'Ada' }) }); await getUserData(1); expect(mockFetch).toHaveBeenCalledWith('https://api/users/1'); }); ``` **Mock sparsam** — übermockt-Tests testen Mocks, nicht Code. --- # E2E — End-to-End --- # Playwright — moderne E2E ```sh npm init playwright@latest ``` ```javascript import { test, expect } from '@playwright/test'; test('login flow', async ({ page }) => { await page.goto('http://localhost:3000'); await page.getByLabel('Email').fill('ada@example.com'); await page.getByLabel('Password').fill('secret123'); await page.getByRole('button', { name: 'Login' }).click(); await expect(page.getByText('Hallo Ada')).toBeVisible(); }); ``` Browser tatsächlich öffnen + interagieren. **Realer User-Flow.** --- # Playwright-Locators ```javascript // Bevorzugt: rollenbasiert (A11y-konform!) page.getByRole('button', { name: 'Submit' }); page.getByLabel('Username'); page.getByText('Welcome'); // Fallback: CSS-Selektor page.locator('.submit-btn'); page.locator('#username'); ``` **Rollenbasierte Locators** finden Bugs, die A11y betreffen. --- # E2E vs. Unit — Trade-offs | | Unit | E2E | |--|------|-----| | Geschwindigkeit | ms | s | | Isolation | hoch | gering | | Realismus | gering | hoch | | Wartung | wenig | viel | | Wann | jede Funktion | kritische Flows | E2E nicht für jede Detail-Logik — für **happy path + Login + Bezahlung**. --- # Coverage + CI --- # Coverage-Report ```sh npx vitest --coverage ``` ``` File | % Stmts | % Branch | % Funcs | Uncovered -------------+---------+----------+---------+----------- utils.js | 100 | 100 | 100 | api.js | 75.3 | 60 | 80 | 42-48 auth.js | 91.2 | 85 | 100 | 15 ``` **100 % ≠ keine Bugs.** Aber 0 % = sicher Bugs. --- # Coverage-Ziel realistisch | Code-Typ | Realistisch | |----------|-------------| | Pure Funktionen | 100 % | | Business-Logik | 80-90 % | | API-Routes | 70-80 % | | UI-Komponenten | 60-70 % | | Drittpartei-Wrapper | 0 % | | Konfig-Files | 0 % | **Faustregel:** über 80 % Gesamt = gut. Unter 50 % = gefährlich. --- # CI: Tests auf jedem Push ```yaml # .github/workflows/test.yml name: Test on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 24 - run: npm ci - run: npm test - run: npm run test:e2e ``` Tests sind grün → PR mergebar. Sonst: rot, mergen verboten. --- # Selbstlernen — Tests schreiben Erweitert eure API: 1. **3+ Unit-Tests** für eure Helper-Funktionen (`utils.test.js`) 2. **3+ Integration-Tests** für eure REST-Endpunkte (mit Supertest) 3. **In-Memory-DB** in `beforeAll`/`afterEach` 4. **1 E2E-Test** mit Playwright (z.B. Login-Flow) 5. **Coverage-Report** generieren **Bonus:** GitHub-Actions-Workflow für CI. --- # Zusammenfassung **Heute gelernt:** - **Test-Pyramide:** viele Unit, einige Integration, wenige E2E - **Vitest** als modernes Test-Runner-Setup - **AAA-Pattern** + Matchers + Lifecycle-Hooks - **TDD-Cycle** Red → Green → Refactor - **Supertest** für API-Integration-Tests - **Playwright** für E2E mit rollenbasierten Locators - **Coverage** als Werkzeug, nicht als Ziel - **CI** mit GitHub-Actions **Nächste Stunde:** TypeScript — Typen über JavaScript.