Skip to content
cccskills
FreePublished on GitHub

tdd-workflow

新機能の作成、バグ修正、コードのリファクタリング時に使用するスキル。ユニット、インテグレーション、E2Eテストを含む80%以上のカバレッジでテスト駆動開発を実施。

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md10.6 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

テスト駆動開発ワークフロー

すべてのコード開発がTDD原則に従い、包括的なテストカバレッジを確保するためのスキルです。

アクティベーション条件

  • 新しい機能を作成するとき
  • バグや問題を修正するとき
  • 既存のコードをリファクタリングするとき
  • APIエンドポイントを追加するとき
  • 新しいコンポーネントを作成するとき

基本原則

1. コードの前にテスト

常にテストを先に書き、テストを通すためのコードを実装します。

2. カバレッジ要件

  • 最小80%カバレッジ(ユニット + インテグレーション + E2E)
  • すべてのエッジケースをカバー
  • エラーシナリオをテスト
  • 境界条件を検証

3. テストの種類

ユニットテスト

  • 個別の関数とユーティリティ
  • コンポーネントロジック
  • 純粋関数
  • ヘルパーとユーティリティ

インテグレーションテスト

  • APIエンドポイント
  • データベース操作
  • サービスインタラクション
  • 外部API呼び出し

E2Eテスト(Playwright)

  • クリティカルなユーザーフロー
  • 完全なワークフロー
  • ブラウザオートメーション
  • UIインタラクション

TDDワークフローステップ

ステップ1: ユーザージャーニーを書く

As a [role], I want to [action], so that [benefit]

Example:
As a user, I want to search for markets semantically,
so that I can find relevant markets even without exact keywords.

ステップ2: テストケースを生成

各ユーザージャーニーについて包括的なテストケースを作成:

describe('Semantic Search', () => {
  it('returns relevant markets for query', async () => {
    // Test implementation
  })

  it('handles empty query gracefully', async () => {
    // Test edge case
  })

  it('falls back to substring search when Redis unavailable', async () => {
    // Test fallback behavior
  })

  it('sorts results by similarity score', async () => {
    // Test sorting logic
  })
})

ステップ3: テストを実行(失敗するはず)

npm test
# Tests should fail - we haven't implemented yet

ステップ4: コードを実装

テストを通すための最小限のコードを書く:

// Implementation guided by tests
export async function searchMarkets(query: string) {
  // Implementation here
}

ステップ5: テストを再実行

npm test
# Tests should now pass

ステップ6: リファクタリング

テストをグリーンに保ちながらコード品質を改善:

  • 重複を除去
  • 命名を改善
  • パフォーマンスを最適化
  • 可読性を向上

ステップ7: カバレッジを検証

npm run test:coverage
# Verify 80%+ coverage achieved

テストパターン

ユニットテストパターン(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 () => {
    // Mock database failure
    const request = new NextRequest('http://localhost/api/markets')
    // Test error handling
  })
})

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) // Mock 1536-dim embedding
  ))
}))

テストカバレッジの検証

カバレッジレポートの実行

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', () => { /* depends on previous test */ })

✅ 正しい: 独立したテスト

// 各テストが自身のデータをセットアップ
test('creates user', () => {
  const user = createTestUser()
  // Test logic
})

test('updates user', () => {
  const user = createTestUser()
  // Update logic
})

継続的テスト

開発中のウォッチモード

npm test -- --watch
# Tests run automatically on file changes

プリコミットフック

# Runs before every commit
npm test && npm run lint

CI/CDインテグレーション

# GitHub Actions
- name: Run Tests
  run: npm test -- --coverage
- name: Upload Coverage
  uses: codecov/codecov-action@v3

ベストプラクティス

  1. テストを先に書く - 常にTDD
  2. テストごとに1つのアサート - 単一の動作に焦点
  3. 説明的なテスト名 - 何をテストしているか説明
  4. Arrange-Act-Assert - 明確なテスト構造
  5. 外部依存関係をモック - ユニットテストを分離
  6. エッジケースをテスト - Null、undefined、空、大きい値
  7. エラーパスをテスト - ハッピーパスだけでなく
  8. テストを高速に保つ - ユニットテストは各50ms未満
  9. テスト後にクリーンアップ - 副作用なし
  10. カバレッジレポートをレビュー - ギャップを特定

成功メトリクス

  • 80%以上のコードカバレッジ達成
  • すべてのテストが通過(グリーン)
  • スキップまたは無効化されたテストなし
  • 高速なテスト実行(ユニットテスト30秒未満)
  • E2Eテストがクリティカルなユーザーフローをカバー
  • テストが本番前にバグを捕捉

覚えておくこと: テストはオプションではありません。自信を持ったリファクタリング、迅速な開発、本番環境の信頼性を可能にするセーフティネットです。

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

コーディングエージェント(Claude Code、Aider、Codexなど)のカスタムタスクによる直接比較。合格率、コスト、時間、一貫性メトリクスを測定

The English summary is being prepared. Showing the original description.

hage-oyaji/everything-claude-code-jp-local3Updated Mar 29, 2026

AIエージェントのアクションスペース、ツール定義、オブザベーションフォーマットを設計・最適化し、タスク完了率を向上させる

The English summary is being prepared. Showing the original description.

hage-oyaji/everything-claude-code-jp-local3Updated Mar 29, 2026

評価ファーストの実行、分解、コスト考慮型モデルルーティングによるエージェンティックエンジニアとしての運用

The English summary is being prepared. Showing the original description.

hage-oyaji/everything-claude-code-jp-local3Updated Mar 29, 2026

AIエージェントが実装出力の大部分を生成するチームのためのエンジニアリング運用モデル

The English summary is being prepared. Showing the original description.

hage-oyaji/everything-claude-code-jp-local3Updated Mar 29, 2026

AI支援開発のためのリグレッションテスト戦略。データベース依存なしのサンドボックスモードAPIテスト、自動バグチェックワークフロー、同一モデルがコードの作成とレビューを行う際のAIの盲点を検出するパターン。

The English summary is being prepared. Showing the original description.

hage-oyaji/everything-claude-code-jp-local3Updated Mar 29, 2026

AndroidおよびKotlin Multiplatformプロジェクトのためのクリーンアーキテクチャパターン — モジュール構成、依存関係ルール、UseCase、Repository、データレイヤーパターン

The English summary is being prepared. Showing the original description.

hage-oyaji/everything-claude-code-jp-local3Updated Mar 29, 2026

See all skills by hage-oyaji

Report a problem with this skill