with one click
tdd-workflow
// Test-Driven Development methodology following the Red-Green-Refactor cycle for writing robust, well-tested code.
// Test-Driven Development methodology following the Red-Green-Refactor cycle for writing robust, well-tested code.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | tdd-workflow |
| description | Test-Driven Development methodology following the Red-Green-Refactor cycle for writing robust, well-tested code. |
Test-Driven Development methodology and best practices.
// 1. RED: Write failing test
describe('calculateDiscount', () => {
it('applies 10% discount for premium users', () => {
const result = calculateDiscount(100, 'premium');
expect(result).toBe(90);
});
});
// 2. GREEN: Make it pass
function calculateDiscount(price: number, tier: string): number {
if (tier === 'premium') return price * 0.9;
return price;
}
// 3. REFACTOR: Improve
const DISCOUNTS = {
premium: 0.1,
standard: 0
};
function calculateDiscount(price: number, tier: keyof typeof DISCOUNTS): number {
return price * (1 - DISCOUNTS[tier]);
}
Feature: User can add items to cart
Given: Empty cart
When: User adds item
Then: Cart contains 1 item
test('adds item to cart', () => {
const cart = new ShoppingCart();
const item = { id: '1', name: 'Book', price: 10 };
cart.addItem(item);
expect(cart.items).toHaveLength(1);
expect(cart.items[0]).toEqual(item);
});
Error: ShoppingCart is not defined
class ShoppingCart {
items: Item[] = [];
addItem(item: Item) {
this.items.push(item);
}
}
ā adds item to cart
class ShoppingCart {
private _items: Item[] = [];
get items(): readonly Item[] {
return this._items;
}
addItem(item: Item): void {
this._items.push(item);
}
}
test('calculates total with multiple items', () => {
// Arrange
const cart = new ShoppingCart();
const item1 = { id: '1', price: 10 };
const item2 = { id: '2', price: 20 };
// Act
cart.addItem(item1);
cart.addItem(item2);
// Assert
expect(cart.total).toBe(30);
});
// ā
Descriptive
test('throws error when adding duplicate item', () => {});
test('applies discount after adding 5 items', () => {});
test('removes item by id', () => {});
// ā Vague
test('cart test', () => {});
test('test addItem', () => {});
// Bad: Tests internal structure
test('calls calculateDiscount method', () => {
const spy = jest.spyOn(cart, 'calculateDiscount');
cart.checkout();
expect(spy).toHaveBeenCalled();
});
// Good: Tests observable behavior
test('applies discount on checkout', () => {
cart.addItem({ price: 100 });
const total = cart.checkout();
expect(total).toBe(90); // 10% discount applied
});
test('user can complete purchase', async () => {
const user = await createUser();
const cart = await addItemsToCart(user, [item1, item2]);
const order = await checkout(cart);
expect(order.status).toBe('completed');
expect(order.total).toBe(30);
});
// Implement addItemsToCart
test('addItemsToCart adds multiple items', () => {
// ...
});
// Implement checkout
test('checkout creates order', () => {
// ...
});
When working with a spec (.claude/templates/spec.md.template), map tests directly to requirement IDs:
// Test names include REQ ID for traceability
test('REQ-001: user can register with valid email', () => {
const result = register({ email: 'user@example.com', password: 'Str0ng!' });
expect(result.success).toBe(true);
});
test('REQ-002: registration rejects invalid email', () => {
const result = register({ email: 'invalid', password: 'Str0ng!' });
expect(result.error).toBe('INVALID_EMAIL');
});
test('REQ-003: confirmation email is sent after registration', () => {
register({ email: 'user@example.com', password: 'Str0ng!' });
expect(mockMailer.sent).toHaveLength(1);
});
Verification tag mapping:
(TEST) requirements ā Unit/integration tests (Phases 1-2 of test ladder)(BROWSER) requirements ā Playwright/Cypress E2E tests (Phase 3)(MANUAL) requirements ā Human checklist (Phase 4)Mutation testing validates that your tests actually catch bugs ā not just that they run green.
A mutation tool (like Stryker) modifies your code (e.g., changes > to >=, removes a return) and checks if your tests catch the change. If they don't, you have a "surviving mutant" ā a gap in your test coverage.
npx stryker init
npx stryker run --reporters clear-text
Mutation testing [======================] 100% (elapsed: 12s)
Kill count: 42/50
Timeout: 3
Survived: 5 ā These are the gaps to fix
No coverage: 0
Mutation score: 84.00%
Surviving mutants mean your tests don't detect those code changes. Focus on:
> mutated to >= survived ā add edge case test)Don't run mutation testing on everything. Target high-value modules:
# Target specific directories
npx stryker run --mutate 'src/services/payment/**/*.ts'
# Target files implementing Must-priority REQs
npx stryker run --mutate 'src/auth/**/*.ts,src/checkout/**/*.ts'
For spec-driven TDD, prioritize mutation testing on modules that implement Must-priority requirements:
npx stryker run --mutate 'src/path/**/*.ts'When Stryker is configured (stryker.conf.js or stryker.conf.mjs), the /checkpoint command automatically runs mutation testing as an optional step.
Code templates for this domain (in templates/):
test.spec.ts.template ā Unit/integration test with AAA pattern