Files
uni/slides/dhbw-2026/05_testing.md
T
libretech 351e3dfc23 branch-coexist: 3 neue decks 223015b-2026 / 223015c-2026 / dhbw-2026 daneben main
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).
2026-05-14 21:54:15 +02:00

7.7 KiB
Raw Blame History

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
<style> :root { --color-foreground: #1a1a2e; --color-highlight: #d63384; } section.invert { --color-foreground: #fff; } section { font-size: 1.4rem; } h1 { color: #a02060; } section.invert h1 { color: #fff; } h2 { color: #1f2937; } pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; } pre code { background: transparent; color: inherit; } code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; } a { color: var(--color-highlight); } </style>

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

  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.

// 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:

  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.