Tdd Workflow Smart
Comprehensive skill designed for test, driven, development, workflow. Includes structured workflows, validation checks, and reusable patterns for development.
TDD Workflow Smart
A disciplined skill for practicing Test-Driven Development with the Red-Green-Refactor cycle. Covers writing failing tests first, implementing minimal passing code, refactoring safely, and applying TDD to different types of software (APIs, UIs, algorithms).
When to Use This Skill
Choose this skill when:
- Building new features using the TDD methodology
- Writing regression tests for bugs before fixing them
- Practicing the Red-Green-Refactor cycle for better design
- Teaching TDD workflow to team members
- Ensuring comprehensive test coverage drives implementation
Consider alternatives when:
- Working with existing untested code → use a test-fixing skill
- Need test framework setup → use a testing patterns skill
- Debugging production issues → use a debugging skill
- Writing tests for visual/UI components → use a component testing skill
Quick Start
// The TDD Cycle in Practice // STEP 1: RED — Write a failing test describe('calculateDiscount', () => { it('should apply 10% discount for orders over $100', () => { expect(calculateDiscount(150)).toBe(135); // function doesn't exist yet }); }); // Run test → FAILS (function not defined) ✅ This is correct! // STEP 2: GREEN — Write minimal code to pass function calculateDiscount(amount: number): number { if (amount > 100) return amount * 0.9; return amount; } // Run test → PASSES ✅ // STEP 3: REFACTOR — Improve without changing behavior const DISCOUNT_THRESHOLD = 100; const DISCOUNT_RATE = 0.1; function calculateDiscount(amount: number): number { if (amount <= DISCOUNT_THRESHOLD) return amount; return amount * (1 - DISCOUNT_RATE); } // Run test → STILL PASSES ✅ // STEP 4: Add next test case and repeat it('should not apply discount for orders under $100', () => { expect(calculateDiscount(50)).toBe(50); });
Core Concepts
TDD Decision Tree
| Scenario | TDD Approach |
|---|---|
| New feature | Write acceptance test → implement piece by piece |
| Bug fix | Write test reproducing the bug → fix → verify test passes |
| Refactoring | Ensure tests exist → refactor → verify tests still pass |
| API endpoint | Write request/response test → implement handler |
| Algorithm | Write input/output cases → implement algorithm |
| UI component | Write render + interaction tests → build component |
Test Case Design Strategies
// Boundary value analysis describe('ageCategory', () => { // Exact boundaries it('infant at 0', () => expect(ageCategory(0)).toBe('infant')); it('infant at 1', () => expect(ageCategory(1)).toBe('infant')); it('child at 2', () => expect(ageCategory(2)).toBe('child')); it('child at 12', () => expect(ageCategory(12)).toBe('child')); it('teen at 13', () => expect(ageCategory(13)).toBe('teen')); it('adult at 18', () => expect(ageCategory(18)).toBe('adult')); // Edge cases it('throws for negative age', () => { expect(() => ageCategory(-1)).toThrow('Age cannot be negative'); }); }); // Equivalence partitioning describe('validatePassword', () => { // Valid partition it('accepts valid password', () => expect(validatePassword('Str0ng!Pass')).toBe(true)); // Invalid partitions it('rejects too short', () => expect(validatePassword('Ab1!')).toBe(false)); it('rejects no uppercase', () => expect(validatePassword('weakpass1!')).toBe(false)); it('rejects no number', () => expect(validatePassword('NoNumbers!')).toBe(false)); });
Outside-In TDD for Features
// Start with acceptance test (outer boundary) describe('POST /api/orders', () => { it('creates an order and returns 201 with order details', async () => { const res = await request(app) .post('/api/orders') .send({ productId: 'abc', quantity: 2 }) .expect(201); expect(res.body).toMatchObject({ id: expect.any(String), status: 'pending', items: [{ productId: 'abc', quantity: 2 }], }); }); }); // This drives: route handler → service → repository → database schema // Then write unit tests for each inner layer describe('OrderService', () => { it('calculates total from product prices', () => { const service = new OrderService(mockProductRepo); const order = service.createOrder([{ productId: 'abc', quantity: 2 }]); expect(order.total).toBe(59.98); // 29.99 * 2 }); });
Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
testRunner | string | 'vitest' | Test runner: vitest, jest, or mocha |
tddStyle | string | 'outside-in' | Style: outside-in (London) or inside-out (Chicago) |
refactorThreshold | number | 3 | Refactor after N passing tests |
coverageTarget | number | 90 | Target code coverage percentage |
assertionStyle | string | 'expect' | Assertion: expect, assert, or should |
mockStrategy | string | 'minimal' | Mocking: minimal, comprehensive, or none |
Best Practices
-
Write the test before the code — always — The temptation to "just write the function first" defeats the purpose. The failing test defines what success looks like. If you write code first, you're writing tests that confirm existing behavior, not driving design.
-
Keep the Green step minimal — Write the simplest, ugliest code that makes the test pass. Hardcode values if needed. The refactor step is where you improve design. Premature abstraction in the Green step leads to over-engineering.
-
Run all tests after every change — Not just the test you're working on. Refactoring can break other tests. Fast test suites make this feasible; if your tests are too slow, invest in speeding them up.
-
One assertion per test for clarity — When a test fails, the reason should be obvious from the test name. Multiple assertions hide which behavior broke. Separate concerns into separate test cases.
-
Refactor only when all tests are green — Never refactor failing code. Get to green first (even with ugly code), then clean up with the safety net of passing tests. This prevents introducing bugs during restructuring.
Common Issues
TDD feels slow for simple CRUD operations — Not everything benefits equally from TDD. For boilerplate CRUD with no business logic, testing after implementation is reasonable. Apply TDD rigorously where business logic, complex conditions, or algorithms exist.
Tests become tightly coupled to implementation — Testing internal methods, asserting on specific function calls, and mocking everything creates brittle tests that break on every refactor. Test behavior (inputs → outputs), not implementation details.
Refactor step gets skipped under time pressure — Skipping refactoring accumulates technical debt rapidly. The refactor step is where design emerges. Budget time for it in estimates and treat it as non-optional.
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.