🛠️ 開発・MCP コミュニティ
tdd-enforcer
Use when implementing new features. Enforces TDD workflow - write tests FIRST, then implementation. Ensures AAA pattern, proper coverage, and quality test design.
⚡ おすすめ: コマンド1行でインストール(60秒)
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o tdd-enforcer.zip https://jpskill.com/download/17474.zip && unzip -o tdd-enforcer.zip && rm tdd-enforcer.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/17474.zip -OutFile "$d\tdd-enforcer.zip"; Expand-Archive "$d\tdd-enforcer.zip" -DestinationPath $d -Force; ri "$d\tdd-enforcer.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
tdd-enforcer.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
tdd-enforcerフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
TDD ワークフローの強制
使用する場面
- 新機能の実装
- 機能の追加
- バグの修正
- コードのリファクタリング
TDD プロセス (必須)
1. 最初にテストを書く (RED フェーズ)
- テストを通じて動作を定義する
- AAA パターン (Arrange, Act, Assert) を使用する
- テストは最初は必ず失敗しなければならない
- 明確なテスト名は、期待される動作を記述する
2. テストの失敗を確認する (確認)
- テストを実行する:
npm test - 正しい理由で失敗することを確認する
- テストは、構文エラーのためではなく、機能が存在しないために失敗する必要がある
3. 実装を書く (GREEN フェーズ)
- テストに合格するために最小限のコードを書く
- 過剰な装飾や余分な機能は行わない
- テストに合格することのみに集中する
4. テストの合格を確認する (検証)
- テストを実行する:
npm test - すべての新しいテストはグリーンでなければならない
- すべての既存のテストは、引き続き合格しなければならない
5. リファクタリング (REFACTOR フェーズ)
- コード品質を向上させる
- 重複を削除する
- 可読性を高める
- テストは全体を通してグリーンを維持する
カバレッジ要件
- 全体: 75%以上
- ビジネスロジック (src/services/): 90%以上
- ユーティリティ (src/utils/): 90%以上
- UI コンポーネント: 60%以上
- 重要なユーザーフローに対する E2E テスト
AAA パターン (Arrange, Act, Assert)
describe('AuthService', () => {
describe('register', () => {
it('should create user with hashed password', async () => {
// ARRANGE: テストデータをセットアップ
const userData = {
email: 'test@example.com',
password: 'Pass123!',
}
// ACT: 動作を実行
const result = await authService.register(userData)
// ASSERT: 結果を検証
expect(result.id).toBeDefined()
expect(result.email).toBe(userData.email)
expect(result).not.toHaveProperty('password') // パスワードは決して返さない
})
it('should reject weak passwords', async () => {
// ARRANGE
const userData = {
email: 'test@example.com',
password: '123', // 弱すぎる
}
// ACT & ASSERT
await expect(authService.register(userData)).rejects.toThrow(
'Password must be at least 8 characters'
)
})
})
})
テスト構造
Describe ブロック
// ✅ DO: モジュール/クラスごとに整理する
describe('UserService', () => {
// ✅ DO: メソッドごとに整理する
describe('findById', () => {
it('should return user when found', () => {})
it('should return null when not found', () => {})
it('should throw error for invalid id', () => {})
})
describe('create', () => {
it('should create user with valid data', () => {})
it('should validate email format', () => {})
it('should hash password before saving', () => {})
})
})
テスト名
// ✅ DO: 説明的なテスト名
it('should return 400 when email is invalid', () => {})
it('should hash password with bcrypt before saving', () => {})
it('should send welcome email after registration', () => {})
// ❌ DON'T: 曖昧なテスト名
it('works', () => {})
it('test user creation', () => {})
it('should work correctly', () => {})
さまざまなレイヤーのテスト
ユニットテスト (ビジネスロジック)
// src/services/auth.service.test.ts
import { AuthService } from './auth.service'
import { prismaMock } from '../test/prisma-mock'
import bcrypt from 'bcrypt'
describe('AuthService', () => {
describe('login', () => {
it('should return user and token for valid credentials', async () => {
// ARRANGE
const hashedPassword = await bcrypt.hash('password123', 10)
const mockUser = {
id: '1',
email: 'user@test.com',
password: hashedPassword,
}
prismaMock.user.findUnique.mockResolvedValue(mockUser)
// ACT
const result = await authService.login({
email: 'user@test.com',
password: 'password123',
})
// ASSERT
expect(result.user.email).toBe('user@test.com')
expect(result.token).toBeDefined()
expect(result.user).not.toHaveProperty('password')
})
it('should throw error for wrong password', async () => {
// ARRANGE
const hashedPassword = await bcrypt.hash('password123', 10)
const mockUser = {
id: '1',
email: 'user@test.com',
password: hashedPassword,
}
prismaMock.user.findUnique.mockResolvedValue(mockUser)
// ACT & ASSERT
await expect(
authService.login({
email: 'user@test.com',
password: 'wrongpassword',
})
).rejects.toThrow('Invalid credentials')
})
})
})
統合テスト (API ルート)
// src/app/api/auth/register/route.test.ts
import { POST } from './route'
describe('POST /api/auth/register', () => {
it('should create user and return 201', async () => {
// ARRANGE
const request = new Request('http://localhost/api/auth/register', {
method: 'POST',
body: JSON.stringify({
email: 'newuser@test.com',
password: 'SecurePass123!',
name: 'Test User',
}),
})
// ACT
const response = await POST(request)
const data = await response.json()
// ASSERT
expect(response.status).toBe(201)
expect(data.user.email).toBe('newuser@test.com')
expect(data.token).toBeDefined()
expect(data.user).not.toHaveProperty('password')
})
it('should return 400 for invalid email', async () => {
// ARRANGE
const request = new Request('http://localhost/api/auth/register', {
method: 'POST',
body: JSON.stringify({
email: 'invalid-email',
password: 'SecurePass123!',
}),
})
// ACT
const response = await POST(request)
const data = await response.json()
// ASSERT
expect(response.status).toBe(400)
expect(data.error).toContain('email')
})
})
コンポーネントテスト (UI)
// src/components/LoginForm.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { LoginForm } from './LoginForm'
describe('LoginForm', () => {
it('should call onSubmit with email and password', async () => {
(原文がここで切り詰められています) 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
TDD Workflow Enforcer
When to Use
- Implementing new features
- Adding functionality
- Fixing bugs
- Refactoring code
TDD Process (MANDATORY)
1. Write Tests FIRST (RED Phase)
- Define behavior through tests
- Use AAA pattern (Arrange, Act, Assert)
- Tests MUST fail initially
- Clear test names describe expected behavior
2. Verify Tests Fail (Confirmation)
- Run tests:
npm test - Confirm failure for the RIGHT reason
- Test should fail because feature doesn't exist, not because of syntax error
3. Write Implementation (GREEN Phase)
- Write minimal code to pass tests
- No gold plating or extra features
- Focus solely on making tests pass
4. Verify Tests Pass (Validation)
- Run tests:
npm test - All new tests must be green
- All existing tests must still pass
5. Refactor (REFACTOR Phase)
- Improve code quality
- Remove duplication
- Enhance readability
- Tests stay green throughout
Coverage Requirements
- Overall: 75%+
- Business Logic (src/services/): 90%+
- Utilities (src/utils/): 90%+
- UI Components: 60%+
- E2E tests for critical user flows
AAA Pattern (Arrange, Act, Assert)
describe('AuthService', () => {
describe('register', () => {
it('should create user with hashed password', async () => {
// ARRANGE: Setup test data
const userData = {
email: 'test@example.com',
password: 'Pass123!',
}
// ACT: Execute the behavior
const result = await authService.register(userData)
// ASSERT: Verify outcome
expect(result.id).toBeDefined()
expect(result.email).toBe(userData.email)
expect(result).not.toHaveProperty('password') // Never return password
})
it('should reject weak passwords', async () => {
// ARRANGE
const userData = {
email: 'test@example.com',
password: '123', // Too weak
}
// ACT & ASSERT
await expect(authService.register(userData)).rejects.toThrow(
'Password must be at least 8 characters'
)
})
})
})
Test Structure
Describe Blocks
// ✅ DO: Organize by module/class
describe('UserService', () => {
// ✅ DO: Organize by method
describe('findById', () => {
it('should return user when found', () => {})
it('should return null when not found', () => {})
it('should throw error for invalid id', () => {})
})
describe('create', () => {
it('should create user with valid data', () => {})
it('should validate email format', () => {})
it('should hash password before saving', () => {})
})
})
Test Names
// ✅ DO: Descriptive test names
it('should return 400 when email is invalid', () => {})
it('should hash password with bcrypt before saving', () => {})
it('should send welcome email after registration', () => {})
// ❌ DON'T: Vague test names
it('works', () => {})
it('test user creation', () => {})
it('should work correctly', () => {})
Testing Different Layers
Unit Tests (Business Logic)
// src/services/auth.service.test.ts
import { AuthService } from './auth.service'
import { prismaMock } from '../test/prisma-mock'
import bcrypt from 'bcrypt'
describe('AuthService', () => {
describe('login', () => {
it('should return user and token for valid credentials', async () => {
// ARRANGE
const hashedPassword = await bcrypt.hash('password123', 10)
const mockUser = {
id: '1',
email: 'user@test.com',
password: hashedPassword,
}
prismaMock.user.findUnique.mockResolvedValue(mockUser)
// ACT
const result = await authService.login({
email: 'user@test.com',
password: 'password123',
})
// ASSERT
expect(result.user.email).toBe('user@test.com')
expect(result.token).toBeDefined()
expect(result.user).not.toHaveProperty('password')
})
it('should throw error for wrong password', async () => {
// ARRANGE
const hashedPassword = await bcrypt.hash('password123', 10)
const mockUser = {
id: '1',
email: 'user@test.com',
password: hashedPassword,
}
prismaMock.user.findUnique.mockResolvedValue(mockUser)
// ACT & ASSERT
await expect(
authService.login({
email: 'user@test.com',
password: 'wrongpassword',
})
).rejects.toThrow('Invalid credentials')
})
})
})
Integration Tests (API Routes)
// src/app/api/auth/register/route.test.ts
import { POST } from './route'
describe('POST /api/auth/register', () => {
it('should create user and return 201', async () => {
// ARRANGE
const request = new Request('http://localhost/api/auth/register', {
method: 'POST',
body: JSON.stringify({
email: 'newuser@test.com',
password: 'SecurePass123!',
name: 'Test User',
}),
})
// ACT
const response = await POST(request)
const data = await response.json()
// ASSERT
expect(response.status).toBe(201)
expect(data.user.email).toBe('newuser@test.com')
expect(data.token).toBeDefined()
expect(data.user).not.toHaveProperty('password')
})
it('should return 400 for invalid email', async () => {
// ARRANGE
const request = new Request('http://localhost/api/auth/register', {
method: 'POST',
body: JSON.stringify({
email: 'invalid-email',
password: 'SecurePass123!',
}),
})
// ACT
const response = await POST(request)
const data = await response.json()
// ASSERT
expect(response.status).toBe(400)
expect(data.error).toContain('email')
})
})
Component Tests (UI)
// src/components/LoginForm.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { LoginForm } from './LoginForm'
describe('LoginForm', () => {
it('should call onSubmit with email and password', async () => {
// ARRANGE
const mockOnSubmit = vi.fn().mockResolvedValue(undefined)
render(<LoginForm onSubmit={mockOnSubmit} />)
// ACT
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'user@test.com' },
})
fireEvent.change(screen.getByLabelText(/password/i), {
target: { value: 'password123' },
})
fireEvent.click(screen.getByRole('button', { name: /login/i }))
// ASSERT
await waitFor(() => {
expect(mockOnSubmit).toHaveBeenCalledWith({
email: 'user@test.com',
password: 'password123',
})
})
})
it('should display error message when login fails', async () => {
// ARRANGE
const mockOnSubmit = vi
.fn()
.mockRejectedValue(new Error('Invalid credentials'))
render(<LoginForm onSubmit={mockOnSubmit} />)
// ACT
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'user@test.com' },
})
fireEvent.change(screen.getByLabelText(/password/i), {
target: { value: 'wrongpassword' },
})
fireEvent.click(screen.getByRole('button', { name: /login/i }))
// ASSERT
await waitFor(() => {
expect(screen.getByText(/invalid credentials/i)).toBeInTheDocument()
})
})
it('should disable submit button while loading', async () => {
// ARRANGE
const mockOnSubmit = vi
.fn()
.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)))
render(<LoginForm onSubmit={mockOnSubmit} />)
// ACT
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'user@test.com' },
})
fireEvent.change(screen.getByLabelText(/password/i), {
target: { value: 'password123' },
})
const submitButton = screen.getByRole('button', { name: /login/i })
fireEvent.click(submitButton)
// ASSERT
expect(submitButton).toBeDisabled()
await waitFor(() => {
expect(submitButton).not.toBeDisabled()
})
})
})
E2E Tests (Critical Flows)
// tests/e2e/auth.spec.ts
import { test, expect } from '@playwright/test'
test.describe('Authentication Flow', () => {
test('user can register and login', async ({ page }) => {
// ARRANGE
const email = `test-${Date.now()}@example.com`
const password = 'SecurePass123!'
// ACT: Register
await page.goto('/register')
await page.fill('[name="email"]', email)
await page.fill('[name="password"]', password)
await page.fill('[name="confirmPassword"]', password)
await page.click('button[type="submit"]')
// ASSERT: Redirected to dashboard
await expect(page).toHaveURL('/dashboard')
await expect(page.locator('h1')).toContainText('Dashboard')
// ACT: Logout
await page.click('[data-testid="user-menu"]')
await page.click('text=Logout')
// ASSERT: Redirected to login
await expect(page).toHaveURL('/login')
// ACT: Login
await page.fill('[name="email"]', email)
await page.fill('[name="password"]', password)
await page.click('button[type="submit"]')
// ASSERT: Back to dashboard
await expect(page).toHaveURL('/dashboard')
})
})
Test Quality Requirements
✅ DO: Test behavior, not implementation
// ✅ DO
it('should display error message when login fails', async () => {
// Test what the user sees
await expect(screen.getByText(/invalid credentials/i)).toBeInTheDocument()
})
// ❌ DON'T
it('should call setError with "Invalid credentials"', async () => {
// Testing implementation detail
expect(setError).toHaveBeenCalledWith('Invalid credentials')
})
✅ DO: Test edge cases
it('should handle empty input', () => {})
it('should handle very long input (> 1000 chars)', () => {})
it('should handle special characters in email', () => {})
it('should handle concurrent requests', () => {})
✅ DO: Test error conditions
it('should handle database connection failure', () => {})
it('should handle network timeout', () => {})
it('should handle invalid JSON response', () => {})
✅ DO: Use test data builders
// Test data builders for cleaner tests
const userBuilder = {
default: () => ({
email: 'test@example.com',
password: 'Pass123!',
name: 'Test User',
}),
withEmail: (email: string) => ({
...userBuilder.default(),
email,
}),
withoutName: () => ({
email: 'test@example.com',
password: 'Pass123!',
}),
}
it('should create user with default data', () => {
const user = userBuilder.default()
// ...
})
it('should create user without name', () => {
const user = userBuilder.withoutName()
// ...
})
Coverage Verification
# Run tests with coverage
npm run test:coverage
# Check coverage thresholds
npm test -- --coverage --coverageThreshold='{"global":{"lines":75,"functions":75,"branches":75}}'
Common TDD Mistakes
❌ DON'T: Write implementation first
// Wrong order
1. Write function
2. Write tests
3. Tests pass (or fix tests to pass)
✅ DO: Write tests first
// Correct order (TDD)
1. Write test (RED)
2. Verify test fails
3. Write minimal implementation (GREEN)
4. Verify test passes
5. Refactor (REFACTOR)
❌ DON'T: Test implementation details
// Bad: Testing internal state
expect(component.state.loading).toBe(true)
// Good: Testing observable behavior
expect(screen.getByTestId('spinner')).toBeInTheDocument()
❌ DON'T: Write one giant test
// Bad: One test does everything
it('should handle entire user flow', () => {
// 100 lines of test code
})
// Good: Split into focused tests
it('should validate email format', () => {})
it('should hash password', () => {})
it('should create user in database', () => {})
it('should send welcome email', () => {})
Checklist Before Committing
- [ ] All new features have tests written FIRST
- [ ] Tests failed initially (RED)
- [ ] Implementation makes tests pass (GREEN)
- [ ] Code refactored for quality (REFACTOR)
- [ ] Coverage thresholds met (75%+ overall, 90%+ business logic)
- [ ] All tests use AAA pattern
- [ ] Test names are descriptive
- [ ] Edge cases tested
- [ ] Error conditions tested
- [ ] E2E tests for critical user flows