ワンクリックで
test-driven-development
Use when implementing any feature or bugfix, before writing implementation code
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Use when implementing any feature or bugfix, before writing implementation code
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
MUST USE for ANY git operations. Enforces GPG-signed commits (-S flag always required; stop and ask user to unlock GPG agent on failure) and 50/72 commit message rule. Triggers: 'commit', 'rebase', 'squash', 'who wrote', 'when was X added', 'find the commit that'.
You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.
Use when facing 2+ independent tasks that can be worked on without shared state or sequential dependencies
Use when you have a written implementation plan to execute in a separate session with review checkpoints
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Use when executing implementation plans with independent tasks in the current session
| name | test-driven-development |
| description | Use when implementing any feature or bugfix, before writing implementation code |
Write the test first. Watch it fail. Write minimal code to pass.
Core principle: If you didn't watch the test fail, you don't know if it tests the right thing.
Violating the letter of the rules is violating the spirit of the rules.
Always:
Exceptions (ask your human partner):
Thinking "skip TDD just this once"? Stop. That's rationalization.
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
Write code before the test? Delete it. Start over.
No exceptions:
Implement fresh from tests. Period.
BEFORE writing your first test, understand what you're building:
Why this matters: Writing tests without context = testing the wrong thing or duplicating existing code.
Example:
// ❌ BAD: Jump straight to test without checking codebase
test('validates email format', () => {
expect(isValidEmail('test@example.com')).toBe(true);
});
// Codebase already has EmailValidator class you didn't find!
// ✅ GOOD: After 2-min search, found existing validator
test('validates email using EmailValidator', () => {
const validator = new EmailValidator();
expect(validator.isValid('test@example.com')).toBe(true);
});
// Consistent with codebase patterns
When to skip: Greenfield projects with no existing code. Otherwise, always understand context first.
After writing tests and minimal code, maintain quality through these practices:
| Principle | Guideline | Example |
|---|---|---|
| Follow established patterns | Match existing code style, structure, organization | ✅ Use same error handling pattern as rest of codebase ❌ Introduce new error pattern |
| Maintain consistency | Same naming conventions, file structure, module organization | ✅ getUserById() matches getPostById() ❌ fetchUser() when others use get*() |
| Keep functions focused | Single responsibility, one clear purpose | ✅ validateEmail() ❌ validateAndSaveEmail() |
| Prefer autonomous methods | Methods that don't rely on external state | ✅ Pure functions when possible ❌ Hidden dependencies |
After implementing each feature:
Avoid redundant work by understanding upfront:
| Type | Avoid | Instead |
|---|---|---|
| Comments | Explain WHAT code does (readable from code) | Explain WHY (business logic, tradeoffs, gotchas) |
| Tests | Multiple tests for same behavior | Consolidate similar tests, use parameterized tests |
| Code | Duplicate implementations | Understand constraints first, write once correctly |
| Rewrites | Multiple attempts due to misunderstanding | Read existing code, clarify requirements BEFORE coding |
Example:
// ❌ BAD: Comment explains WHAT (redundant with code)
// Check if user is authenticated
if (user.token && user.token.expiresAt > Date.now()) {
// ...
}
// ✅ GOOD: Comment explains WHY (adds context)
// Must check token expiry here, not in middleware, because
// admin routes bypass middleware but still need validation
if (user.token && user.token.expiresAt > Date.now()) {
// ...
}
Test consolidation:
// ❌ BAD: Redundant tests
test('validates email with @', () => { /*...*/ });
test('validates email with domain', () => { /*...*/ });
test('validates email with TLD', () => { /*...*/ });
// ✅ GOOD: Parameterized test
test.each([
['valid@example.com', true],
['missing-at.com', false],
['no-domain@', false],
['no-tld@example', false],
])('validates email format: %s → %s', (email, expected) => {
expect(isValidEmail(email)).toBe(expected);
});
flowchart LR
A[UNDERSTAND<br/>Context check]
B[RED<br/>Write failing test]
C{Verify fails<br/>correctly}
D[GREEN<br/>Minimal code]
E{Verify passes<br/>All green}
F[REFACTOR<br/>Clean up]
G((Next))
A --> B
B --> C
C -->|yes| D
C -->|wrong failure| B
D --> E
E -->|yes| F
E -->|no| D
F -->|stay green| E
E -->|next| G
G --> B
style A fill:#ffffcc
style B fill:#ffcccc
style D fill:#ccffcc
style F fill:#ccccff
Write one minimal test showing what should happen.
```typescript test('retries failed operations 3 times', async () => { let attempts = 0; const operation = () => { attempts++; if (attempts < 3) throw new Error('fail'); return 'success'; };const result = await retryOperation(operation);
expect(result).toBe('success'); expect(attempts).toBe(3); });
Clear name, tests real behavior, one thing
</Good>
<Bad>
```typescript
test('retry works', async () => {
const mock = jest.fn()
.mockRejectedValueOnce(new Error())
.mockRejectedValueOnce(new Error())
.mockResolvedValueOnce('success');
await retryOperation(mock);
expect(mock).toHaveBeenCalledTimes(3);
});
Vague name, tests mock not code
Requirements:
MANDATORY. Never skip.
npm test path/to/test.test.ts
Confirm:
Test passes? You're testing existing behavior. Fix test.
Test errors? Fix error, re-run until it fails correctly.
Write simplest code to pass the test.
```typescript async function retryOperation(fn: () => Promise): Promise { for (let i = 0; i < 3; i++) { try { return await fn(); } catch (e) { if (i === 2) throw e; } } throw new Error('unreachable'); } ``` Just enough to pass ```typescript async function retryOperation( fn: () => Promise, options?: { maxRetries?: number; backoff?: 'linear' | 'exponential'; onRetry?: (attempt: number) => void; } ): Promise { // YAGNI } ``` Over-engineeredDon't add features, refactor other code, or "improve" beyond the test.
MANDATORY.
npm test path/to/test.test.ts
Confirm:
Test fails? Fix code, not test.
Other tests fail? Fix now.
After green only:
Keep tests green. Don't add behavior.
Next failing test for next feature.
| Quality | Good | Bad |
|---|---|---|
| Minimal | One thing. "and" in name? Split it. | test('validates email and domain and whitespace') |
| Clear | Name describes behavior | test('test1') |
| Shows intent | Demonstrates desired API | Obscures what code should do |
Test data should mirror production reality:
| Principle | Guideline | Example |
|---|---|---|
| Mirrors production | Use realistic data structures, values, formats | ✅ {email: "user@example.com", age: 28} ❌ {email: "x", age: 1} |
| Full transformation chain | Test data through entire processing pipeline | ✅ Input → validate → transform → store → retrieve ❌ Test only transform step |
| Satisfies all constraints | Data passes ALL validation rules | ✅ Check required fields, formats, ranges ❌ Only test happy path |
| Includes edge cases | Empty strings, nulls, boundary values, special chars | ✅ ["", null, 0, -1, "🔥"] ❌ Only "test" |
Why this matters: Tests with toy data (x, y, foo, bar) pass but real data fails. Production-quality test data catches real bugs.
Example:
// ❌ BAD: Toy data that doesn't reflect reality
test('processes user', () => {
const user = { name: 'x', email: 'y' };
expect(process(user)).toBeTruthy();
});
// Missing: age validation, email format, required fields
// ✅ GOOD: Production-realistic data
test('processes valid user through full pipeline', () => {
const user = {
name: 'Alice Smith',
email: 'alice.smith@example.com',
age: 28,
preferences: { newsletter: true }
};
const result = process(user);
expect(result.id).toMatch(/^usr_[a-z0-9]+$/);
expect(result.name).toBe('Alice Smith');
expect(result.email).toBe('alice.smith@example.com');
});
test('rejects user with invalid email format', () => {
const user = {
name: 'Bob Jones',
email: 'not-an-email', // Edge case: malformed
age: 35
};
expect(() => process(user)).toThrow('Invalid email format');
});
test('handles edge cases in user data', () => {
const edgeCases = [
{ name: '', email: 'test@example.com', age: 18 }, // Empty name
{ name: 'Test', email: '', age: 18 }, // Empty email
{ name: 'Test', email: 'test@example.com', age: 0 }, // Boundary age
{ name: 'Test', email: 'test@example.com', age: null }, // Null age
];
edgeCases.forEach(user => {
expect(() => process(user)).toThrow();
});
});
"I'll write tests after to verify it works"
Tests written after code pass immediately. Passing immediately proves nothing:
Test-first forces you to see the test fail, proving it actually tests something.
"I already manually tested all the edge cases"
Manual testing is ad-hoc. You think you tested everything but:
Automated tests are systematic. They run the same way every time.
"Deleting X hours of work is wasteful"
Sunk cost fallacy. The time is already gone. Your choice now:
The "waste" is keeping code you can't trust. Working code without real tests is technical debt.
"TDD is dogmatic, being pragmatic means adapting"
TDD IS pragmatic:
"Pragmatic" shortcuts = debugging in production = slower.
"Tests after achieve the same goals - it's spirit not ritual"
No. Tests-after answer "What does this do?" Tests-first answer "What should this do?"
Tests-after are biased by your implementation. You test what you built, not what's required. You verify remembered edge cases, not discovered ones.
Tests-first force edge case discovery before implementing. Tests-after verify you remembered everything (you didn't).
30 minutes of tests after ≠ TDD. You get coverage, lose proof tests work.
| Excuse | Reality |
|---|---|
| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
| "I'll test after" | Tests passing immediately prove nothing. |
| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" |
| "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. |
| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. |
| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. |
| "Need to explore first" | Fine. Throw away exploration, start with TDD. |
| "Test hard = design unclear" | Listen to test. Hard to test = hard to use. |
| "TDD will slow me down" | TDD faster than debugging. Pragmatic = test-first. |
| "Manual test faster" | Manual doesn't prove edge cases. You'll re-test every change. |
| "Existing code has no tests" | You're improving it. Add tests for existing code. |
All of these mean: Delete code. Start over with TDD.
Bug: Empty email accepted
RED
test('rejects empty email', async () => {
const result = await submitForm({ email: '' });
expect(result.error).toBe('Email required');
});
Verify RED
$ npm test
FAIL: expected 'Email required', got undefined
GREEN
function submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: 'Email required' };
}
// ...
}
Verify GREEN
$ npm test
PASS
REFACTOR Extract validation for multiple fields if needed.
Before marking work complete:
Can't check all boxes? You skipped TDD. Start over.
| Problem | Solution |
|---|---|
| Don't know how to test | Write wished-for API. Write assertion first. Ask your human partner. |
| Test too complicated | Design too complicated. Simplify interface. |
| Must mock everything | Code too coupled. Use dependency injection. |
| Test setup huge | Extract helpers. Still complex? Simplify design. |
Bug found? Write failing test reproducing it. Follow TDD cycle. Test proves fix and prevents regression.
Never fix bugs without a test.
When adding mocks or test utilities, read testing-anti-patterns.md to avoid common pitfalls:
Production code → test exists and failed first
Otherwise → not TDD
No exceptions without your human partner's permission.