Skip to main content

test-driven-development

Use when writing production code. Enforces RED-GREEN-REFACTOR cycle: write failing test, make it pass, improve design. Prevents test-after development and ensures verified behavior.

ソース情報

リポジトリ
MadAppGang/claude-code
ソースの最終更新活動
2026年1月20日 08:16
検出された SKILL.md の言語
英語
スター
286
フォーク
24

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
test-driven-development
description
Use when writing production code. Enforces RED-GREEN-REFACTOR cycle: write failing test, make it pass, improve design. Prevents test-after development and ensures verified behavior.
keywords
["TDD","test-driven","red-green-refactor","failing-test","unit-test","integration-test","test-first","jest","vitest","pytest","bun-test","go-test","test-coverage","test-cases","behavior-verification","implementation-without-tests"]
created
2026-01-20T00:00:00.000Z
updated
2026-01-20T00:00:00.000Z
plugin
dev
type
discipline
difficulty
beginner
# Test-Driven Development (TDD) **Iron Law:** "NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST" ## When to Use Use this skill when: - Writing new functions, methods, or classes - Adding features to existing code - Fixing bugs (write test that reproduces bug first) - Refactoring code (tests verify behavior preservation) - Implementing API endpoints or business logic - Creating UI components with testable behavior ## Red Flags (Violation Indicators) Watch for these patterns that indicate TDD violations: - [ ] **Implementation First** - Writing production code before any test exists ("I'll write the function, then add tests") - [ ] **"Tests After" Promise** - Planning to write tests later ("Let me implement this quickly, I'll add tests after") - [ ] **"Same Purpose" Rationalization** - Claiming manual testing is equivalent ("I tested it manually, that's the same thing") - [ ] **Skipping "Simple" Code** - Avoiding tests for "obvious" logic ("This function is too simple to test") - [ ] **Happy Path Only** - Writing tests only for success cases, ignoring errors ("The normal case works, that's enough") - [ ] **No Test for Changes** - Modifying code without adding corresponding test ("Just a small change, doesn't need a test") - [ ] **"Just a Small Fix"** - Bypassing TDD for "quick fixes" ("It's only one line, I don't need a test") **Violation Detection:** If you find yourself saying "I'll test this after I get it working," you're violating TDD. ## RED-GREEN-REFACTOR Cycle TDD follows a strict 3-phase workflow: ### Phase 1: RED (Write a Failing Test) **Objective:** Specify desired behavior through a test that fails **Steps:** 1. **Write the test first** - Before any production code exists 2. **Define expected behavior** - What should the code do? 3. **Use API you wish existed** - Test the interface you want 4. **Run the test** - Verify it fails (if it passes, you didn't test new behavior) 5. **Check failure reason** - Ensure it fails for the right reason (missing code, not syntax error) **Example (TypeScript/Jest):** ```typescript // tests/user-validator.test.ts describe('UserValidator', () => { it('rejects email without @ symbol', () => { const validator = new UserValidator(); const result = validator.validateEmail('invalid-email'); expect(result.isValid).toBe(false); expect(result.error).toBe('Email must contain @ symbol'); }); }); // Run: npm test // Result: FAIL - UserValidator is not defined ✓ (correct failure) ``` **Red Phase Complete When:** Test fails with expected error message --- ### Phase 2: GREEN (Make the Test Pass) **Objective:** Write minimal code to make test pass **Steps:** 1. **Write minimal code** - Just enough to pass the test 2. **Don't optimize yet** - Resist the urge to add "nice-to-have" features 3. **Run the test** - Verify it passes 4. **Commit frequently** - Small, passing test = commit point **Example (TypeScript):** ```typescript // src/user-validator.ts interface ValidationResult { isValid: boolean; error?: string; } export class UserValidator { validateEmail(email: string): ValidationResult { if (!email.includes('@')) { return { isValid: false, error: 'Email must contain @ symbol' }; } return { isValid: true }; } } // Run: npm test // Result: PASS ✓ ``` **Green Phase Complete When:** Test passes consistently --- ### Phase 3: REFACTOR (Improve Design) **Objective:** Improve code quality while keeping tests green **Steps:** 1. **Look for duplication** - Extract repeated code 2. **Improve naming** - Make intent clearer 3. **Simplify logic** - Reduce complexity 4. **Run tests after each change** - Ensure behavior preserved 5. **Commit when satisfied** - Refactored code + passing tests = commit **Example (TypeScript - Refactored):** ```typescript // src/user-validator.ts interface ValidationResult { isValid: boolean; error?: string; } export class UserValidator { private static readonly EMAIL_REQUIRED_CHARS = '@'; private static readonly EMAIL_ERROR = 'Email must contain @ symbol'; validateEmail(email: string): ValidationResult { if (!this.containsRequiredChars(email)) { return this.createError(UserValidator.EMAIL_ERROR); } return this.createSuccess(); } private containsRequiredChars(email: string): boolean { return email.includes(UserValidator.EMAIL_REQUIRED_CHARS); } private createError(message: string): ValidationResult { return { isValid: false, error: message }; } private createSuccess(): ValidationResult { return { isValid: true }; } } // Run: npm test // Result: PASS ✓ (behavior unchanged) ``` **Refactor Phase Complete When:** Code is clean AND tests still pass --- ## Anti-patterns Table | Anti-pattern | ✗ Wrong Approach | ✓ Correct TDD Approach | |--------------|------------------|------------------------| | **Test-After** | Write `calculateTotal()` function, then write tests | Write test for `calculateTotal()`, see it fail, implement function | | **Empty Tests** | Write test that always passes: `expect(true).toBe(true)` | Write test that fails until production code is correct | | **Happy-Path-Only** | Test only valid inputs: `validateEmail('user@example.com')` | Test invalid inputs too: `validateEmail('no-at-sign')`, `validateEmail('')` | | **Skip-Simple** | Skip test for "obvious" `add(a, b) { return a + b }` | Write test: `expect(add(2, 3)).toBe(5)` - bugs hide in "simple" code | | **Change-Then-Test** | Modify `calculateDiscount()`, run app manually, then add test | Write failing test showing bug, modify code until test passes | ## Testing Strategy by Code Type ### Pure Functions **Pattern:** 1 happy path + 3 edge cases minimum **Example (TypeScript):** ```typescript describe('calculateDiscount', () => { it('applies 10% discount to $100 purchase', () => { expect(calculateDiscount(100, 0.1)).toBe(90); }); it('returns 0 for negative amounts', () => { expect(calculateDiscount(-50, 0.1)).toBe(0); }); it('returns original amount for 0 discount', () => { expect(calculateDiscount(100, 0)).toBe(100); }); it('throws error for discount > 1', () => { expect(() => calculateDiscount(100, 1.5)).toThrow('Discount must be <= 1'); }); }); ``` --- ### Error Handlers **Pattern:** 1 test per error type + 1 success case **Example (Python/pytest):** ```python def test_divide_by_zero_raises_error(): with pytest.raises(ZeroDivisionError, match="Cannot divide by zero"): divide(10, 0) def test_divide_non_numeric_raises_error(): with pytest.raises(TypeError, match="Arguments must be numbers"): divide("10", 5) def test_divide_returns_float(): result = divide(10, 3) assert result == pytest.approx(3.333, rel=1e-3) ``` --- ### UI Components **Pattern:** 1 render test + 3 interaction tests **Example (TypeScript/React Testing Library):** ```typescript describe('LoginForm', () => { it('renders email and password inputs', () => { render(<LoginForm />); expect(screen.getByLabelText('Email')).toBeInTheDocument(); expect(screen.getByLabelText('Password')).toBeInTheDocument(); }); it('shows error for invalid email', async () => { render(<LoginForm />); await userEvent.type(screen.getByLabelText('Email'), 'invalid'); await userEvent.click(screen.getByRole('button', { name: 'Login' })); expect(screen.getByText('Invalid email address')).toBeInTheDocument(); }); it('disables submit button while loading', async () => { render(<LoginForm onSubmit={async () => await delay(1000)} />); const submitButton = screen.getByRole('button', { name: 'Login' }); await userEvent.click(submitButton); expect(submitButton).toBeDisabled(); }); it('calls onSubmit with form data', async () => { const onSubmit = jest.fn(); render(<LoginForm onSubmit={onSubmit} />); await userEvent.type(screen.getByLabelText('Email'), 'user@example.com'); await userEvent.type(screen.getByLabelText('Password'), 'password123'); await userEvent.click(screen.getByRole('button', { name: 'Login' })); expect(onSubmit).toHaveBeenCalledWith({ email: 'user@example.com', password: 'password123' }); }); }); ``` --- ### API Endpoints **Pattern:** 1 success test + 2 error tests **Example (Go/testing):** ```go func TestGetUser(t *testing.T) { t.Run("returns user for valid ID", func(t *testing.T) { req := httptest.NewRequest("GET", "/users/123", nil) w := httptest.NewRecorder() handler := NewUserHandler(mockUserRepo) handler.GetUser(w, req) assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), `"id":"123"`) }) t.Run("returns 404 for non-existent user", func(t *testing.T) { req := httptest.NewRequest("GET", "/users/999", nil) w := httptest.NewRecorder() handler := NewUserHandler(mockUserRepo) handler.GetUser(w, req) assert.Equal(t, http.StatusNotFound, w.Code) }) t.Run("returns 400 for invalid ID format", func(t *testing.T) { req := httptest.NewRequest("GET", "/users/invalid", nil) w := httptest.NewRecorder() handler := NewUserHandler(mockUserRepo) handler.GetUser(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) }) } ``` --- ## Examples ### Example 1: Bug Fix with TDD (TypeScript) **Scenario:** User reports: "App crashes when searching with empty query" **Step 1 (RED): Write test that reproduces bug** ```typescript // tests/search.test.ts describe('searchProducts', () => { it('returns empty array for empty query', () => { const result = searchProducts(''); expect(result).toEqual([]); }); }); // Run: npm test // Result: FAIL - TypeError: Cannot read property 'toLowerCase' of undefined ``` **Step 2 (GREEN): Fix the bug** ```typescript // src/search.ts export function searchProducts(query: string): Product[] { if (!query || query.trim() === '') { return []; } return products.filter(p => p.name.toLowerCase().includes(query.toLowerCase())); } // Run: npm test // Result: PASS ✓ ``` **Step 3 (REFACTOR): Add more edge cases** ```typescript describe('searchProducts', () => { it('returns empty array for empty query', () => { expect(searchProducts('')).toEqual([]); }); it('returns empty array for whitespace-only query', () => { expect(searchProducts(' ')).toEqual([]);
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る