Awesome-claude-notes tdd-workflow
新機能の作成、バグ修正、コードのリファクタリング時にこのスキルを使用します。ユニット、統合、E2Eテストを含む80%以上のカバレッジでテスト駆動開発を強制します。
install
source · Clone the upstream repo
git clone https://github.com/loulanyue/awesome-claude-notes
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/loulanyue/awesome-claude-notes "$T" && mkdir -p ~/.claude/skills && cp -r "$T/docs/ja-JP/skills/tdd-workflow" ~/.claude/skills/loulanyue-awesome-claude-notes-tdd-workflow && rm -rf "$T"
manifest:
docs/ja-JP/skills/tdd-workflow/SKILL.mdsource content
テスト駆動開発ワークフロー
このスキルは、すべてのコード開発が包括的なテストカバレッジを備えたTDDの原則に従うことを保証します。
有効化するタイミング
- 新機能や機能の作成
- バグや問題の修正
- 既存コードのリファクタリング
- APIエンドポイントの追加
- 新しいコンポーネントの作成
コア原則
1. コードの前にテスト
常にテストを最初に書き、次にテストに合格するコードを実装します。
2. カバレッジ要件
- 最低80%のカバレッジ(ユニット + 統合 + E2E)
- すべてのエッジケースをカバー
- エラーシナリオのテスト
- 境界条件の検証
3. テストタイプ
ユニットテスト
- 個々の関数とユーティリティ
- コンポーネントロジック
- 純粋関数
- ヘルパーとユーティリティ
統合テスト
- APIエンドポイント
- データベース操作
- サービス間相互作用
- 外部API呼び出し
E2Eテスト (Playwright)
- クリティカルなユーザーフロー
- 完全なワークフロー
- ブラウザ自動化
- UI相互作用
TDDワークフローステップ
ステップ1:ユーザージャーニーを書く
[役割]として、[行動]をしたい、それによって[利益]を得られるようにするため 例: ユーザーとして、セマンティックに市場を検索したい、 それによって正確なキーワードなしでも関連する市場を見つけられるようにするため。
ステップ2:テストケースを生成
各ユーザージャーニーについて、包括的なテストケースを作成:
describe('Semantic Search', () => { it('returns relevant markets for query', async () => { // テスト実装 }) it('handles empty query gracefully', async () => { // エッジケースのテスト }) it('falls back to substring search when Redis unavailable', async () => { // フォールバック動作のテスト }) it('sorts results by similarity score', async () => { // ソートロジックのテスト }) })
ステップ3:テストを実行(失敗するはず)
npm test # テストは失敗するはず - まだ実装していない
ステップ4:コードを実装
テストに合格する最小限のコードを書く:
// テストにガイドされた実装 export async function searchMarkets(query: string) { // 実装はここ }
ステップ5:テストを再実行
npm test # テストは今度は成功するはず
ステップ6:リファクタリング
テストをグリーンに保ちながらコード品質を向上:
- 重複を削除
- 命名を改善
- パフォーマンスを最適化
- 可読性を向上
ステップ7:カバレッジを確認
npm run test:coverage # 80%以上のカバレッジを達成したことを確認
テストパターン
ユニットテストパターン (Jest/Vitest)
import { render, screen, fireEvent } from '@testing-library/react' import { Button } from './Button' describe('Button Component', () => { it('renders with correct text', () => { render(<Button>Click me</Button>) expect(screen.getByText('Click me')).toBeInTheDocument() }) it('calls onClick when clicked', () => { const handleClick = jest.fn() render(<Button onClick={handleClick}>Click</Button>) fireEvent.click(screen.getByRole('button')) expect(handleClick).toHaveBeenCalledTimes(1) }) it('is disabled when disabled prop is true', () => { render(<Button disabled>Click</Button>) expect(screen.getByRole('button')).toBeDisabled() }) })
API統合テストパターン
import { NextRequest } from 'next/server' import { GET } from './route' describe('GET /api/markets', () => { it('returns markets successfully', async () => { const request = new NextRequest('http://localhost/api/markets') const response = await GET(request) const data = await response.json() expect(response.status).toBe(200) expect(data.success).toBe(true) expect(Array.isArray(data.data)).toBe(true) }) it('validates query parameters', async () => { const request = new NextRequest('http://localhost/api/markets?limit=invalid') const response = await GET(request) expect(response.status).toBe(400) }) it('handles database errors gracefully', async () => { // データベース障害をモック const request = new NextRequest('http://localhost/api/markets') // エラー処理のテスト }) })
E2Eテストパターン (Playwright)
import { test, expect } from '@playwright/test' test('user can search and filter markets', async ({ page }) => { // 市場ページに移動 await page.goto('/') await page.click('a[href="/markets"]') // ページが読み込まれたことを確認 await expect(page.locator('h1')).toContainText('Markets') // 市場を検索 await page.fill('input[placeholder="Search markets"]', 'election') // デバウンスと結果を待つ await page.waitForTimeout(600) // 検索結果が表示されることを確認 const results = page.locator('[data-testid="market-card"]') await expect(results).toHaveCount(5, { timeout: 5000 }) // 結果に検索語が含まれることを確認 const firstResult = results.first() await expect(firstResult).toContainText('election', { ignoreCase: true }) // ステータスでフィルタリング await page.click('button:has-text("Active")') // フィルタリングされた結果を確認 await expect(results).toHaveCount(3) }) test('user can create a new market', async ({ page }) => { // 最初にログイン await page.goto('/creator-dashboard') // 市場作成フォームに入力 await page.fill('input[name="name"]', 'Test Market') await page.fill('textarea[name="description"]', 'Test description') await page.fill('input[name="endDate"]', '2025-12-31') // フォームを送信 await page.click('button[type="submit"]') // 成功メッセージを確認 await expect(page.locator('text=Market created successfully')).toBeVisible() // 市場ページへのリダイレクトを確認 await expect(page).toHaveURL(/\/markets\/test-market/) })
テストファイル構成
src/ ├── components/ │ ├── Button/ │ │ ├── Button.tsx │ │ ├── Button.test.tsx # ユニットテスト │ │ └── Button.stories.tsx # Storybook │ └── MarketCard/ │ ├── MarketCard.tsx │ └── MarketCard.test.tsx ├── app/ │ └── api/ │ └── markets/ │ ├── route.ts │ └── route.test.ts # 統合テスト └── e2e/ ├── markets.spec.ts # E2Eテスト ├── trading.spec.ts └── auth.spec.ts
外部サービスのモック
Supabaseモック
jest.mock('@/lib/supabase', () => ({ supabase: { from: jest.fn(() => ({ select: jest.fn(() => ({ eq: jest.fn(() => Promise.resolve({ data: [{ id: 1, name: 'Test Market' }], error: null })) })) })) } }))
Redisモック
jest.mock('@/lib/redis', () => ({ searchMarketsByVector: jest.fn(() => Promise.resolve([ { slug: 'test-market', similarity_score: 0.95 } ])), checkRedisHealth: jest.fn(() => Promise.resolve({ connected: true })) }))
OpenAIモック
jest.mock('@/lib/openai', () => ({ generateEmbedding: jest.fn(() => Promise.resolve( new Array(1536).fill(0.1) // 1536次元埋め込みをモック )) }))
テストカバレッジ検証
カバレッジレポートを実行
npm run test:coverage
カバレッジ閾値
{ "jest": { "coverageThresholds": { "global": { "branches": 80, "functions": 80, "lines": 80, "statements": 80 } } } }
避けるべき一般的なテストの誤り
❌ 誤り:実装の詳細をテスト
// 内部状態をテストしない expect(component.state.count).toBe(5)
✅ 正解:ユーザーに見える動作をテスト
// ユーザーが見るものをテスト expect(screen.getByText('Count: 5')).toBeInTheDocument()
❌ 誤り:脆弱なセレクタ
// 簡単に壊れる await page.click('.css-class-xyz')
✅ 正解:セマンティックセレクタ
// 変更に強い await page.click('button:has-text("Submit")') await page.click('[data-testid="submit-button"]')
❌ 誤り:テストの分離なし
// テストが互いに依存 test('creates user', () => { /* ... */ }) test('updates same user', () => { /* 前のテストに依存 */ })
✅ 正解:独立したテスト
// 各テストが独自のデータをセットアップ test('creates user', () => { const user = createTestUser() // テストロジック }) test('updates user', () => { const user = createTestUser() // 更新ロジック })
継続的テスト
開発中のウォッチモード
npm test -- --watch # ファイル変更時に自動的にテストが実行される
プリコミットフック
# すべてのコミット前に実行 npm test && npm run lint
CI/CD統合
# GitHub Actions - name: Run Tests run: npm test -- --coverage - name: Upload Coverage uses: codecov/codecov-action@v3
ベストプラクティス
- テストを最初に書く - 常にTDD
- テストごとに1つのアサート - 単一の動作に焦点
- 説明的なテスト名 - テスト内容を説明
- Arrange-Act-Assert - 明確なテスト構造
- 外部依存関係をモック - ユニットテストを分離
- エッジケースをテスト - null、undefined、空、大きい値
- エラーパスをテスト - ハッピーパスだけでなく
- テストを高速に保つ - ユニットテスト各50ms未満
- テスト後にクリーンアップ - 副作用なし
- カバレッジレポートをレビュー - ギャップを特定
成功指標
- 80%以上のコードカバレッジを達成
- すべてのテストが成功(グリーン)
- スキップまたは無効化されたテストなし
- 高速なテスト実行(ユニットテストは30秒未満)
- E2Eテストがクリティカルなユーザーフローをカバー
- テストが本番前にバグを検出
覚えておいてください:テストはオプションではありません。テストは自信を持ってリファクタリングし、迅速に開発し、本番の信頼性を可能にする安全網です。