| name | tdd |
| description | GitHub IssueからTDD(Test-Driven Development)サイクルを実行し、テストと実装を段階的に作成。backendまたはfrontendの実装にTDDを適用する際に使用。 |
| argument-hint | <Issue番号> |
指定されたGitHub Issueからアプリケーションコード(backend/、frontend/ディレクトリ配下)のみを対象にTDDサイクル(Red-Green-Refactor)を実行してください。
cdk/配下のインフラコードは絶対に実装・変更しないでください。
Issueのタスク一覧のうち、アプリケーションコード(backend/frontend)に関するタスクのみを対象としてください。
完了したアプリケーションコードタスクのみ gh issue edit コマンドでGitHub Issueのチェックリストを更新してください。
Issue指定: $ARGUMENTS
TDD実行の詳細手順
GitHub Issueの内容を解析し、Red-Green-Refactorサイクルに従ってテストと実装を段階的に作成する。
本プロジェクトの仕様駆動開発フローにおける Step 6: 実装(TDD) を支援する。
⚠️ スコープ制約(厳守)
- アプリケーションコード(
backend/、frontend/ディレクトリ配下)のみを対象とする
cdk/配下のインフラコードは絶対に実装・変更しないこと
- Issueのタスク一覧のうち、アプリケーションコード(backend/frontend)に関するタスクのみを実装対象とする
- CDKスタック実装、Construct作成、インフラ設定、スナップショットテスト、cdk synth等は対象外
- 対象外のタスクはスキップし、GitHub Issueのチェックリストも更新しないこと
実行プロセス
Phase 1: Issue読み込みと実装仕様の抽出
ステップ 1-1: Issue番号の取得
まず、与えられた指示(プロンプト)に Issue指定: に続く値が含まれているか確認する。
引数が提供されている場合(例: Issue指定: 15):
引数が提供されていない場合(空文字または指定なし):
AskUserQuestion ツールを使用してユーザーに入力を促す:
question: "実装したいIssueを指定してください。Issue番号(例: 15)を入力してください。"
header: "Issue指定"
options: [
{ label: "その他(手動入力)", description: "Issue番号を入力してください" }
]
multiSelect: false
取得情報:
ステップ 1-2: GitHub IssueのJSON取得
Bash ツールで GitHub Issue の情報を取得:
gh issue view {番号} --json number,title,body,labels
エラーハンドリング:
-
Issue が見つからない場合:
=== Issue 読み込みエラー ===
Error: Issue #{番号} が見つかりませんでした。
gh issue list で利用可能なIssue一覧を確認してください。
→ AskUserQuestion で再入力を促す(最大3回まで)
-
body が空の場合:
=== Issue 読み込みエラー ===
Error: Issue #{番号} の本文が空です。
→ 処理を中止
ステップ 1-3: Issue内容の解析
取得したJSONから以下の情報を抽出:
1. Issue番号とタイトル
- Issue番号:
.number フィールド
- タイトル:
.title フィールド
2. ラベル
.labels[].name フィールドから抽出
- 抽出例:
[{name: "backend"}, {name: "frontend"}] → ['backend', 'frontend']
3. スコープ/作業項目
body内 ## スコープ / 作業項目 セクションの内容全体を抽出
4. タスク一覧(アプリケーションコードタスクのみ抽出)
body内 ## タスク一覧 セクションのチェックリスト(- [ ] 形式)を抽出し、アプリケーションコード(backend/frontend)に関するタスクのみをフィルタリングする。
フィルタリングルール:
- 対象: 「Lambda実装」「ハンドラー実装」「API実装」「フロントエンド」「画面」「テストコード作成」「TDD」「単体テスト」等、アプリケーションコードに関するタスク
- 対象外(スキップ): 「CDK」「cdk」「インフラ」「スタック」「Construct」「デプロイ」「synth」「スナップショット」等、CDK/インフラに関するタスク
- キーワードだけで判断が難しい場合は、タスクの文脈からアプリケーションコード関連かどうかを判断する
- フィルタリング結果(対象タスク・スキップしたタスク)を明示的に表示する
5. 対象ファイル
body内 ## 📂 コンテキスト または ### 対象ファイル セクションから抽出
6. 実装詳細
body内 ### 実装詳細 セクションの内容を抽出(存在する場合)
出力:
{
issueNumber: 15,
issueTitle: "State検証機能の実装",
labels: ['backend'],
scope: "...",
taskList: ["- [ ] 実装完了", "- [ ] テスト完了"],
targetFiles: {
implementation: 'backend/src/utils/state.ts',
test: 'backend/src/utils/state.test.ts'
},
implementationDetails: "...",
}
ステップ 1-5: 対象ディレクトリの自動判定
ラベル情報から対象ディレクトリをマッピング:
| ラベル | 対応ディレクトリ |
|---|
| backend | backend/ |
| frontend | frontend/ |
| cdk, infra | cdk/ |
例:
ステップ 1-6: テスト対象ファイルとテストファイルの特定
Issue内の「対象ファイル」セクションから抽出:
明示的に記載がある場合:
## 対象ファイル
- backend/src/utils/state.ts
- backend/src/utils/state.test.ts
→ そのまま使用
記載がない場合:
AskUserQuestion で確認:
question: "テスト対象ファイルとテストファイルのパスを入力してください。\n\n例:\n実装ファイル: backend/src/utils/state.ts\nテストファイル: backend/src/utils/state.test.ts"
header: "対象ファイル指定"
options: [
{ label: "その他(手動入力)", description: "ファイルパスを入力してください" }
]
ファイル命名規則の推測:
- ラベルが
backend の場合: backend/src/**/*.ts, backend/src/**/*.test.ts
- ラベルが
frontend の場合: frontend/src/**/*.ts, frontend/src/**/*.test.ts
ステップ 1-7: 実装仕様の整理と確認
Issueから抽出した情報を整理してユーザーに確認:
表示フォーマット:
=== 実装仕様 ===
Issue: #{番号} {タイトル}
対象ファイル:
- 実装: {実装ファイルパス}
- テスト: {テストファイルパス}
実装する機能:
{スコープ/作業項目の要約}
{実装詳細があれば表示}
この内容でTDDサイクルを開始しますか?
AskUserQuestion で確認:
question: "上記の内容でTDDサイクルを開始しますか?"
header: "実装仕様確認"
options: [
{ label: "はい、開始します", description: "TDDサイクルを開始します" },
{ label: "修正する", description: "対象ファイルやIssue番号を修正します" },
{ label: "中断する", description: "コマンドを終了します" }
]
分岐処理:
- 「はい、開始します」→ Phase 2へ
- 「修正する」→ ステップ 1-1へ戻る
- 「中断する」→ 処理を中止
出力:
{
issueNumber: 15,
issueTitle: "State検証機能の実装",
implementationFile: 'backend/src/utils/state.ts',
testFile: 'backend/src/utils/state.test.ts',
scope: "...",
implementationDetails: "..."
}
Phase 2: 赤フェーズ(Red)- 失敗するテストを作成
ステップ 2-0: テスト方針の読み込み(必須)
テストコードを書く前に、必ず以下の設計書を Read ツールで読み込む。
Read: docs/design/test-policy.md
読み込んだテスト方針から、以下のルールを抽出し、Phase 2〜Phase 7 の全工程で遵守する:
| # | ルール | 参照セクション |
|---|
| 1 | 振る舞いを検証し、実装の詳細を検証しない | 2.3 検証原則 |
| 2 | 検証手法の優先順位: 出力値ベース > 状態ベース > コミュニケーションベース | 2.3 検証手法の優先順位 |
| 3 | 単体テストではモック・スタブを可能な限り使用しない | 2.4 テストダブルの使用方針 |
| 4 | スタブとのやりとりを検証してはならない | 2.4 |
| 5 | AAAパターン(Arrange-Act-Assert)で構成する | 2.5 AAAパターン |
| 6 | フェーズコメント(// Arrange 等)は準備 or 確認が複数行の場合のみ | 2.5 AAAパターン |
| 7 | テストケース名は日本語、メソッド名を含めない、事実を示す表現 | 2.5 命名規則 |
| 8 | SUT(テスト対象)を const sut = handler で明示する | 2.5 SUT の明示 |
| 9 | テスト内で if 文を使わない | 2.5 |
| 10 | テストケースの分離は仕様上の条件で判断する | 2.5 分離基準 |
| 11 | パラメータ化テスト(it.each)は仕様上同じ条件のバリエーションに使用 | 2.5 パラメータ化テスト |
| 12 | 正常系と異常系は必ず分離する | 2.5 パラメータ化テスト |
| 13 | beforeEach にテストケース固有データを置かない | 2.6 テストフィクスチャ |
| 14 | テストデータ共通化には Object Mother パターンを使用 | 2.6 テストデータの共通化 |
| 15 | テスト対象はビジネスロジック・事前条件チェック・サポートログのみ | 2.8 テスト対象の判断基準 |
| 16 | フロントエンドはユーザー視点でテスト(getByRole, getByText) | 2.9 フロントエンド固有 |
さらに、ラベルに応じて関連設計書も読み込み、テストケース設計の入力情報とする:
| ラベル | 追加で読み込む設計書 |
|---|
| backend | docs/design/backend-design.md, docs/design/backend-api-specification.md, docs/design/backend-flow.md |
| frontend | docs/design/frontend-design.md |
ステップ 2-1: テストケースの設計支援
グレーボックステストの考え方に基づき、Issue内容と設計書からテストケースを設計する。
抽出元:
- スコープ/作業項目
- 実装詳細
- 設計書のインターフェース定義、シーケンス図、エラーハンドリング仕様
テストケース設計の原則(test-policy.md セクション 2.3, 2.5, 2.8 に基づく):
- テスト対象の判断基準(2.8)に照らして、テストすべきコードかを先に判断する
- 仕様上の条件ごとにテストケースを分離する(2.5 分離基準)
- 同じ条件のバリエーションは
it.each でまとめる(2.5 パラメータ化テスト)
- 正常系と異常系は必ず分離する
推奨テストケースの提示:
テストケースを設計します。
テスト方針(test-policy.md)に基づく設計:
- テスト対象: {ビジネスロジック / 事前条件チェック等、テスト対象判断基準に照らした根拠}
- 検証手法: {出力値ベース / 状態ベース}
Issueと設計書から抽出したテストケース:
正常系:
- {仕様上の条件A}の場合に{期待結果}を返す
- {仕様上の条件B}の場合に{期待結果}を返す
異常系:
- {仕様上の条件C}の場合に{期待結果}を返す
パラメータ化候補(同一条件のバリエーション):
- {条件X}: [{値1}, {値2}, {値3}]
他に追加したいテストケースはありますか?
AskUserQuestion で確認:
question: "テストケースの選択"
header: "Red Phase"
options: [
{ label: "推奨ケースで開始", description: "上記のテストケースで開始します" },
{ label: "追加する", description: "テストケースを追加します" }
]
「追加する」が選択された場合:
question: "追加したいテストケースを入力してください(1行1ケース)"
header: "テストケース追加"
options: [
{ label: "その他(手動入力)", description: "テストケースを入力" }
]
ステップ 2-2: テストコードの生成
原則(test-policy.md に完全準拠):
- SUT の明示: テスト対象を
const sut = handler 等で明示する
- AAAパターン: Arrange → Act(必ず1行) → Assert の3フェーズで構成
- フェーズコメント: 準備 or 確認が複数行の場合のみ
// Arrange // Act // Assert を記載。それ以外は空白行のみで区切る
- 命名規則: テストケース名は日本語、メソッド名を含めない、「〜を返す」「〜される」等の事実表現
- describe グループ名: 日本語で記述
- テストダブル: 単体テストではモック・スタブを使用しない(共有依存のみスタブ可、モックは統合テストのみ)
- スタブとのやりとり: スタブに対する
toHaveBeenCalledWith 等の検証は禁止
- テストデータ: テスト関数内に直接記述。共通化が必要な場合は Object Mother パターン
- beforeEach: テストケース固有データを置かない
- パラメータ化テスト: 仕様上同じ条件のバリエーションに
it.each を使用。正常系と異常系は分離
- テスト内 if 文: 禁止
- 検証対象: 観察可能な振る舞い(出力・戻り値・状態変化)のみ。実装の詳細は検証しない
テストフレームワークの検出:
Glob で package.json を検索し、Readで読み込んで依存関係を確認:
jest → Jest形式
vitest → Vitest形式
生成例(バックエンド: Lambda handler のテスト):
import { handler } from '../authorize'
const createApiGatewayEvent = (overrides?: Partial<APIGatewayProxyEvent>) => ({
httpMethod: 'GET',
path: '/authorize',
headers: {},
pathParameters: null,
queryStringParameters: null,
body: null,
...overrides,
})
describe('認可エンドポイント', () => {
const sut = handler
it('正常なリクエストの場合に認可URLへのリダイレクトを返す', async () => {
const event = createApiGatewayEvent()
const result = await sut(event, context)
expect(result.statusCode).toBe(302)
expect(result.headers?.Location).toContain('https://auth.example.com/authorize')
})
it.each(['', 'invalid', '@no-local', 'no-domain@'])(
'メールアドレス「%s」が不正な場合に400を返す',
async (email) => {
const event = createApiGatewayEvent({ body: JSON.stringify({ email }) })
const result = await sut(event, context)
expect(result.statusCode).toBe(400)
}
)
it('必須パラメータが欠落している場合に400を返す', async () => {
const event = createApiGatewayEvent({
queryStringParameters: {},
})
const result = await sut(event, context)
const body = JSON.parse(result.body)
expect(result.statusCode).toBe(400)
expect(body.error).toBe('invalid_request')
})
})
生成例(フロントエンド: コンポーネントテスト):
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { LoginForm } from '../LoginForm'
describe('ログインフォーム', () => {
it('フォーム送信失敗時にエラーメッセージを表示する', async () => {
render(<LoginForm />)
await userEvent.click(screen.getByRole('button', { name: '送信' }))
expect(screen.getByText('入力内容を確認してください')).toBeInTheDocument()
})
})
ステップ 2-3: テスト方針準拠チェック
生成したテストコードを test-policy.md の全項目に照らしてチェック:
チェック項目:
-
SUT の明示(セクション 2.5)
- ✅
const sut = handler 等でテスト対象を明示
- ❌ SUT が明示されていない → 修正
-
AAAパターン(セクション 2.5)
- ✅ Arrange → Act(1行) → Assert の構成
- ❌ Act が複数行 → 警告(カプセル化の見直しを提案)
-
フェーズコメント(セクション 2.5)
- ✅ 準備 or 確認が複数行の場合のみ
// Arrange // Act // Assert を記載
- ❌ シンプルなテストに不要なフェーズコメント → 削除
- ❌ 複数行なのにフェーズコメントなし → 追加
-
テストケース命名(セクション 2.5)
- ✅ 日本語、メソッド名なし、事実表現(「〜を返す」「〜される」)
- ❌ 英語 / メソッド名を含む / 曖昧な表現 → 修正
-
テストダブル方針(セクション 2.4)
- ✅ モック未使用(単体テスト)
- ✅ 共有依存のみスタブ使用
- ❌ プライベート依存のスタブ / 内部関数のモック → 警告
- ❌ スタブに対する
toHaveBeenCalledWith 等の検証 → 警告(過剰検証)
-
テストデータの配置(セクション 2.6)
- ✅ テスト関数内に直接記述、または Object Mother パターン
- ❌ fixture ファイル使用 /
beforeEach にテスト固有データ → 警告
-
テストケースの分離(セクション 2.5)
- ✅ 仕様上の条件ごとにテストケースを分離
- ✅ 正常系と異常系が分離されている
- ❌ 異なる仕様条件が1テストにまとまっている → 分離を提案
-
パラメータ化テスト(セクション 2.5)
- ✅ 同一仕様条件のバリエーションに
it.each を使用
- ❌ 異なる仕様条件を
it.each にまとめている → 分離を提案
-
検証対象(セクション 2.3)
- ✅ 観察可能な振る舞い(出力・戻り値・状態変化)を検証
- ❌ 実装の詳細(内部メソッドの呼び出し順序、内部変数の中間状態)を検証 → 警告
-
テスト対象の妥当性(セクション 2.8)
- ✅ ビジネスロジック / 事前条件チェック / サポートログ
- ❌ private メソッド / 単純な委譲 / 診断ログ / ライブラリ内部 → テスト不要と警告
-
テスト内 if 文(セクション 2.5)
- ✅ if 文なし
- ❌ if 文あり → テストケースの分割を提案
-
フロントエンド固有(セクション 2.9、ラベルが frontend の場合のみ)
- ✅
getByRole, getByText 等ユーザーが認識できる属性で要素取得
- ❌ 内部 state / props の直接検証 → 警告
違反時の警告例:
⚠️ テスト方針違反を検出(test-policy.md)
Phase 2: テスト作成
違反内容:
- [セクション 2.5] SUT が明示されていません → `const sut = handler` を追加してください
- [セクション 2.5] フェーズコメントが不要です(準備・確認が1行のため)→ 削除して空白行区切りにしてください
- [セクション 2.4] スタブに対する toHaveBeenCalledWith は過剰検証です → 最終結果のみを検証してください
対処方法:
1. `const sut = handler` を追加
2. 不要なフェーズコメントを削除
3. スタブの呼び出し検証を削除し、最終結果の検証に変更
修正しますか?
[はい / このまま続行]
ステップ 2-4: テストファイルの作成/更新
新規ファイルの場合:
Write ツールで作成:
file_path: {testFile}
content: <生成したテストコード>
既存ファイルへの追加の場合:
Read でファイルを読み込み、Edit で追加:
file_path: {testFile}
old_string: <describeブロックの末尾>
new_string: <describeブロックの末尾> + <新しいテストケース>
ステップ 2-5: テストコードの確認
生成したテストコードをユーザーに確認:
作成したテストコード:
{生成したテストコード}
TDD原則チェック:
✓ テストデータはテスト関数内に記述
✓ モック未使用
✓ WHYコメント記載
✓ 小さな単位のテスト
このテストで進めますか?
AskUserQuestion で確認:
question: "このテストで進めますか?"
header: "Red Phase"
options: [
{ label: "はい、進めます", description: "Phase 3(Red確認)に進みます" },
{ label: "修正する", description: "テストコードを修正します" }
]
出力:
Phase 3: テスト実行(Red確認)
ステップ 3-1: unit-test-runnerエージェント呼び出し
Task ツールを使用して unit-test-runner サブエージェントを起動:
Task({
subagent_type: 'unit-test-runner',
prompt: '{testFile} のテストを実行してください',
description: 'Run Red phase tests'
})
ステップ 3-2: テスト結果の解析
テスト実行結果を解析:
期待される失敗の例:
Cannot find module './state' → 実装が存在しない
verifyState is not a function → 関数が未定義
ReferenceError: verifyState is not defined → export忘れ
予期しない失敗の例:
SyntaxError: Unexpected token → 構文エラー
Error: Cannot find module (テスト側) → importパスエラー
ステップ 3-3: 結果判定と報告
期待通りの失敗の場合:
🔴 Red Phase: テストが期待通り失敗しました
エラー内容:
- Cannot find module './state' from 'state.test.ts'
理由: まだ実装されていないため(期待通り)
次のフェーズ(Green: 実装)に進みますか?
AskUserQuestion で確認:
question: "次のフェーズ(Green: 実装)に進みますか?"
header: "Red Phase 完了"
options: [
{ label: "はい、進めます", description: "Phase 4(実装)に進みます" },
{ label: "テストを修正", description: "Phase 2に戻ります" }
]
予期しない失敗の場合:
❌ Red Phase: 予期しないエラーが発生しました
エラー内容:
- SyntaxError: Unexpected token 'export' at state.test.ts:1
理由: テストコードに構文エラーがあります
Phase 2(テスト作成)に戻って修正しますか?
AskUserQuestion で確認:
question: "Phase 2に戻って修正しますか?"
header: "エラー検出"
options: [
{ label: "はい、修正します", description: "Phase 2に戻ります" },
{ label: "中断する", description: "コマンドを終了します" }
]
判定基準:
- ✅ 実装が存在しない → Phase 4へ
- ✅ 関数が未定義 → Phase 4へ
- ❌ 構文エラー → Phase 2へ戻る
- ❌ importエラー → Phase 2へ戻る
- ❌ テストが成功してしまった → 警告表示、Phase 2へ戻る
出力:
- テスト実行結果
- 失敗理由の分析
- 次フェーズへの遷移指示
Phase 4: 緑フェーズ(Green)- 最小限の実装
ステップ 4-1: テストコードの解析
Phase 2で作成したテストコードを解析し、実装すべき内容を抽出:
抽出する情報:
- 関数名(例:
verifyState)
- 引数の型と名前(例:
state: string, expectedState: string)
- 戻り値の型(例:
boolean)
- 期待される動作(テストケースから推測)
解析例:
テストから抽出した要件:
- 関数名: verifyState
- 引数: state: string, expectedState: string
- 戻り値: boolean
- 動作:
- 引数が一致する場合: true
- 引数が不一致の場合: false
ステップ 4-2: 最小限の実装コードの生成
原則:
- テストを通す最小限のコードのみ
- 過剰な実装や最適化は避ける
- コメント・JSDoc は
docs/policy/code-comment-policy.md に従う(実装コメントは WHY、doc comment は契約を書く。コードが語る WHAT/HOW は書かない)
生成例:
export function verifyState(state: string, expectedState: string): boolean {
return state === expectedState
}
ステップ 4-3: TDD原則チェック
生成した実装コードをチェック:
チェック項目:
-
最小限の実装
- ✅ テストを通すために必要な最小限のコード
- ❌ テストにない機能の追加 → 警告、削除を提案
-
最適化の禁止
- ✅ シンプルな実装
- ❌ パフォーマンス最適化 → 警告、リファクタリングフェーズへ延期を提案
-
コメント
- ✅ WHYコメント記載
- ❌ WHATコメントのみ → 警告
違反時の警告例:
⚠️ TDD原則違反を検出
Phase 4: 実装
違反内容:
- テストにない機能が実装されています(引数のバリデーション)
TDD原則:
- テストを通す最小限のコードのみ実装する
対処方法:
1. テストにない機能を削除
2. 必要であれば、先にテストを追加
修正しますか?
[はい / このまま続行]
ステップ 4-4: 実装ファイルの作成/更新
新規ファイルの場合:
Write ツールで作成:
file_path: {implementationFile}
content: <生成した実装コード>
既存ファイルへの追加の場合:
Read でファイルを読み込み、Edit で追加:
file_path: {implementationFile}
old_string: <ファイルの末尾>
new_string: <ファイルの末尾> + <新しい関数>
ステップ 4-5: 実装コードの確認
生成した実装コードをユーザーに確認:
実装コード:
{生成した実装コード}
Green Phase原則チェック:
✓ 最小限の実装
✓ テストを通すことに集中
✓ WHYコメント記載
この実装で進めますか?
AskUserQuestion で確認:
question: "この実装で進めますか?"
header: "Green Phase"
options: [
{ label: "はい、進めます", description: "Phase 5(Green確認)に進みます" },
{ label: "修正する", description: "実装コードを修正します" }
]
出力:
Phase 5: テスト実行(Green確認)
ステップ 5-1: unit-test-runnerエージェント呼び出し
Task ツールを使用して unit-test-runner サブエージェントを起動:
Task({
subagent_type: 'unit-test-runner',
prompt: '{testFile} のテストを実行してください',
description: 'Run Green phase tests'
})
ステップ 5-2: テスト結果の解析
テスト実行結果を解析:
期待される成功:
PASS backend/src/utils/state.test.ts
✓ 正しいstateが渡された場合にtrueを返す (3 ms)
✓ 異なるstateが渡された場合にfalseを返す (1 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
失敗の例:
FAIL backend/src/utils/state.test.ts
✕ 正しいstateが渡された場合にtrueを返す (5 ms)
Expected: true
Received: false
ステップ 5-3: 結果判定と報告
成功の場合:
🟢 Green Phase: 全テストが成功しました
テスト結果:
✓ 正しいstateが渡された場合にtrueを返す
✓ 異なるstateが渡された場合にfalseを返す
次のフェーズ(Refactor)に進みますか?
AskUserQuestion で確認:
question: "次のフェーズ(Refactor)に進みますか?"
header: "Green Phase 完了"
options: [
{ label: "はい、進めます", description: "Phase 6(リファクタリング)に進みます" },
{ label: "実装を修正", description: "Phase 4に戻ります" },
{ label: "リファクタリングをスキップ", description: "Phase 7(最終確認)に進みます" }
]
失敗の場合:
❌ Green Phase: テストが失敗しました
失敗したテスト:
✕ 正しいstateが渡された場合にtrueを返す
エラー内容:
- Expected: true
- Received: false
Phase 4(実装)に戻って修正しますか?
AskUserQuestion で確認:
question: "Phase 4に戻って修正しますか?"
header: "テスト失敗"
options: [
{ label: "はい、修正します", description: "Phase 4に戻ります" },
{ label: "中断する", description: "コマンドを終了します" }
]
判定基準:
- ✅ 全テスト成功 → Phase 6へ
- ❌ 一部/全部失敗 → Phase 4へ戻る
出力:
- テスト実行結果
- 成功/失敗の判定
- 次フェーズへの遷移指示
Phase 6: リファクタリング(Refactor)
ステップ 6-1: リファクタリング可能性の分析
テストと実装のコードを解析し、リファクタリング候補を抽出:
対象:
- 実装コード(重複排除、変数名改善、可読性向上)
- テストコード(共通ロジック抽出、可読性向上)
リファクタリング候補の例:
リファクタリング候補:
実装コード:
- なし(既に最小限)
テストコード:
1. 変数名の明確化
- state → actualState
- expectedState はそのまま
2. 期待値の定数化
- VALID_STATE = 'abc123'
- INVALID_STATE = 'xyz789'
リファクタリング原則:
- 動作を変えない
- テストが通ることを前提とする
- 過度な最適化は避ける(YAGNI原則)
ステップ 6-2: リファクタリング提案の提示
リファクタリング候補をユーザーに提示:
リファクタリング提案:
1. テストコードの変数名を明確化
- state → actualState
- expectedState はそのまま
2. 期待値を定数化
- VALID_STATE = 'abc123'
- INVALID_STATE = 'xyz789'
リファクタリングを実行しますか?
AskUserQuestion で確認:
question: "リファクタリングを実行しますか?"
header: "Refactor Phase"
options: [
{ label: "全て実行", description: "すべての提案を実行します" },
{ label: "選択", description: "実行する提案を選択します" },
{ label: "スキップ", description: "リファクタリングをスキップします" }
]
「選択」が選択された場合:
各提案について個別に確認:
question: "提案 {番号}: {提案内容} を実行しますか?"
header: "リファクタリング選択"
options: [
{ label: "はい", description: "この提案を実行します" },
{ label: "いいえ", description: "スキップします" }
]
ステップ 6-3: リファクタリングの実行
Edit ツールでリファクタリングを実行:
テストコードのリファクタリング例:
import { verifyState } from './state'
describe('verifyState', () => {
const VALID_STATE = 'abc123'
const INVALID_STATE = 'xyz789'
test('正しいstateが渡された場合にtrueを返す', () => {
const actualState = VALID_STATE
const expectedState = VALID_STATE
const result = verifyState(actualState, expectedState)
expect(result).toBe(true)
})
test('異なるstateが渡された場合にfalseを返す', () => {
const actualState = INVALID_STATE
const expectedState = VALID_STATE
const result = verifyState(actualState, expectedState)
expect(result).toBe(false)
})
})
ステップ 6-4: リファクタリング内容の確認
リファクタリング後のコードをユーザーに確認:
リファクタリング完了:
{差分表示}
次のフェーズ(最終確認)に進みますか?
AskUserQuestion で確認:
question: "次のフェーズ(最終確認)に進みますか?"
header: "Refactor Phase 完了"
options: [
{ label: "はい、進めます", description: "Phase 7(最終確認)に進みます" },
{ label: "さらにリファクタリング", description: "追加のリファクタリングを実施します" }
]
出力:
Phase 7: 最終確認(Final Verification)
ステップ 7-1: unit-test-runnerエージェント呼び出し
Task ツールを使用して unit-test-runner サブエージェントを起動:
Task({
subagent_type: 'unit-test-runner',
prompt: '{testFile} のテストを実行してください',
description: 'Run final verification'
})
ステップ 7-2: テスト結果の解析
テスト実行結果を解析:
期待される結果: 全テスト成功(リファクタリング前と同じ)
失敗の場合: リファクタリングでバグが混入
ステップ 7-3: 結果判定
成功の場合:
Phase 8(設計書整合性チェック)へ進む
失敗の場合:
❌ 最終確認: リファクタリング後にテストが失敗しました
失敗したテスト:
✕ 正しいstateが渡された場合にtrueを返す
リファクタリングをロールバックしますか?
AskUserQuestion で確認:
question: "リファクタリングをロールバックしますか?"
header: "リファクタリング失敗"
options: [
{ label: "はい、ロールバックします", description: "Phase 6に戻ります" },
{ label: "修正する", description: "Phase 6に戻って修正します" },
{ label: "中断する", description: "コマンドを終了します" }
]
判定基準:
- ✅ 全テスト成功 → Phase 8へ
- ❌ 失敗 → Phase 6へ戻る
Phase 8: 設計書とのテスト整合性チェック(Design Verification)
ステップ 8-1: 関連設計書の読み込み
Phase 2(ステップ 2-0)で読み込んだ設計書を再度確認する。コンテキストが失われている場合は Read ツールで再読み込みする。
必ず読み込む設計書:
docs/design/test-policy.md
ラベルに応じて読み込む設計書:
| ラベル | 追加で読み込む設計書 |
|---|
| backend | docs/design/backend-design.md, docs/design/backend-api-specification.md, docs/design/backend-flow.md |
| frontend | docs/design/frontend-design.md |
抽出する情報:
- インターフェース定義(関数シグネチャ、入出力の型、ステータスコード)
- シーケンス図・フロー図(処理フロー、分岐条件、正常系・異常系パス)
- エラーハンドリング仕様(エラーケース、エラーレスポンス)
- ビジネスルール・制約条件
- test-policy.md セクション 2.8 のテスト対象判断基準
ステップ 8-2: テストケースと設計書の照合
実装したテストファイルを Read で読み込み、設計書から抽出した情報と照合する。
照合観点:
| # | 観点 | 確認内容 |
|---|
| 1 | テストケースの網羅性 | 設計書に記載された正常系・異常系の処理パスがテストされているか |
| 2 | インターフェース整合性 | テストで使用している入出力(引数の型、レスポンスの構造、ステータスコード等)が設計書のインターフェース定義と一致しているか |
| 3 | エラーケースの網羅性 | 設計書に記載されたエラーパターン(バリデーションエラー、認証エラー等)がテストされているか |
| 4 | テスト対象の妥当性 | test-policy.md セクション 2.8 の基準に照らして、テスト対象が適切か(テスト不要なコードにテストを書いていないか) |
| 5 | テスト方針への準拠 | test-policy.md の方針(AAAパターン、命名規則、テストダブル方針、SUT明示、ブラックボックステスト等)に準拠しているか |
照合ルール:
- 設計書に明記されているがテストされていないケース → 不足として報告
- test-policy.md セクション 2.8 で「テスト対象としないもの」に該当するテスト → 過剰として報告
- 設計書の型定義・ステータスコードとテストの期待値が不一致 → 不整合として報告
- 設計書に記載がないが、ビジネスロジック上テストすべきケース → 推奨追加として報告(強制はしない)
ステップ 8-3: チェック結果の報告
照合結果をユーザーに報告:
報告フォーマット:
=== 設計書とのテスト整合性チェック ===
参照した設計書:
- docs/design/test-policy.md
- {ラベルに応じた設計書}
■ テストケースの過不足
不足しているテストケース:
- {設計書に記載があるがテストされていないケースと、その根拠(設計書のどのセクション)}
過剰なテストケース:
- {test-policy.md の基準に照らしてテスト不要なケースと、その理由}
※ 不足・過剰なし の場合: 「過不足なし」と表示
■ インターフェース整合性
- {設計書の定義とテストの入出力が一致しているか、不一致がある場合はその内容}
■ テスト方針への準拠
- {test-policy.md の方針に準拠しているか、違反がある場合はその内容}
総合判定: ✅ 問題なし / ⚠️ 要対応
問題なしの場合:
AskUserQuestion で確認:
question: "設計書との整合性チェックが完了しました。問題は見つかりませんでした。完了処理に進みますか?"
header: "整合性チェック完了"
options: [
{ label: "はい、進めます", description: "Phase 9(完了処理)に進みます" },
{ label: "テストを追加する", description: "追加テストを実装します(Phase 2に戻る)" }
]
要対応の場合:
AskUserQuestion で確認:
question: "設計書との整合性に問題が見つかりました。どのように対応しますか?"
header: "整合性チェック"
options: [
{ label: "不足テストを追加", description: "Phase 2に戻って不足テストを追加します" },
{ label: "問題なしとして続行", description: "現状のまま完了処理に進みます" },
{ label: "中断する", description: "コマンドを終了します" }
]
分岐処理:
- 「不足テストを追加」→ Phase 2へ戻る(追加テストのTDDサイクル開始。Phase 2〜8を繰り返す)
- 「はい、進めます」/「問題なしとして続行」→ Phase 9へ
- 「中断する」→ 処理を中止
出力:
- 照合結果レポート
- 不足・過剰・不整合の一覧
- 次フェーズへの遷移指示
Phase 9: 完了処理(Completion)
ステップ 9-1: GitHub Issueのタスクチェックリスト更新
Bash ツールで実装・テストに関連するタスクを完了マークに更新:
BODY=$(gh issue view {番号} --json body --jq '.body')
UPDATED_BODY=$(echo "$BODY" | sed 's/- \[ \] \(.*実装.*\)/- [x] \1/g' | sed 's/- \[ \] \(.*テスト.*\)/- [x] \1/g')
gh issue edit {番号} --body "$UPDATED_BODY"
該当するタスクが見つからない場合はスキップ(エラーにしない)。
ステップ 9-2: TDDサイクル完了レポート
TDDサイクル完了を報告:
✅ TDDサイクル完了!
作成されたファイル:
- {実装ファイルパス} (実装)
- {テストファイルパス} (テスト)
テスト結果:
✓ 全テスト成功 ({テスト数} tests, 0 failures)
TDD原則チェック:
✓ Red → Green → Refactor サイクル完遂
テスト方針準拠チェック(test-policy.md):
✓ SUT 明示(const sut = ...)
✓ AAAパターン・フェーズコメント規則
✓ テストケース命名(日本語、メソッド名なし)
✓ テストダブル方針(モック不使用 / スタブ検証なし)
✓ テストデータ配置(関数内 / Object Mother)
✓ テストケース分離(仕様条件ごと / 正常系・異常系分離)
✓ 検証対象(振る舞いのみ、実装詳細なし)
設計書整合性チェック:
✓ テストケースの過不足なし(または対応済み)
✓ インターフェース定義と一致
✓ テスト方針に準拠
GitHub Issue更新:
✓ Issue #{番号} のタスクチェックリストを更新しました
Next Actions:
1. git commit でコミット作成
2. 次の機能のTDDサイクルを開始
3. /ci で静的解析・全テスト実行
次の機能を実装しますか?
AskUserQuestion で確認:
question: "次の機能を実装しますか?"
header: "TDDサイクル完了"
options: [
{ label: "/tdd を再実行", description: "別のIssueでTDDサイクルを開始します" },
{ label: "終了", description: "コマンドを終了します" }
]
出力:
- 最終テスト結果
- TDDサイクル完了レポート
- Next Action提案
エラーハンドリング一覧
| エラー条件 | 判定方法 | 処理内容 |
|---|
| Issue が見つからない | gh issue view がエラー | エラーメッセージ表示して再入力(最大3回) |
| Issue の body が空 | JSON body フィールドが空 | エラーメッセージ表示して中止 |
| テストフレームワーク未検出 | package.json解析で検出なし | インストールを提案、中止 |
| Red Phase: 予期しない失敗 | エラー種別で判定 | Phase 2に戻る(テスト修正) |
| Red Phase: テストが成功してしまった | テスト結果で判定 | 警告表示、Phase 2に戻る |
| Green Phase: テスト失敗 | テスト結果で判定 | Phase 4に戻る(実装修正) |
| 最終確認: テスト失敗 | テスト結果で判定 | Phase 6に戻る(リファクタリング修正/ロールバック) |
| 設計書整合性: 不足テスト検出 | 設計書との照合で検出 | 報告表示、Phase 2に戻って追加テスト or 続行 |
| 設計書整合性: 過剰テスト検出 | test-policy基準で検出 | 報告表示、ユーザー判断(削除 or 続行) |
| 設計書整合性: インターフェース不整合 | 設計書の型定義との照合 | 報告表示、修正提案、ユーザー選択 |
| TDD原則違反 | コード解析で検出 | 警告表示、修正提案、ユーザー選択 |
| ファイル書き込み失敗 | Write/Editがエラー | エラーメッセージ表示して中止 |
| 不正な入力(3回連続) | 再入力カウンター = 3 | 処理を中止 |
テスト方針準拠チェック詳細(test-policy.md 基準)
Phase 2(テスト作成)のチェック
1. SUT の明示(セクション 2.5)
it('正常なリクエストの場合にリダイレクトを返す', async () => {
const result = await handler(event, context)
expect(result.statusCode).toBe(302)
})
describe('認可エンドポイント', () => {
const sut = handler
it('正常なリクエストの場合にリダイレクトを返す', async () => {
const event = createApiGatewayEvent()
const result = await sut(event, context)
expect(result.statusCode).toBe(302)
})
})
2. AAAパターンとフェーズコメント(セクション 2.5)
it('有効なIDの場合にユーザー情報を返す', async () => {
const event = createApiGatewayEvent({ pathParameters: { id: 'user-123' } })
const result = await sut(event, context)
expect(result.statusCode).toBe(200)
})
it('有効なIDの場合にユーザー情報を返す', async () => {
const event = createApiGatewayEvent({ pathParameters: { id: 'user-123' } })
const result = await sut(event, context)
expect(result.statusCode).toBe(200)
})
it('ゴールド会員の場合に割引が適用された注文サマリーを返す', async () => {
const user = createUser({ membershipLevel: 'gold' })
const items = [createItem({ price: 1000 }), createItem({ price: 2000 })]
const event = createApiGatewayEvent({
body: JSON.stringify({ userId: user.id, items }),
})
const result = await sut(event, context)
const body = JSON.parse(result.body)
expect(result.statusCode).toBe(200)
expect(body.totalAmount).toBe(2700)
})
3. テストケース命名(セクション 2.5)
'handleRequestは不正な入力に対して400を返す'
'不正な入力でエラーを返す'
'必須パラメータが欠落している場合に400を返す'
'有効期限切れのセッションは拒否される'
4. テストダブル方針(セクション 2.4)
vi.mock('./utils/helper')
const stubRepository = { findById: vi.fn(() => ({ id: 'user-123', name: 'Alice' })) }
expect(stubRepository.findById).toHaveBeenCalledWith('user-123')
const stubRepository = { findById: () => ({ id: 'user-123', name: 'Alice' }) }
const result = await sut(event, context)
expect(result.statusCode).toBe(200)
5. テストデータの配置(セクション 2.6)
let event: APIGatewayProxyEvent
beforeEach(() => {
event = createApiGatewayEvent({ pathParameters: { id: 'user-123' } })
})
const createApiGatewayEvent = (overrides?: Partial<APIGatewayProxyEvent>) => ({
httpMethod: 'GET', path: '/', headers: {}, pathParameters: null,
queryStringParameters: null, body: null, ...overrides,
})
it('有効なIDの場合にユーザー情報を返す', async () => {
const event = createApiGatewayEvent({ pathParameters: { id: 'user-123' } })
const result = await sut(event, context)
expect(result.statusCode).toBe(200)
})
6. テストケースの分離とパラメータ化(セクション 2.5)
it('不正なリクエストの場合にエラーを返す', async () => {
})
it.each(['', 'invalid', '@no-local'])(
'メールアドレス「%s」が不正な場合に400を返す',
async (email) => {
const event = createApiGatewayEvent({ body: JSON.stringify({ email }) })
const result = await sut(event, context)
expect(result.statusCode).toBe(400)
}
)
it('未認証の場合に403を返す', async () => {
const event = createApiGatewayEvent({ headers: {} })
const result = await sut(event, context)
expect(result.statusCode).toBe(403)
})
7. 検証対象(セクション 2.3)
expect(formatNameSpy).toHaveBeenCalledWith('alice')
expect(validateInputSpy).toHaveBeenCalledTimes(1)
const result = await sut(event, context)
expect(result.statusCode).toBe(200)
expect(JSON.parse(result.body).userId).toBe('user-123')
Phase 4(実装)のチェック
1. 最小限の実装
export function verifyState(state: string, expectedState: string): boolean {
if (!state || !expectedState) {
throw new Error('Invalid input')
}
return state === expectedState
}
export function verifyState(state: string, expectedState: string): boolean {
return state === expectedState
}
2. 過度な最適化の禁止
export function verifyState(state: string, expectedState: string): boolean {
if (state.length !== expectedState.length) {
return false
}
return state === expectedState
}
export function verifyState(state: string, expectedState: string): boolean {
return state === expectedState
}
使用可能なツール
- AskUserQuestion: ユーザー対話(Issue指定、テストケース選択、確認等)
- Glob: Issueファイル検索、package.json検索
- Read: Issueファイル、Planファイル、既存ファイル読み込み
- Write: テストファイル/実装ファイルの新規作成
- Edit: 既存ファイルへのテスト/実装追加、リファクタリング
- Task: unit-test-runnerエージェント呼び出し(テスト実行)
実装上の注意点
-
既存パターンの踏襲
- update-designと同じアーキテクチャ
- 同じツールセット
- 同じエラーハンドリングパターン
-
TDD原則の厳格な適用
- 各フェーズでTDD原則をチェック
- 違反時は警告表示と修正提案
- ユーザーに強制はしない(警告のみ)
-
段階的なUX
- 各フェーズの完了を明確に表示
- 次フェーズへの遷移はユーザー確認
- いつでも前フェーズに戻れる
-
テストフレームワークの自動検出
- package.jsonから Jest/Vitest を検出
- 対応するimport文とassertion構文を生成
-
コード生成の品質
- TypeScript型定義を適切に生成
- JSDoc形式のドキュメントを生成
- WHYコメントを自動追加
-
エラーリカバリー
- 各フェーズで失敗した場合、前フェーズに戻れる
- 最大3回まで自動リトライ
- 3回失敗したら中断を提案
-
パフォーマンス
- テスト実行は unit-test-runner に委譲
- コード生成は必要最小限
-
トレーサビリティ
- 生成されたコードにTDD原則準拠のコメントを自動追加
- WHYコメントで実装理由を明記