Files
uni/slides/dhbw/05_testing.md

357 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
marp: true
theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering DHBW Stuttgart"
footer: "Michael Czechowski SoSe 2026"
title: Testing
---
<style>
:root {
--color-foreground: #1a1a2e;
--color-highlight: #005f8a;
--color-dimmed: #4a6a7a;
}
section.invert {
--color-foreground: #fff;
}
section {
font-size: 1.7rem;
}
h1 {
color: #005f8a;
}
section.invert h1 {
color: #fff;
}
pre {
background: #0f1e2e;
color: #00b4d8;
border-radius: 8px;
border-left: 3px solid #005f8a;
}
pre code {
background: transparent;
color: inherit;
}
code {
background: #1a3a4a;
color: #00b4d8;
padding: 0.15em 0.4em;
border-radius: 4px;
}
a {
color: var(--color-highlight);
}
</style>
<!-- _class: invert -->
<!-- _header: '' -->
<!-- _backgroundColor: #001520 -->
# 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
```
▲ manual / exploratory
▲ ▲ E2E (UI + API)
▲▲▲▲ Integration (class + real deps)
▲▲▲▲▲▲ Unit (class + mocked deps)
▲▲▲▲▲▲▲▲ Static analysis (compiler, linter)
```
- Oben: wenige, langsam, teuer, brüchig
- Unten: viele, schnell, billig, stabil
---
# Test Automation V-Modell
```
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";
describe("Cache", () => {
it("creates and retrieves data with custom key", () => {
const cache = new Cache();
const data = { user: "wolfgang", city: "stuttgart" };
const key = cache.create(data, "custom-key");
expect(key).toBe("custom-key");
expect(cache.get("custom-key")).toEqual(data);
});
});
```
---
# 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)
```javascript
// app.integration.test.js
import request from "supertest";
import { app, cache } from "./app.js";
test("full CRUD lifecycle with real cache", async () => {
const res = await request(app)
.post("/api/entries")
.send({ name: "integration-test", status: "active" });
expect(res.status).toBe(201);
const { key } = res.body;
expect(cache.keys()).toContain(key);
});
```
---
# 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
---
# E2E API Test Beispiel
```javascript
// index.e2e.test.js
import { createServer } from "node:http";
import { app } from "./app.js";
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() }),
});
expect(res.status).toBe(201);
});
```
---
# End-to-End Tests (Client)
- **Echter Browser** Chromium / Firefox / WebKit
- **Echtes DOM** Klicks, Tippen, Scrollen
- **Echtes Rendering** CSS, async Operationen
- **Cross-Origin** Client ↔ Server-Kommunikation
Tools: **Cypress**, **Playwright**
---
# Cypress Minimalbeispiel
```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");
});
});
```
```bash
npx cypress open # GUI
npx cypress run # headless / CI
```
---
# Playwright Minimalbeispiel
```javascript
// tests/counter.spec.js
import { test, expect } from "@playwright/test";
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");
});
```
```bash
npx playwright test
npx playwright test --ui
```
---
# Coverage Reports
```bash
npx vitest run --coverage
```
```
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`
---
# Best Practices
- **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
---
# Live Coding
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
```
---
<!-- _class: invert -->
<!-- _backgroundColor: #001520 -->
# Fragen?
**Hausaufgabe:** Eigene Projektidee mit mind. 1 Unit + 1 Integration Test starten.