| name | tdd |
| description | Guide for implementing features using Test-Driven Development (TDD) methodology. Use when: (1) User requests to implement a feature using TDD, (2) User asks to write tests first before implementation, (3) User mentions Red-Green-Refactor cycle, (4) Starting a new feature that requires systematic testing. This skill provides step-by-step TDD workflows, concrete test patterns, and best practices for writing tests before implementation in TypeScript/React projects using Vitest. |
TDD (Test-Driven Development)
Overview
This skill guides you through implementing features using the Test-Driven Development methodology. TDD ensures high code quality, comprehensive test coverage, and designs that emerge from tests.
Core TDD Cycle
๐ด Red โ ๐ข Green โ ๐ต Refactor
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ด Red: Write a Failing Test
- Choose ONE feature to implement
- Write the simplest test for that feature
- Run the test and confirm it fails
- Verify the failure reason is correct
Example:
// Test for a feature that doesn't exist yet
describe('calculateTotal', () => {
it('้
ๅใฎ้้กใๅ่จใใ', () => {
const result = calculateTotal([100, 200, 300])
expect(result).toBe(600)
})
})
// Run: โ FAIL - ReferenceError: calculateTotal is not defined
๐ข Green: Make It Pass (Minimal Implementation)
- Write the minimum code to make the test pass
- Don't add features not covered by tests
- Don't optimize prematurely
Example:
function calculateTotal(amounts: number[]): number {
return amounts.reduce((sum, amount) => sum + amount, 0)
}
// Run: โ
PASS
๐ต Refactor: Improve While Green
- Improve code quality without changing behavior
- Keep all tests passing
- Focus on: DRY, naming, structure, types
When to refactor:
- After tests pass
- Code duplication appears
- Naming can be clearer
- Function is too complex
Detailed guidance: See references/tdd_cycle.md
TDD Workflow
Step 1: Identify the Feature
Break down the feature into small, testable units:
Feature: Savings history display
โ
Sub-features:
- Display list of savings records
- Show records in descending order (newest first)
- Display empty state when no records
- Format dates correctly
Step 2: Write the First Test (Red)
Start with the simplest case:
import { describe, it, expect } from 'vitest'
describe('SavingsHistoryPage', () => {
it('่ฒฏ้่จ้ฒใฎใชในใใ่กจ็คบใใ', () => {
// Arrange
const records = [
{ id: '1', amount: 800, recordedAt: new Date('2025-01-01') }
]
// Act
render(<SavingsHistoryPage records={records} />)
// Assert
expect(screen.getByText('ยฅ800')).toBeInTheDocument()
})
})
// Run: โ FAIL - SavingsHistoryPage is not defined
Step 3: Implement (Green)
export function SavingsHistoryPage({ records }: Props) {
return (
<div>
{records.map(record => (
<div key={record.id}>ยฅ{record.amount}</div>
))}
</div>
)
}
// Run: โ
PASS
Step 4: Add Next Test (Red)
it('่จ้ฒใใชใๅ ดๅใฏใใพใ ่จ้ฒใใใใพใใใใจ่กจ็คบใใ', () => {
render(<SavingsHistoryPage records={[]} />)
expect(screen.getByText('ใพใ ่จ้ฒใใใใพใใ')).toBeInTheDocument()
})
// Run: โ FAIL - Unable to find element
Step 5: Implement (Green)
export function SavingsHistoryPage({ records }: Props) {
if (records.length === 0) {
return <div>ใพใ ่จ้ฒใใใใพใใ</div>
}
return (
<div>
{records.map(record => (
<div key={record.id}>ยฅ{record.amount}</div>
))}
</div>
)
}
// Run: โ
PASS
Step 6: Refactor (Blue)
export function SavingsHistoryPage({ records }: Props) {
if (records.length === 0) {
return <EmptyState />
}
return <RecordList records={records} />
}
// Extracted components for better structure
// Run: โ
PASS (all tests still pass)
Best Practices
1. Small Steps
Good:
- Write 1 test
- Make it pass
- Repeat
Avoid:
- Writing multiple tests before implementing
- Implementing features without tests
2. Test Naming (Japanese)
Use descriptive Japanese names that explain the specification:
describe('SavingsRecordRepository', () => {
describe('ๆญฃๅธธ็ณป', () => {
it('ๆญฃใฎ้้กใงใฌใณใผใใไฝๆใงใใ', () => {})
it('้้กใ0ใฎๅ ดๅใไฝๆใงใใ', () => {})
})
describe('็ฐๅธธ็ณป', () => {
it('่ฒ ใฎ้้กใฎๅ ดๅใฏใจใฉใผใๆใใ', () => {})
it('NaNใฎๅ ดๅใฏใจใฉใผใๆใใ', () => {})
})
})
3. AAA Pattern (Arrange-Act-Assert)
Structure every test with three sections:
it('ใฌใณใผใใไฝๆใใ', async () => {
// Arrange: Setup test data
const repository = new SavingsRecordRepository()
const input = { amount: 800 }
// Act: Execute the function
const result = await repository.create(input)
// Assert: Verify expectations
expect(result.amount).toBe(800)
})
4. One Assertion Concept Per Test
// โ
Good: One concept per test
it('ไฝๆใใใฌใณใผใใฎ้้กใๆญฃใใ', () => {
const result = repository.create({ amount: 800 })
expect(result.amount).toBe(800)
})
it('ไฝๆใใใฌใณใผใใซๆฅๆใ่จ้ฒใใใ', () => {
const result = repository.create({ amount: 800 })
expect(result.recordedAt).toBeInstanceOf(Date)
})
// โ Avoid: Multiple unrelated concepts
it('ใฌใณใผใใๆญฃใใไฝๆใใใ', () => {
const result = repository.create({ amount: 800 })
expect(result.amount).toBe(800)
expect(result.recordedAt).toBeInstanceOf(Date)
expect(result.id).toBeDefined()
})
Test Priority Order
Follow this order when writing tests:
- Happy Path: Most common use case
- Boundary Values: 0, empty, min, max
- Error Cases: Invalid input, exceptions
- Edge Cases: Special scenarios
Example sequence:
// 1. Happy Path
it('ๆญฃใฎ้้กใๅ่จใใ', () => {
expect(calculateTotal([100, 200])).toBe(300)
})
// 2. Boundary
it('็ฉบ้
ๅใฎๅ ดๅใฏ0ใ่ฟใ', () => {
expect(calculateTotal([])).toBe(0)
})
// 3. Error Case
it('NaNใๅซใๅ ดๅใฏใจใฉใผใๆใใ', () => {
expect(() => calculateTotal([NaN])).toThrow()
})
Quick Commands
# Run tests in watch mode (for TDD)
npm run test -- --watch
# Run all tests once
npm run test
# Run tests with coverage
npm run test:coverage
# Run unit tests only
npm run test -- --run --project=unit
TDD Checklist
Before committing:
- All tests pass (Green)
- Tests cover the feature specification
- Test names clearly describe behavior
- No commented-out tests
- Refactoring complete (if needed)
- No implementation without tests
Advanced Patterns
For detailed test patterns and examples:
TDD Cycle Details: references/tdd_cycle.md
- Detailed Red-Green-Refactor workflow
- Timing and rhythm
- Common pitfalls
Test Patterns: references/test_patterns.md
- AAA pattern examples
- Mock patterns
- Async testing
- Edge cases and boundary values
- Data-driven tests
- Anti-patterns to avoid
Notes
- Focus on behavior, not implementation: Test what the code does, not how it does it
- Tests are specification: Test names and assertions should clearly document expected behavior
- Small cycles: 5-15 minutes per Red-Green-Refactor cycle
- Project conventions: Follow commit message format in
/.claude/CLAUDE.md