T

Tdd Workflow Smart

Comprehensive skill designed for test, driven, development, workflow. Includes structured workflows, validation checks, and reusable patterns for development.

SkillClipticsdevelopmentv1.0.0MIT
0 views0 copies

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

ScenarioTDD Approach
New featureWrite acceptance test → implement piece by piece
Bug fixWrite test reproducing the bug → fix → verify test passes
RefactoringEnsure tests exist → refactor → verify tests still pass
API endpointWrite request/response test → implement handler
AlgorithmWrite input/output cases → implement algorithm
UI componentWrite 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

ParameterTypeDefaultDescription
testRunnerstring'vitest'Test runner: vitest, jest, or mocha
tddStylestring'outside-in'Style: outside-in (London) or inside-out (Chicago)
refactorThresholdnumber3Refactor after N passing tests
coverageTargetnumber90Target code coverage percentage
assertionStylestring'expect'Assertion: expect, assert, or should
mockStrategystring'minimal'Mocking: minimal, comprehensive, or none

Best Practices

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

Community

Reviews

Write a review

No reviews yet. Be the first to review this template!

Similar Templates