TDD mit Claude Code: Testgetriebene Entwicklung mit Vitest und node:test
Lerne TDD mit Claude Code anhand lauffähiger Vitest- und node:test-Beispiele, CI, hooks und Prompt-Vorlagen.
Claude Code kann sehr schnell Code schreiben. Geschwindigkeit allein macht eine Änderung aber nicht sicher. Die eigentlichen Probleme tauchen oft später auf: ein übersehener Grenzwert, eine Regression, ein falsch berechneter Rabatt oder ein Test, der erst in CI fehlschlägt.
TDD, also testgetriebene Entwicklung, bringt diese Geschwindigkeit in eine kontrollierbare Form. Der Ablauf ist einfach: zuerst einen fehlschlagenden Test schreiben, dann die kleinste Implementierung bauen, die ihn grün macht, und anschließend refaktorieren, ohne das Verhalten zu ändern. Dieser Ablauf heißt Red-Green-Refactor.
Claude Code passt gut dazu, weil es Testfälle sammeln, Fehlermeldungen lesen, kleine Änderungen implementieren und CI ergänzen kann. Wichtig ist, nicht nur “baue die Funktion” zu schreiben. Fordere zuerst den roten Test an. Dieser Artikel enthält kopierbare Beispiele mit Vitest und node:test, CI, aktuelle Claude Code hooks und Prompt-Vorlagen.
Für diese Aktualisierung wurden die offiziellen Seiten Claude Code hooks reference, Claude Code memory, Claude Code settings, Vitest Getting Started, Vitest CLI und Node.js test runner geprüft. Das hook-Beispiel liest JSON von stdin und nutzt tool_input.file_path, statt sich auf veraltete Umgebungsvariablen-Beispiele zu verlassen.
Wobei Claude Code im TDD hilft
Claude Code eignet sich für Testfalllisten, fehlende Implementierungen, Fehleranalyse, CI-Ergänzungen und Risikozusammenfassungen. Menschen sollten Geschäftsregeln, Sicherheitsgrenzen, API-Verträge und Release-Entscheidungen festlegen.
| Schritt | Aufgabe für Claude Code | Menschliche Prüfung |
|---|---|---|
| Red | Fehlschlagende Tests aus der Spezifikation schreiben | Wurden Anforderungen erfunden? |
| Green | Kleinste passende Implementierung | Gibt es unnötige Abstraktion? |
| Refactor | Duplikate und Namen verbessern | Bleibt das Verhalten gleich? |
| CI | Tests pro PR ausführen | Passt die Node-Version? |
| Betrieb | hooks und CLAUDE.md nutzen | Ist die Automatisierung schnell genug? |
flowchart LR
A["Spezifikation schneiden"] --> B["Red: fehlschlagender Test"]
B --> C["Green: kleinste Implementierung"]
C --> D["Refactor: aufräumen"]
D --> E["CI und hooks erneut ausführen"]
E --> B
Beispiel 1: Preise und Coupons mit Vitest
Preislogik, Coupons und Abos sind gute TDD-Kandidaten, weil kleine Fehler direkt Umsatz oder Vertrauen betreffen.
npm install -D vitest
{
"type": "module",
"scripts": {
"test": "vitest run",
"test:watch": "vitest"
},
"devDependencies": {
"vitest": "^3.0.0"
}
}
Lege zuerst src/cart.test.ts an.
import { describe, expect, it } from "vitest";
import { priceCart, ValidationError } from "./cart";
describe("priceCart", () => {
it("calculates subtotal and total without a coupon", () => {
const result = priceCart({
items: [
{ sku: "book", unitPriceCents: 1200, quantity: 2 },
{ sku: "video", unitPriceCents: 3000, quantity: 1 },
],
});
expect(result).toEqual({
subtotalCents: 5400,
discountCents: 0,
totalCents: 5400,
});
});
it("applies a valid percent coupon", () => {
const result = priceCart(
{
items: [{ sku: "course", unitPriceCents: 10000, quantity: 1 }],
coupon: {
code: "SPRING20",
percentOff: 20,
expiresAt: "2026-06-30T00:00:00.000Z",
},
},
{ now: new Date("2026-06-02T00:00:00.000Z") },
);
expect(result.totalCents).toBe(8000);
expect(result.discountCents).toBe(2000);
});
it("rejects expired coupons", () => {
expect(() =>
priceCart(
{
items: [{ sku: "course", unitPriceCents: 10000, quantity: 1 }],
coupon: {
code: "OLD20",
percentOff: 20,
expiresAt: "2026-05-01T00:00:00.000Z",
},
},
{ now: new Date("2026-06-02T00:00:00.000Z") },
),
).toThrow(ValidationError);
});
it("rejects zero or negative quantity", () => {
expect(() =>
priceCart({
items: [{ sku: "book", unitPriceCents: 1200, quantity: 0 }],
}),
).toThrow("quantity must be positive");
});
});
Bitte Claude Code zuerst um Red.
Wir sind im Red-Schritt. src/cart.test.ts existiert, src/cart.ts aber noch nicht.
Bitte:
1. npm test ausführen und den Fehler bestätigen.
2. Nur das kleinste src/cart.ts implementieren, das die Tests grün macht.
3. Keine UI, Datenbank, externe API oder Zukunftsfunktion hinzufügen.
4. Erst nach Green refaktorieren.
Minimale Implementierung für src/cart.ts.
export class ValidationError extends Error {
constructor(message: string) {
super(message);
this.name = "ValidationError";
}
}
type CartItem = {
sku: string;
unitPriceCents: number;
quantity: number;
};
type Coupon = {
code: string;
percentOff: number;
expiresAt: string;
};
type CartInput = {
items: CartItem[];
coupon?: Coupon;
};
type PriceOptions = {
now?: Date;
};
export function priceCart(input: CartInput, options: PriceOptions = {}) {
if (input.items.length === 0) {
throw new ValidationError("cart must contain at least one item");
}
const subtotalCents = input.items.reduce((sum, item) => {
if (!Number.isInteger(item.quantity) || item.quantity <= 0) {
throw new ValidationError("quantity must be positive");
}
if (!Number.isInteger(item.unitPriceCents) || item.unitPriceCents < 0) {
throw new ValidationError("unitPriceCents must be a non-negative integer");
}
return sum + item.unitPriceCents * item.quantity;
}, 0);
const discountCents = calculateDiscount(subtotalCents, input.coupon, options.now ?? new Date());
return {
subtotalCents,
discountCents,
totalCents: subtotalCents - discountCents,
};
}
function calculateDiscount(subtotalCents: number, coupon: Coupon | undefined, now: Date) {
if (!coupon) return 0;
if (coupon.percentOff <= 0 || coupon.percentOff > 100) {
throw new ValidationError("percentOff must be between 1 and 100");
}
if (new Date(coupon.expiresAt).getTime() < now.getTime()) {
throw new ValidationError("coupon expired");
}
return Math.round(subtotalCents * (coupon.percentOff / 100));
}
Beispiel 2: CLI-Grenzwerte mit node:test
Für kleine Node-Werkzeuge reicht oft node:test. Speichere die Datei als limit.test.mjs.
import test from "node:test";
import assert from "node:assert/strict";
export function parseLimit(value, fallback = 20) {
if (value === undefined || value === "") return fallback;
const parsed = Number(value);
if (!Number.isInteger(parsed)) {
throw new TypeError("limit must be an integer");
}
if (parsed < 1 || parsed > 100) {
throw new RangeError("limit must be between 1 and 100");
}
return parsed;
}
test("parseLimit uses fallback when the value is empty", () => {
assert.equal(parseLimit(undefined), 20);
assert.equal(parseLimit("", 50), 50);
});
test("parseLimit accepts values from 1 to 100", () => {
assert.equal(parseLimit("1"), 1);
assert.equal(parseLimit("100"), 100);
});
test("parseLimit rejects decimals and out-of-range values", () => {
assert.throws(() => parseLimit("1.5"), /integer/);
assert.throws(() => parseLimit("0"), /between 1 and 100/);
assert.throws(() => parseLimit("101"), /between 1 and 100/);
});
node --test limit.test.mjs
Beispiel 3: API-Bug als Regression
Ein Produktionsfehler sollte zuerst als fehlschlagender Test festgehalten werden.
Füge einen API-Regressionstest mit TDD hinzu.
Hintergrund:
- POST /checkout akzeptiert fälschlich abgelaufene Coupons.
- Gültige Coupons und Checkout ohne Coupon müssen weiter funktionieren.
Red:
- Test hinzufügen, der bei abgelaufenem Coupon 400 erwartet.
- Bestätigen, dass die aktuelle Implementierung fehlschlägt.
Green:
- Kleinste API-Änderung umsetzen.
Refactor:
- Nur doppelte Datumsvergleiche extrahieren.
Rückgabe:
- Testname, Fehlerausgabe, geänderte Dateien, Befehle, Restrisiken.
Mehr dazu steht im API-Testleitfaden und im Teststrategie-Leitfaden.
CI und hooks
name: test
on:
pull_request:
push:
branches: [main]
jobs:
unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm test
Hook für zugehörige Vitest-Tests nach Bearbeitungen.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/run-related-vitest.mjs",
"timeout": 120
}
]
}
]
}
}
import { spawnSync } from "node:child_process";
import path from "node:path";
let raw = "";
for await (const chunk of process.stdin) {
raw += chunk;
}
const event = raw ? JSON.parse(raw) : {};
const filePath = event.tool_input?.file_path;
if (typeof filePath !== "string" || !/\.[cm]?[jt]sx?$/.test(filePath)) {
process.exit(0);
}
const target = path.isAbsolute(filePath)
? path.relative(process.cwd(), filePath)
: filePath;
const result = spawnSync("npx", ["vitest", "related", target, "--run"], {
stdio: "inherit",
shell: process.platform === "win32",
});
process.exit(result.status ?? 1);
CLAUDE.md sollte kurz bleiben.
## TDD workflow
- Behavior changes start with a failing test.
- Show the Red result before implementation.
- Implement the smallest change that makes the test pass.
- Refactor only after the targeted test is Green.
- Report the command, result, changed files, and remaining risk.
Siehe auch den hooks-Leitfaden und die CLAUDE.md Best Practices.
Prompt-Vorlagen
Neue Funktion mit TDD:
Ziel:
[Funktion] hinzufügen.
Spezifikation:
- [Normalfall]
- [Grenzwerte]
- [Fehlerverhalten]
Prozess:
1. Tests zuerst schreiben.
2. npm test ausführen und Red zeigen.
3. Kleinste Green-Implementierung.
4. Refactor ohne Verhaltensänderung.
Rückgabe:
Fehlerausgabe, Befehle, geänderte Dateien, Risiken.
Bugfix mit TDD:
Reproduktion:
[Input, Aktion oder Log]
Erwartet:
[korrektes Verhalten]
Aktuell:
[aktuelles Verhalten]
Auftrag:
Erst einen fehlschlagenden Regressionstest hinzufügen.
Dann die kleinste Korrektur umsetzen.
Bestehende Tests nicht ohne Begründung abschwächen oder löschen.
Sicheres Refactoring:
Ziel:
[Datei/Funktion]
Einschränkung:
Öffentliches Verhalten bleibt gleich.
Schritte:
1. Characterization Tests für aktuelles Verhalten.
2. Green bestätigen.
3. Nur interne Struktur ändern.
4. Dieselben Tests erneut ausführen.
Häufige Fehler
Der erste Fehler ist, Red zu überspringen. Ein Test, der schon vor dem Fix grün ist, schützt nichts. Der zweite ist, Implementierungsdetails statt Verhalten zu testen. Der dritte ist echte Zeit im Test; injiziere now. Der vierte ist zu viel Vertrauen in Mocks bei Zahlung, E-Mail oder CRM. Der fünfte ist, Claude Code Tests löschen zu lassen, nur um Green zu erreichen.
CTA
Starte mit einer Preisregel, einem CLI-Parser oder einer API-Regression. Einzelne Entwickler können das kostenlose Claude Code Cheatsheet und die Vorlagen nutzen. Für wiederverwendbare Prompts, hooks und Review-Checklisten gibt es die ClaudeCodeLab Produkte. Teams, die TDD, CI, Berechtigungen und Reviews in einem echten Repository einführen wollen, nutzen Claude Code Training und Beratung.
Ergebnis des Praxistests
In Masas Workflow verkürzte es die Review-Zeit, Claude Code zuerst den fehlschlagenden Test schreiben zu lassen. Abgelaufene Coupons, Menge null und nicht authentifizierte API-Pfade wurden früher sichtbar. Alle Tests nach jeder Bearbeitung per hook zu starten war zu langsam; praktikabel war ein related Vitest nach Edits und vollständiges E2E in CI.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Workflow von Obsidian zu CLAUDE.md
Obsidian-Arbeitsnotizen in CLAUDE.md-Betriebsnotizen verwandeln und Kontext nicht ständig neu erklären.
Claude Code Revenue CTA Routing: Artikel zu PDF, Gumroad und Beratung führen
Ein Claude-Code-Ablauf, der Leser nach Absicht zu Gratis-PDF, Gumroad oder Beratung führt.
Claude-Code-Team-Handoff-Regeln: Belege, Berechtigungen, Rollback und Umsatzpfade
Ein praktisches Claude-Code-Handoff für Review-Belege, Berechtigungen, Rollback, Gratis-PDF, Gumroad und Beratung.