ワンクリックで
tdd
GitHub IssueからTDD(Test-Driven Development)サイクルを実行し、テストと実装を段階的に作成。backendまたはfrontendの実装にTDDを適用する際に使用。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
GitHub IssueからTDD(Test-Driven Development)サイクルを実行し、テストと実装を段階的に作成。backendまたはfrontendの実装にTDDを適用する際に使用。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
いま進行中のセッションの会話の流れを5項目で要約し、コンテキストスイッチから戻った人間が状況を思い出すのを助ける。「catchup」「ここまで要約して」「どこまでやったか」「文脈を思い出したい」と指示されたときに使う。
CDK実装の一気通貫オーケストレーションワークフロー。CDK実装→コードレビュー→レビュー指摘修正→CI実行→CI指摘修正を人間への確認なしに自律実行する。CLAUDE.md の開発フロー Step 7(CDK実装)で使用。Issue番号を引数に指定すること(例: /cdk-dev 15)。frontend/backend実装には使用しない。
アプリケーション実装の一気通貫オーケストレーションワークフロー。実装(テスト変更を含む場合はTDD、含まない場合は直接実装)→コードレビュー→レビュー指摘修正→CI実行→CI指摘修正→設計書整合性チェック→設計書修正を人間への確認なしに自律実行する。CLAUDE.md の開発フロー Step 6(frontend/backend実装)で使用。Issue番号を引数に指定すること(例: /code-dev 15)。CDKには使用しない。
統合設計ワークフロー。GitHub IssueをもとにIssueを読み込み設計書(docs/design/)を更新し、自動レビューと修正まで実行する。仕様駆動開発のStep 4で使用。Issue番号を引数に指定すること(例: /design 15)
AWS CDK実装専用コマンド。GitHub IssueからCDK実装・テスト・CDK合成まで自動実行する。CDKインフラの実装を依頼されたときに使用すること。
AWS CDK インフラコードを詳細にレビュー。/cdk-review コマンドが呼ばれたとき、またはユーザーが CDK コードのレビューを依頼したときに使用する。cdk/ ディレクトリ内の TypeScript ファイルを対象に、型安全性・CDK ベストプラクティス・プロジェクトルール準拠を自動チェックする。
| 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
GitHub Issueの内容を解析し、Red-Green-Refactorサイクルに従ってテストと実装を段階的に作成する。 本プロジェクトの仕様駆動開発フローにおける Step 6: 実装(TDD) を支援する。
backend/、frontend/ディレクトリ配下)のみを対象とするcdk/配下のインフラコードは絶対に実装・変更しないことまず、与えられた指示(プロンプト)に Issue指定: に続く値が含まれているか確認する。
引数が提供されている場合(例: Issue指定: 15):
引数が提供されていない場合(空文字または指定なし):
AskUserQuestion ツールを使用してユーザーに入力を促す:
question: "実装したいIssueを指定してください。Issue番号(例: 15)を入力してください。"
header: "Issue指定"
options: [
{ label: "その他(手動入力)", description: "Issue番号を入力してください" }
]
multiSelect: false
取得情報:
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 #{番号} の本文が空です。
→ 処理を中止
取得したJSONから以下の情報を抽出:
1. Issue番号とタイトル
.number フィールド.title フィールド2. ラベル
.labels[].name フィールドから抽出
[{name: "backend"}, {name: "frontend"}] → ['backend', 'frontend']3. スコープ/作業項目
body内 ## スコープ / 作業項目 セクションの内容全体を抽出
4. タスク一覧(アプリケーションコードタスクのみ抽出)
body内 ## タスク一覧 セクションのチェックリスト(- [ ] 形式)を抽出し、アプリケーションコード(backend/frontend)に関するタスクのみをフィルタリングする。
フィルタリングルール:
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: "...", // 実装詳細(あれば)
}
ラベル情報から対象ディレクトリをマッピング:
| ラベル | 対応ディレクトリ |
|---|---|
| backend | backend/ |
| frontend | frontend/ |
| cdk, infra | cdk/ |
例:
backend → backend/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.tsfrontend の場合: frontend/src/**/*.ts, frontend/src/**/*.test.tsIssueから抽出した情報を整理してユーザーに確認:
表示フォーマット:
=== 実装仕様 ===
Issue: #{番号} {タイトル}
対象ファイル:
- 実装: {実装ファイルパス}
- テスト: {テストファイルパス}
実装する機能:
{スコープ/作業項目の要約}
{実装詳細があれば表示}
この内容でTDDサイクルを開始しますか?
AskUserQuestion で確認:
question: "上記の内容でTDDサイクルを開始しますか?"
header: "実装仕様確認"
options: [
{ label: "はい、開始します", description: "TDDサイクルを開始します" },
{ label: "修正する", description: "対象ファイルやIssue番号を修正します" },
{ label: "中断する", description: "コマンドを終了します" }
]
分岐処理:
出力:
{
issueNumber: 15,
issueTitle: "State検証機能の実装",
implementationFile: 'backend/src/utils/state.ts',
testFile: 'backend/src/utils/state.test.ts',
scope: "...",
implementationDetails: "..."
}
テストコードを書く前に、必ず以下の設計書を 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 |
グレーボックステストの考え方に基づき、Issue内容と設計書からテストケースを設計する。
抽出元:
テストケース設計の原則(test-policy.md セクション 2.3, 2.5, 2.8 に基づく):
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: "テストケースを入力" }
]
原則(test-policy.md に完全準拠):
const sut = handler 等で明示する// Arrange // Act // Assert を記載。それ以外は空白行のみで区切るtoHaveBeenCalledWith 等の検証は禁止it.each を使用。正常系と異常系は分離テストフレームワークの検出:
Glob で package.json を検索し、Readで読み込んで依存関係を確認:
jest → Jest形式vitest → Vitest形式生成例(バックエンド: Lambda handler のテスト):
// backend/src/handlers/__tests__/authorize.test.ts
import { handler } from '../authorize'
// Object Mother: APIGatewayイベントのファクトリ関数
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 () => {
// Arrange
const event = createApiGatewayEvent({
queryStringParameters: {},
})
// Act
const result = await sut(event, context)
// Assert
const body = JSON.parse(result.body)
expect(result.statusCode).toBe(400)
expect(body.error).toBe('invalid_request')
})
})
生成例(フロントエンド: コンポーネントテスト):
// frontend/src/components/__tests__/LoginForm.test.tsx
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()
})
})
生成したテストコードを test-policy.md の全項目に照らしてチェック:
チェック項目:
SUT の明示(セクション 2.5)
const sut = handler 等でテスト対象を明示AAAパターン(セクション 2.5)
フェーズコメント(セクション 2.5)
// Arrange // Act // Assert を記載テストケース命名(セクション 2.5)
テストダブル方針(セクション 2.4)
toHaveBeenCalledWith 等の検証 → 警告(過剰検証)テストデータの配置(セクション 2.6)
beforeEach にテスト固有データ → 警告テストケースの分離(セクション 2.5)
パラメータ化テスト(セクション 2.5)
it.each を使用it.each にまとめている → 分離を提案検証対象(セクション 2.3)
テスト対象の妥当性(セクション 2.8)
テスト内 if 文(セクション 2.5)
フロントエンド固有(セクション 2.9、ラベルが frontend の場合のみ)
getByRole, getByText 等ユーザーが認識できる属性で要素取得違反時の警告例:
⚠️ テスト方針違反を検出(test-policy.md)
Phase 2: テスト作成
違反内容:
- [セクション 2.5] SUT が明示されていません → `const sut = handler` を追加してください
- [セクション 2.5] フェーズコメントが不要です(準備・確認が1行のため)→ 削除して空白行区切りにしてください
- [セクション 2.4] スタブに対する toHaveBeenCalledWith は過剰検証です → 最終結果のみを検証してください
対処方法:
1. `const sut = handler` を追加
2. 不要なフェーズコメントを削除
3. スタブの呼び出し検証を削除し、最終結果の検証に変更
修正しますか?
[はい / このまま続行]
新規ファイルの場合:
Write ツールで作成:
file_path: {testFile}
content: <生成したテストコード>
既存ファイルへの追加の場合:
Read でファイルを読み込み、Edit で追加:
file_path: {testFile}
old_string: <describeブロックの末尾>
new_string: <describeブロックの末尾> + <新しいテストケース>
生成したテストコードをユーザーに確認:
作成したテストコード:
{生成したテストコード}
TDD原則チェック:
✓ テストデータはテスト関数内に記述
✓ モック未使用
✓ WHYコメント記載
✓ 小さな単位のテスト
このテストで進めますか?
AskUserQuestion で確認:
question: "このテストで進めますか?"
header: "Red Phase"
options: [
{ label: "はい、進めます", description: "Phase 3(Red確認)に進みます" },
{ label: "修正する", description: "テストコードを修正します" }
]
出力:
Task ツールを使用して unit-test-runner サブエージェントを起動:
Task({
subagent_type: 'unit-test-runner',
prompt: '{testFile} のテストを実行してください',
description: 'Run Red phase tests'
})
テスト実行結果を解析:
期待される失敗の例:
Cannot find module './state' → 実装が存在しないverifyState is not a function → 関数が未定義ReferenceError: verifyState is not defined → export忘れ予期しない失敗の例:
SyntaxError: Unexpected token → 構文エラーError: Cannot find module (テスト側) → importパスエラー期待通りの失敗の場合:
🔴 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 2で作成したテストコードを解析し、実装すべき内容を抽出:
抽出する情報:
verifyState)state: string, expectedState: string)boolean)解析例:
テストから抽出した要件:
- 関数名: verifyState
- 引数: state: string, expectedState: string
- 戻り値: boolean
- 動作:
- 引数が一致する場合: true
- 引数が不一致の場合: false
原則:
docs/policy/code-comment-policy.md に従う(実装コメントは WHY、doc comment は契約を書く。コードが語る WHAT/HOW は書かない)生成例:
// backend/src/utils/state.ts
/**
* OIDC stateパラメータを検証する
*
* WHY: CSRF攻撃を防ぐため、認可リクエスト時に生成したstateと
* コールバック時に受け取ったstateが一致することを確認する
*
* @param state - コールバックで受け取ったstate
* @param expectedState - 認可リクエスト時に生成したstate
* @returns stateが一致する場合true、それ以外false
*/
export function verifyState(state: string, expectedState: string): boolean {
return state === expectedState
}
生成した実装コードをチェック:
チェック項目:
最小限の実装
最適化の禁止
コメント
違反時の警告例:
⚠️ TDD原則違反を検出
Phase 4: 実装
違反内容:
- テストにない機能が実装されています(引数のバリデーション)
TDD原則:
- テストを通す最小限のコードのみ実装する
対処方法:
1. テストにない機能を削除
2. 必要であれば、先にテストを追加
修正しますか?
[はい / このまま続行]
新規ファイルの場合:
Write ツールで作成:
file_path: {implementationFile}
content: <生成した実装コード>
既存ファイルへの追加の場合:
Read でファイルを読み込み、Edit で追加:
file_path: {implementationFile}
old_string: <ファイルの末尾>
new_string: <ファイルの末尾> + <新しい関数>
生成した実装コードをユーザーに確認:
実装コード:
{生成した実装コード}
Green Phase原則チェック:
✓ 最小限の実装
✓ テストを通すことに集中
✓ WHYコメント記載
この実装で進めますか?
AskUserQuestion で確認:
question: "この実装で進めますか?"
header: "Green Phase"
options: [
{ label: "はい、進めます", description: "Phase 5(Green確認)に進みます" },
{ label: "修正する", description: "実装コードを修正します" }
]
出力:
Task ツールを使用して unit-test-runner サブエージェントを起動:
Task({
subagent_type: 'unit-test-runner',
prompt: '{testFile} のテストを実行してください',
description: 'Run Green phase tests'
})
テスト実行結果を解析:
期待される成功:
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
成功の場合:
🟢 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: "コマンドを終了します" }
]
判定基準:
出力:
テストと実装のコードを解析し、リファクタリング候補を抽出:
対象:
リファクタリング候補の例:
リファクタリング候補:
実装コード:
- なし(既に最小限)
テストコード:
1. 変数名の明確化
- state → actualState
- expectedState はそのまま
2. 期待値の定数化
- VALID_STATE = 'abc123'
- INVALID_STATE = 'xyz789'
リファクタリング原則:
リファクタリング候補をユーザーに提示:
リファクタリング提案:
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: "スキップします" }
]
Edit ツールでリファクタリングを実行:
テストコードのリファクタリング例:
// backend/src/utils/state.test.ts
import { verifyState } from './state'
describe('verifyState', () => {
const VALID_STATE = 'abc123'
const INVALID_STATE = 'xyz789'
test('正しいstateが渡された場合にtrueを返す', () => {
// WHY: OIDC認可フローでstateパラメータが改ざんされていないことを確認するため
const actualState = VALID_STATE
const expectedState = VALID_STATE
const result = verifyState(actualState, expectedState)
expect(result).toBe(true)
})
test('異なるstateが渡された場合にfalseを返す', () => {
// WHY: CSRF攻撃を防ぐため、不正なstateは拒否する必要がある
const actualState = INVALID_STATE
const expectedState = VALID_STATE
const result = verifyState(actualState, expectedState)
expect(result).toBe(false)
})
})
リファクタリング後のコードをユーザーに確認:
リファクタリング完了:
{差分表示}
次のフェーズ(最終確認)に進みますか?
AskUserQuestion で確認:
question: "次のフェーズ(最終確認)に進みますか?"
header: "Refactor Phase 完了"
options: [
{ label: "はい、進めます", description: "Phase 7(最終確認)に進みます" },
{ label: "さらにリファクタリング", description: "追加のリファクタリングを実施します" }
]
出力:
Task ツールを使用して unit-test-runner サブエージェントを起動:
Task({
subagent_type: 'unit-test-runner',
prompt: '{testFile} のテストを実行してください',
description: 'Run final verification'
})
テスト実行結果を解析:
期待される結果: 全テスト成功(リファクタリング前と同じ)
失敗の場合: リファクタリングでバグが混入
成功の場合:
Phase 8(設計書整合性チェック)へ進む
失敗の場合:
❌ 最終確認: リファクタリング後にテストが失敗しました
失敗したテスト:
✕ 正しいstateが渡された場合にtrueを返す
リファクタリングをロールバックしますか?
AskUserQuestion で確認:
question: "リファクタリングをロールバックしますか?"
header: "リファクタリング失敗"
options: [
{ label: "はい、ロールバックします", description: "Phase 6に戻ります" },
{ label: "修正する", description: "Phase 6に戻って修正します" },
{ label: "中断する", description: "コマンドを終了します" }
]
判定基準:
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 |
抽出する情報:
実装したテストファイルを Read で読み込み、設計書から抽出した情報と照合する。
照合観点:
| # | 観点 | 確認内容 |
|---|---|---|
| 1 | テストケースの網羅性 | 設計書に記載された正常系・異常系の処理パスがテストされているか |
| 2 | インターフェース整合性 | テストで使用している入出力(引数の型、レスポンスの構造、ステータスコード等)が設計書のインターフェース定義と一致しているか |
| 3 | エラーケースの網羅性 | 設計書に記載されたエラーパターン(バリデーションエラー、認証エラー等)がテストされているか |
| 4 | テスト対象の妥当性 | test-policy.md セクション 2.8 の基準に照らして、テスト対象が適切か(テスト不要なコードにテストを書いていないか) |
| 5 | テスト方針への準拠 | test-policy.md の方針(AAAパターン、命名規則、テストダブル方針、SUT明示、ブラックボックステスト等)に準拠しているか |
照合ルール:
照合結果をユーザーに報告:
報告フォーマット:
=== 設計書とのテスト整合性チェック ===
参照した設計書:
- 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: "コマンドを終了します" }
]
分岐処理:
出力:
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"
該当するタスクが見つからない場合はスキップ(エラーにしない)。
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: "コマンドを終了します" }
]
出力:
| エラー条件 | 判定方法 | 処理内容 |
|---|---|---|
| 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 | 処理を中止 |
1. SUT の明示(セクション 2.5)
// ❌ NGパターン: SUT が不明確
it('正常なリクエストの場合にリダイレクトを返す', async () => {
const result = await handler(event, context) // handler が SUT だと明示されていない
expect(result.statusCode).toBe(302)
})
// ✅ OKパターン: SUT を明示
describe('認可エンドポイント', () => {
const sut = handler
it('正常なリクエストの場合にリダイレクトを返す', async () => {
const event = createApiGatewayEvent()
const result = await sut(event, context)
expect(result.statusCode).toBe(302)
})
})
2. AAAパターンとフェーズコメント(セクション 2.5)
// ❌ NGパターン: シンプルなテストに不要なフェーズコメント
it('有効なIDの場合にユーザー情報を返す', async () => {
// Arrange ← 不要(準備が1行のため)
const event = createApiGatewayEvent({ pathParameters: { id: 'user-123' } })
// Act ← 不要
const result = await sut(event, context)
// Assert ← 不要(確認が1行のため)
expect(result.statusCode).toBe(200)
})
// ✅ OKパターン: シンプルなテスト → 空白行のみで区切る
it('有効なIDの場合にユーザー情報を返す', async () => {
const event = createApiGatewayEvent({ pathParameters: { id: 'user-123' } })
const result = await sut(event, context)
expect(result.statusCode).toBe(200)
})
// ✅ OKパターン: 準備が複数行 → フェーズコメントを入れる
it('ゴールド会員の場合に割引が適用された注文サマリーを返す', async () => {
// Arrange
const user = createUser({ membershipLevel: 'gold' })
const items = [createItem({ price: 1000 }), createItem({ price: 2000 })]
const event = createApiGatewayEvent({
body: JSON.stringify({ userId: user.id, items }),
})
// Act
const result = await sut(event, context)
// Assert
const body = JSON.parse(result.body)
expect(result.statusCode).toBe(200)
expect(body.totalAmount).toBe(2700)
})
3. テストケース命名(セクション 2.5)
// ❌ NGパターン: メソッド名を含む
'handleRequestは不正な入力に対して400を返す'
// ❌ NGパターン: 検証内容が曖昧
'不正な入力でエラーを返す'
// ✅ OKパターン: 日本語、メソッド名なし、事実表現
'必須パラメータが欠落している場合に400を返す'
'有効期限切れのセッションは拒否される'
4. テストダブル方針(セクション 2.4)
// ❌ NGパターン: 内部関数のモック
vi.mock('./utils/helper')
// ❌ NGパターン: スタブとのやりとりを検証(過剰検証)
const stubRepository = { findById: vi.fn(() => ({ id: 'user-123', name: 'Alice' })) }
// ...テスト実行後...
expect(stubRepository.findById).toHaveBeenCalledWith('user-123') // スタブの検証は禁止
// ✅ OKパターン: スタブはデータ提供のみ、最終結果を検証
const stubRepository = { findById: () => ({ id: 'user-123', name: 'Alice' }) }
const result = await sut(event, context)
expect(result.statusCode).toBe(200)
5. テストデータの配置(セクション 2.6)
// ❌ NGパターン: beforeEach にテスト固有データ
let event: APIGatewayProxyEvent
beforeEach(() => {
event = createApiGatewayEvent({ pathParameters: { id: 'user-123' } })
})
// ✅ OKパターン: Object Mother パターン + テスト関数内でカスタマイズ
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)
// ❌ NGパターン: 異なる仕様条件を1つのテストにまとめている
it('不正なリクエストの場合にエラーを返す', async () => {
// 未認証と不正メールが混在
})
// ✅ OKパターン: 仕様条件ごとに分離、同一条件のバリエーションは it.each
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)
// ❌ NGパターン: 実装の詳細を検証
expect(formatNameSpy).toHaveBeenCalledWith('alice')
expect(validateInputSpy).toHaveBeenCalledTimes(1)
// ✅ OKパターン: 観察可能な振る舞い(出力)を検証
const result = await sut(event, context)
expect(result.statusCode).toBe(200)
expect(JSON.parse(result.body).userId).toBe('user-123')
1. 最小限の実装
// ❌ NGパターン: テストにない機能の追加
export function verifyState(state: string, expectedState: string): boolean {
// バリデーション(テストにない)
if (!state || !expectedState) {
throw new Error('Invalid input')
}
return state === expectedState
}
// ✅ OKパターン: 最小限の実装
export function verifyState(state: string, expectedState: string): boolean {
return state === expectedState
}
2. 過度な最適化の禁止
// ❌ NGパターン: 最適化コード
export function verifyState(state: string, expectedState: string): boolean {
// 長さチェックで早期リターン(最適化)
if (state.length !== expectedState.length) {
return false
}
return state === expectedState
}
// ✅ OKパターン: シンプルな実装
export function verifyState(state: string, expectedState: string): boolean {
return state === expectedState
}
既存パターンの踏襲
TDD原則の厳格な適用
段階的なUX
テストフレームワークの自動検出
コード生成の品質
エラーリカバリー
パフォーマンス
トレーサビリティ