per user-request einen branch der main-content UND rewrite-content trägt — saubere separierung statt symlink/merge-conflict. struktur: - slides/<course>/ → main-content restored, eigene assets (kein new-demo-leak) - slides/<course>-2026/ → rewrite-content, eigene assets (komplett kopiert + new demos) Makefile: COURSES += 223015b-2026 223015c-2026 dhbw-2026 mit eigenen NAME/KAPITEL/DEPLOY/KLAUSUR-konfig. Klausur für b+c=1, dhbw=leer. Deploy-paths /hdm/223015b-2026 etc. make build durchlaufen für alle 6 decks, 0 fehler. dedup: 223015c+dhbw assets identisch in beiden ordnern (kein neu-demos auf b ergänzt). 223015b 86 → 120 files in -2026 (= +34 neue eroeffner/usb-c-drei-achsen/container-vs-codec/etc).
7.7 KiB
marp, theme, paginate, backgroundColor, header, footer, title
| marp | theme | paginate | backgroundColor | header | footer | title |
|---|---|---|---|---|---|---|
| true | gaia | true | Web Engineering – DHBW Stuttgart | Michael Czechowski – SoSe 2026 | 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
npm install -D vitest
// 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 ');
});
});
npx vitest
npx vitest --coverage
Test-Struktur (AAA)
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
- Red: Test schreiben (schlägt fehl)
- Green: minimaler Code, damit Test besteht
- Refactor: Code säubern, Test bleibt grün
Kent Beck, 2002. Steile Lernkurve, aber stark fokussiert.
// 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
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
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
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
npm init playwright@latest
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
// 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
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
# .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:
- 3+ Unit-Tests für eure Helper-Funktionen (
utils.test.js) - 3+ Integration-Tests für eure REST-Endpunkte (mit Supertest)
- In-Memory-DB in
beforeAll/afterEach - 1 E2E-Test mit Playwright (z.B. Login-Flow)
- 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.