U

Ultimate Test-driven Development Engine

Enterprise-ready skill that automates tDD workflow for implementing features and bugfixes. Built for Claude Code with best practices and real-world patterns.

SkillCommunitytestingv1.0.0MIT
0 views0 copies

Test-Driven Development Engine

A software development methodology skill for practicing TDD with the Red-Green-Refactor cycle, writing effective test suites, and building quality software through test-first development.

When to Use

Choose Test-Driven Development when:

  • Building new features where requirements are clear and testable
  • Refactoring complex code that needs safety net tests before modification
  • Developing business logic with well-defined inputs and outputs
  • Training teams on disciplined development practices

Consider alternatives when:

  • Prototyping or exploring solutions — test after the approach is validated
  • UI-heavy work — use visual testing and component testing instead
  • Integration with external APIs — mock extensively or test at integration level

Quick Start

# Set up test framework npm install -D vitest @testing-library/react @testing-library/jest-dom
// TDD Example: Building a shopping cart // Step 1: RED - Write failing test import { describe, it, expect } from 'vitest'; import { ShoppingCart } from './ShoppingCart'; describe('ShoppingCart', () => { it('starts empty', () => { const cart = new ShoppingCart(); expect(cart.items).toEqual([]); expect(cart.total).toBe(0); }); it('adds items with quantity', () => { const cart = new ShoppingCart(); cart.addItem({ id: '1', name: 'Widget', price: 9.99 }, 2); expect(cart.items).toHaveLength(1); expect(cart.items[0].quantity).toBe(2); expect(cart.total).toBe(19.98); }); it('increases quantity when adding existing item', () => { const cart = new ShoppingCart(); cart.addItem({ id: '1', name: 'Widget', price: 9.99 }, 1); cart.addItem({ id: '1', name: 'Widget', price: 9.99 }, 3); expect(cart.items).toHaveLength(1); expect(cart.items[0].quantity).toBe(4); }); it('removes items', () => { const cart = new ShoppingCart(); cart.addItem({ id: '1', name: 'Widget', price: 9.99 }, 2); cart.removeItem('1'); expect(cart.items).toHaveLength(0); expect(cart.total).toBe(0); }); it('applies percentage discount', () => { const cart = new ShoppingCart(); cart.addItem({ id: '1', name: 'Widget', price: 100 }, 1); cart.applyDiscount({ type: 'percentage', value: 10 }); expect(cart.total).toBe(90); }); it('does not allow negative totals', () => { const cart = new ShoppingCart(); cart.addItem({ id: '1', name: 'Widget', price: 10 }, 1); cart.applyDiscount({ type: 'fixed', value: 50 }); expect(cart.total).toBe(0); }); }); // Step 2: GREEN - Implement minimum code to pass interface Product { id: string; name: string; price: number; } interface CartItem { product: Product; quantity: number; } interface Discount { type: 'percentage' | 'fixed'; value: number; } export class ShoppingCart { private _items: CartItem[] = []; private _discount: Discount | null = null; get items() { return this._items.map(i => ({ ...i.product, quantity: i.quantity })); } get total() { const subtotal = this._items.reduce( (sum, item) => sum + item.product.price * item.quantity, 0 ); if (!this._discount) return Math.round(subtotal * 100) / 100; if (this._discount.type === 'percentage') { return Math.max(0, Math.round(subtotal * (1 - this._discount.value / 100) * 100) / 100); } return Math.max(0, Math.round((subtotal - this._discount.value) * 100) / 100); } addItem(product: Product, quantity: number = 1) { const existing = this._items.find(i => i.product.id === product.id); if (existing) { existing.quantity += quantity; } else { this._items.push({ product, quantity }); } } removeItem(productId: string) { this._items = this._items.filter(i => i.product.id !== productId); } applyDiscount(discount: Discount) { this._discount = discount; } } // Step 3: REFACTOR - Improve code while keeping tests green

Core Concepts

TDD Cycle

PhaseActionGoal
RedWrite a failing testDefine expected behavior
GreenWrite minimum code to passMake the test pass
RefactorImprove code structureClean up without breaking tests
RepeatNext test caseIncrementally build features

Test Quality Principles

// GOOD: Tests describe behavior, not implementation describe('UserService', () => { it('registers a new user with hashed password', async () => { const result = await userService.register('[email protected]', 'password123'); expect(result.email).toBe('[email protected]'); expect(result.password).not.toBe('password123'); // Hashed expect(result.id).toBeDefined(); }); it('rejects duplicate email registration', async () => { await userService.register('[email protected]', 'password123'); await expect( userService.register('[email protected]', 'different') ).rejects.toThrow('Email already registered'); }); }); // BAD: Tests are coupled to implementation details describe('UserService', () => { it('calls bcrypt.hash with rounds=10', async () => { await userService.register('[email protected]', 'password123'); expect(bcrypt.hash).toHaveBeenCalledWith('password123', 10); }); });

Configuration

OptionDescriptionDefault
test_frameworkTesting framework: vitest, jest, mocha"vitest"
coverage_thresholdMinimum code coverage80
watch_modeRun tests on file changetrue
test_patternTest file naming pattern"*.test.ts"
setup_fileTest setup/configuration file"test/setup.ts"
timeoutTest timeout in milliseconds5000
parallelRun test files in paralleltrue
verboseVerbose test outputfalse

Best Practices

  1. Write the test before the implementation to ensure you are testing behavior rather than implementation — if you write tests after code, you tend to test what the code does rather than what it should do
  2. Keep tests small and focused with one assertion per test case (or one logical assertion group) — when a test fails, you should immediately know what behavior is broken without reading through complex test logic
  3. Use descriptive test names that read like documentation: "should reject registration with invalid email" tells you exactly what the test verifies and serves as a specification when reading test output
  4. Refactor only when all tests pass (green phase) — never refactor while tests are failing because you lose the safety net that tells you if refactoring introduces regressions
  5. Test behavior, not implementation — tests that verify internal method calls or private state are brittle and break on every refactor; test the public interface and observable outcomes instead

Common Issues

Tests passing but code has bugs: Tests may be too focused on happy paths. Add edge case tests for null inputs, empty collections, boundary values, and error conditions systematically using boundary value analysis and equivalence partitioning.

Test suite becoming slow: Hundreds of tests with database or network calls take minutes to run. Mock external dependencies, use in-memory databases for repository tests, and separate fast unit tests from slower integration tests so developers can run unit tests continuously.

Difficulty testing legacy code without tests: Legacy code was not designed for testability and has tightly coupled dependencies. Start by writing characterization tests that document current behavior (even if buggy), then extract interfaces and inject dependencies incrementally to make the code testable.

Community

Reviews

Write a review

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

Similar Templates