dhbw phase 4: alle 8 kapitel slide-MD komplett (4176 zeilen → ~1100 zeilen, ~210 slides total)
kap1 web_eng (104 → 57 slides): kurs-intro + prüfungsleistung-übersicht, internet-timeline (4 milestones), URL→pixel-7-schritte, HTTP, onboarding (node 24 LTS via fnm, brew/winget paket-manager, VS Code/Chromium/Hoppscotch, git setup mit SSH-key, projekt-skeleton), HTML (anatomie, self-closing, grundgerüst, semantik-tabelle, native elemente details/dialog/input-types), A11y volle tiefe (BFSG+EAA 28.06.2025, WCAG-POUR, Perceivable/Operable/Understandable/Robust einzeln mit code, testen mit lighthouse/axe/manuell), CSS-vorschau (anatomie/selektoren/spezifizität/box-model/farben/einheiten/pseudos), JS-vorschau (ECMAScript-timeline/grundlagen/DOM/fetch), frameworks-übersicht (react/vue/svelte/astro counter-vergleich), build-tools/rendering-modi, selbstlernen erste-eigene-seite kap2 css_extended (13 → 28 slides): box-model + box-sizing global, flexbox basics+achsen+item-properties+typische-patterns, grid basics+areas+auto-layout, flexbox-vs-grid tabelle, mobile-first media queries, viewport-meta, fluid sizing mit clamp(), transitions (was performant), keyframe-animationen, prefers-reduced-motion pflicht, CSS-variablen + dark-mode-pattern, CSS functions (calc/clamp/gradient/oklch/filter), CSS-frameworks tabelle + tailwind pro/contra, selbstlernen flexboxfroggy+cssgridgarden kap3 nodejs_basics (~22 → 20 slides): was-ist-node + warum-node-tabelle, fnm versions-manager + node 24 LTS + .nvmrc, npm init + package.json (mit type:module), ESM modern + CJS legacy, built-in modules (node: prefix), HTTP-server eingebaut + express, REST mit express (GET/POST/PUT/DELETE-pattern), alternative frameworks tabelle (fastify/hono/koa), routing-pattern mit Router, fetch global ab node 18, error-handling res.ok, environment variables mit dotenv + niemals committen, selbstlernen mini-API kap4 nodejs_advanced (~24 → 22 slides): synchron-vs-asynchron, promise then-vs-async/await, sequenziell-vs-parallel + Promise.all, promise-methoden tabelle (all/allSettled/race/any), fs/promises lesen+schreiben, streams pipeline für große dateien, DB-übersicht SQLite/Postgres/MongoDB/Redis, better-sqlite3 prepared statements, ORM-übersicht (drizzle/prisma/knex), CRUD-pattern express+sqlite, auth-strategien tabelle, bcrypt für passwörter, JWT-pattern mit httpOnly+secure cookie, try/catch + error-klassen, globaler express-error-handler, selbstlernen REST+DB+auth kap5 testing (~23 → 23 slides): test-pyramide 70%/20%/10%, vitest setup + describe/it/expect, AAA-pattern, matchers tabelle, TDD red-green-refactor mit konkretem beispiel, supertest für API-integration, test-DB :memory: + lifecycle hooks, mocking mit vi.spyOn (sparsam), playwright E2E mit role-based-locators, E2E-vs-unit tradeoffs, coverage realistisch (80% gut), CI mit github-actions, selbstlernen tests-schreiben kap6 typescript (~30 → 19 slides): was-ist-TS + warum-tabelle, setup mit tsx + tsconfig.json + strict:true, basis-typen + any vermeiden, object-typen + interfaces + type-aliases, function-typen (Promise<T>), union + discriminated union + intersection, generics + constraints, built-in generics (Partial/Omit/Record), typ-inference (nicht über-annotieren), typen für REST-API mit express, zod runtime-validation + z.infer, any-vs-unknown-vs-never, best practices, selbstlernen API-zu-TS-migration kap7 docker (~24 → 24 slides): auf-meinem-rechner-läufts problem, container-vs-VM tabelle, docker basics (image/container/dockerfile/registry), dockerfile hallo-welt, schichten + cache-optimierung, multi-stage build, base-image-größen tabelle (alpine ~180MB vs node ~1.1GB), .dockerignore, compose.yml beispiel, compose-commands, networking (service-namen als hostnamen), volumes (named vs bind), env+secrets, github-actions docker-build, registry-übersicht, health-checks, best-practices, selbstlernen app-dockerizen kap8 best_practices (~30 → 28 slides): sinnvolle commits + conventional commits format, .gitignore template, branches+PRs workflow, README-template mit 6 sektionen, SOLID 5 prinzipien tabelle, S-single-responsibility + D-dependency-inversion mit code, 12-factor wichtigste (codebase/deps/config/processes/disposability), factor 3 config-in-env, factor 6 stateless-processes, SemVer MAJOR.MINOR.PATCH, package.json caret/tilde/exakt, code-reviews geben+nehmen + checkliste, doku-quellen tabelle (MDN/nodejs/npm/tc39/caniuse), stack-overflow gut-nutzen, AI-assistenten pro/contra (verstehen-nicht-kopieren), selbstlernen projekt-hygiene-check build geht durch alle 8 kapitel-html+pdf render-spot-check: cover-slide-dark-mode-invert, git-setup code-syntax-highlight, css-anatomie code-blocks — alle sauber kein deploy, nur build+verify
This commit is contained in:
+294
-285
@@ -9,366 +9,375 @@ title: Testing
|
||||
---
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--color-foreground: #1a1a2e;
|
||||
--color-highlight: #d63384;
|
||||
--color-dimmed: #4a4a6a;
|
||||
}
|
||||
section.invert {
|
||||
--color-foreground: #fff;
|
||||
}
|
||||
section {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
h1 {
|
||||
color: #a02060;
|
||||
}
|
||||
section.invert h1 {
|
||||
color: #fff;
|
||||
}
|
||||
h2 {
|
||||
color: #1f2937;
|
||||
}
|
||||
section.invert h2 {
|
||||
color: #f48fb1;
|
||||
}
|
||||
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);
|
||||
}
|
||||
: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>
|
||||
|
||||
<!-- _class: invert -->
|
||||
<!-- _header: '' -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
<!-- _class: lead -->
|
||||
|
||||
# Web Engineering
|
||||
|
||||
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
|
||||
|
||||
**Sommersemester 2026** · Sitzung 7 — Testing
|
||||
# Kapitel 5 — Testing
|
||||
## Unit · Integration · E2E · TDD
|
||||
|
||||
---
|
||||
|
||||
# Ressourcen zum Selbstlernen
|
||||
# Warum testen?
|
||||
|
||||
- **CODE CRISPIES** — [codecrispi.es](https://codecrispi.es/)
|
||||
- **Online Code-Editor** — [codepen.io](https://codepen.io/pen/)
|
||||
- **MDN** (Mozilla Developer Network) — [developer.mozilla.org/de/](https://developer.mozilla.org/de/)
|
||||
- **Flexbox-Spiel** — [flexboxfroggy.com](https://flexboxfroggy.com/)
|
||||
- **Grid-Spiel** — [cssgridgarden.com](https://cssgridgarden.com/)
|
||||
| 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."**
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: invert -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||
# Testing
|
||||
|
||||
## Unit · Integration · End-to-End
|
||||
|
||||
---
|
||||
|
||||
# Inhalt
|
||||
|
||||
1. Test Automation Pyramid
|
||||
2. Test Automation Trophy
|
||||
3. Static Code Analysis
|
||||
4. Unit Tests
|
||||
5. Integration Tests
|
||||
6. End-to-End Tests (API + Client)
|
||||
7. Live Coding
|
||||
|
||||
---
|
||||
|
||||
# Test Automation Pyramid
|
||||
# Test-Pyramide
|
||||
|
||||
```
|
||||
▲ manual / exploratory
|
||||
▲ ▲ E2E (UI + API)
|
||||
▲▲▲▲ Integration (class + real deps)
|
||||
▲▲▲▲▲▲ Unit (class + mocked deps)
|
||||
▲▲▲▲▲▲▲▲ Static analysis (compiler, linter)
|
||||
/\
|
||||
/E2E\ wenige (langsam, fragil)
|
||||
/-----\
|
||||
/ Integ \ einige (mittel)
|
||||
/---------\
|
||||
/ Unit \ viele (schnell, isoliert)
|
||||
/-------------\
|
||||
```
|
||||
|
||||
- Oben: wenige, langsam, teuer, brüchig
|
||||
- Unten: viele, schnell, billig, stabil
|
||||
**Faustregel:** 70 % Unit, 20 % Integration, 10 % E2E.
|
||||
|
||||
---
|
||||
|
||||
# Test Automation V-Modell
|
||||
# Vitest — modernes JS-Testing
|
||||
|
||||
```sh
|
||||
npm install -D vitest
|
||||
```
|
||||
Requirements ────────────► Acceptance Test
|
||||
▼ ▲
|
||||
Architecture ────────► Integration Test
|
||||
▼ ▲
|
||||
Modular Design ──────► Unit Test
|
||||
▼ ▲
|
||||
Implementation
|
||||
```
|
||||
|
||||
Jede Design-Phase hat Testpendant. Testfälle früh ableiten.
|
||||
|
||||
---
|
||||
|
||||
# Test Automation Trophy (Kent C. Dodds)
|
||||
|
||||
```
|
||||
┌─────────┐
|
||||
│ E2E │ ← klein
|
||||
├─────────┤
|
||||
│ Integr. │ ← GROSS (Hauptfokus)
|
||||
├─────────┤
|
||||
│ Unit │ ← mittel
|
||||
├─────────┤
|
||||
│ Static │ ← Basis (TS, ESLint)
|
||||
└─────────┘
|
||||
```
|
||||
|
||||
Andere Gewichtung: **Integration im Zentrum**, Static als breite Basis.
|
||||
|
||||
---
|
||||
|
||||
# Testing Frameworks Übersicht
|
||||
|
||||
**Static Code Analysis**
|
||||
- ESLint, Prettier, SonarQube, TypeScript
|
||||
|
||||
**Unit + Integration**
|
||||
- jest, mocha, **vitest**
|
||||
|
||||
**End-to-End**
|
||||
- **Cypress**, **Playwright**, supertest (API only)
|
||||
|
||||
**Visual / Interaction**
|
||||
- Storybook, Chromatic
|
||||
|
||||
---
|
||||
|
||||
# Static Code Analysis
|
||||
|
||||
- Läuft in Millisekunden
|
||||
- Findet Syntaxfehler, ungenutzte Variablen, undefined refs
|
||||
- Kein Runtime-Overhead
|
||||
- **Erste Verteidigungslinie**
|
||||
|
||||
```bash
|
||||
npm i -D eslint prettier
|
||||
npx eslint --init
|
||||
npx prettier --write .
|
||||
```
|
||||
|
||||
`SonarQube`: zentraler Server, Code Smells, Security, Coverage.
|
||||
|
||||
---
|
||||
|
||||
# Unit Tests – Eigenschaften
|
||||
|
||||
- **Isolation** – jede Dependency gemockt
|
||||
- **Deterministisch** – gleicher Input → gleicher Output
|
||||
- **Schnell** – kein I/O, kein Netzwerk, kein FS
|
||||
- **Fokussiert** – eine Funktion pro Test
|
||||
|
||||
---
|
||||
|
||||
# Unit Test – Beispiel (vitest)
|
||||
|
||||
```javascript
|
||||
// cache.unit.test.js
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { Cache } from "./cache.js";
|
||||
// utils.test.js
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { greet } from './utils.js';
|
||||
|
||||
describe("Cache", () => {
|
||||
it("creates and retrieves data with custom key", () => {
|
||||
const cache = new Cache();
|
||||
const data = { user: "wolfgang", city: "stuttgart" };
|
||||
describe('greet', () => {
|
||||
it('returns greeting with name', () => {
|
||||
expect(greet('Ada')).toBe('Hallo Ada');
|
||||
});
|
||||
|
||||
it('handles empty string', () => {
|
||||
expect(greet('')).toBe('Hallo ');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
const key = cache.create(data, "custom-key");
|
||||
```sh
|
||||
npx vitest
|
||||
npx vitest --coverage
|
||||
```
|
||||
|
||||
expect(key).toBe("custom-key");
|
||||
expect(cache.get("custom-key")).toEqual(data);
|
||||
---
|
||||
|
||||
# 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 |
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: lead -->
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: lead -->
|
||||
|
||||
# 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');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Integration Tests – Eigenschaften
|
||||
|
||||
- **Reale Komponenten** – echte Cache + Express-Instanzen
|
||||
- **Supertest-Magie** – simuliert HTTP ohne Server-Startup
|
||||
- **In-Process** – alles im selben Node-Prozess
|
||||
- **Kein Netzwerk** – keine TCP-Connections
|
||||
|
||||
→ teste Zusammenspiel mehrerer Module ohne Deployment.
|
||||
|
||||
---
|
||||
|
||||
# Integration Test – Beispiel (supertest)
|
||||
# Test-Datenbank
|
||||
|
||||
```javascript
|
||||
// app.integration.test.js
|
||||
import request from "supertest";
|
||||
import { app, cache } from "./app.js";
|
||||
import { beforeAll, afterEach, afterAll } from 'vitest';
|
||||
import Database from 'better-sqlite3';
|
||||
|
||||
test("full CRUD lifecycle with real cache", async () => {
|
||||
const res = await request(app)
|
||||
.post("/api/entries")
|
||||
.send({ name: "integration-test", status: "active" });
|
||||
let db;
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
const { key } = res.body;
|
||||
|
||||
expect(cache.keys()).toContain(key);
|
||||
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());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# End-to-End Tests (API)
|
||||
|
||||
- **Echter Server** – HTTP auf TCP-Port
|
||||
- **Echtes Netzwerk** – `fetch` über localhost
|
||||
- **Echte Umgebung** – CORS, Middleware-Stack
|
||||
- **User-Perspektive** – exakt was Clients sehen
|
||||
`:memory:`-DB = jeder Test isoliert, blitzschnell.
|
||||
|
||||
---
|
||||
|
||||
# E2E API Test – Beispiel
|
||||
# Mocking
|
||||
|
||||
```javascript
|
||||
// index.e2e.test.js
|
||||
import { createServer } from "node:http";
|
||||
import { app } from "./app.js";
|
||||
import { vi, it, expect } from 'vitest';
|
||||
|
||||
let server;
|
||||
const TEST_PORT = 8081;
|
||||
const baseUrl = `http://localhost:${TEST_PORT}`;
|
||||
|
||||
beforeAll(async () => {
|
||||
server = createServer(app);
|
||||
await new Promise((r) => server.listen(TEST_PORT, r));
|
||||
});
|
||||
|
||||
afterAll(() => server.close());
|
||||
|
||||
test("complete user journey", async () => {
|
||||
const res = await fetch(`${baseUrl}/api`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ message: "e2e", timestamp: Date.now() }),
|
||||
it('calls fetch with right URL', async () => {
|
||||
const mockFetch = vi.spyOn(global, 'fetch').mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ name: 'Ada' })
|
||||
});
|
||||
expect(res.status).toBe(201);
|
||||
|
||||
await getUserData(1);
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith('https://api/users/1');
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# End-to-End Tests (Client)
|
||||
|
||||
- **Echter Browser** – Chromium / Firefox / WebKit
|
||||
- **Echtes DOM** – Klicks, Tippen, Scrollen
|
||||
- **Echtes Rendering** – CSS, async Operationen
|
||||
- **Cross-Origin** – Client- und Server-Kommunikation
|
||||
|
||||
Tools: **Cypress**, **Playwright**
|
||||
**Mock sparsam** — übermockt-Tests testen Mocks, nicht Code.
|
||||
|
||||
---
|
||||
|
||||
# Cypress – Minimalbeispiel
|
||||
<!-- _class: lead -->
|
||||
|
||||
# E2E — End-to-End
|
||||
|
||||
---
|
||||
|
||||
# Playwright — moderne E2E
|
||||
|
||||
```sh
|
||||
npm init playwright@latest
|
||||
```
|
||||
|
||||
```javascript
|
||||
// cypress/e2e/counter.cy.js
|
||||
describe("Counter App", () => {
|
||||
it("increments on click", () => {
|
||||
cy.visit("http://localhost:3000");
|
||||
cy.contains("button", "Clicked 0 times").click();
|
||||
cy.contains("button", "Clicked 1 times");
|
||||
});
|
||||
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();
|
||||
});
|
||||
```
|
||||
|
||||
```bash
|
||||
npx cypress open # GUI
|
||||
npx cypress run # headless / CI
|
||||
```
|
||||
Browser tatsächlich öffnen + interagieren. **Realer User-Flow.**
|
||||
|
||||
---
|
||||
|
||||
# Playwright – Minimalbeispiel
|
||||
# Playwright-Locators
|
||||
|
||||
```javascript
|
||||
// tests/counter.spec.js
|
||||
import { test, expect } from "@playwright/test";
|
||||
// Bevorzugt: rollenbasiert (A11y-konform!)
|
||||
page.getByRole('button', { name: 'Submit' });
|
||||
page.getByLabel('Username');
|
||||
page.getByText('Welcome');
|
||||
|
||||
test("counter increments", async ({ page }) => {
|
||||
await page.goto("http://localhost:3000");
|
||||
await page.getByRole("button").click();
|
||||
await expect(page.getByRole("button")).toContainText("Clicked 1 times");
|
||||
});
|
||||
// Fallback: CSS-Selektor
|
||||
page.locator('.submit-btn');
|
||||
page.locator('#username');
|
||||
```
|
||||
|
||||
```bash
|
||||
npx playwright test
|
||||
npx playwright test --ui
|
||||
```
|
||||
**Rollenbasierte Locators** finden Bugs, die A11y betreffen.
|
||||
|
||||
---
|
||||
|
||||
# Coverage Reports
|
||||
# E2E vs. Unit — Trade-offs
|
||||
|
||||
```bash
|
||||
npx vitest run --coverage
|
||||
```
|
||||
| | Unit | E2E |
|
||||
|--|------|-----|
|
||||
| Geschwindigkeit | ms | s |
|
||||
| Isolation | hoch | gering |
|
||||
| Realismus | gering | hoch |
|
||||
| Wartung | wenig | viel |
|
||||
| Wann | jede Funktion | kritische Flows |
|
||||
|
||||
```
|
||||
File | % Stmts | % Branch | % Funcs | % Lines
|
||||
--------------|---------|----------|---------|--------
|
||||
cache.js | 95.2 | 80.0 | 100.0 | 95.2
|
||||
app.js | 88.7 | 75.0 | 90.0 | 88.7
|
||||
```
|
||||
|
||||
- `lcov.info` → CI / SonarQube
|
||||
- HTML Report → `coverage/index.html`
|
||||
E2E nicht für jede Detail-Logik — für **happy path + Login + Bezahlung**.
|
||||
|
||||
---
|
||||
|
||||
# Best Practices
|
||||
<!-- _class: lead -->
|
||||
|
||||
- **AAA**: Arrange · Act · Assert
|
||||
- Tests sind **Dokumentation** – beschreibend benennen
|
||||
- Ein Verhalten pro Test
|
||||
- Keine Logik im Test (keine `if`/`for`)
|
||||
- **Test Doubles**: stub, mock, spy, fake
|
||||
- CI: Tests bei jedem Push
|
||||
# Coverage + CI
|
||||
|
||||
---
|
||||
|
||||
# Live Coding
|
||||
# Coverage-Report
|
||||
|
||||
Repo: `node-cache-api`
|
||||
|
||||
```bash
|
||||
git clone https://github.com/nextlevelshit/node-cache-api
|
||||
cd node-cache-api
|
||||
npm install
|
||||
npm test # unit + integration
|
||||
npm run test:e2e # e2e
|
||||
```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.
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: aufgabe -->
|
||||
|
||||
# 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.
|
||||
|
||||
Reference in New Issue
Block a user