gen-test-plan について
gen-test-planは、リポジトリの技術スタックとブランチの変更を分析し、実行可能なYAML形式のE2Eテスト計画を作成するスキルです。ユーザーに見える動作を重視し、自動テストの重複を避けます。
主な機能
- 01技術スタックとブランチ変更の分析
- 02変更が影響するユーザー向け入口の追跡
- 03コア機能と設定経路の分類
- 04YAML形式のE2Eテスト計画の作成
- 05網羅性の確認とテストランナー手順の除外
活用シーン
- 01ブランチ変更に対するQA計画の作成
- 02実際の操作による変更後のCLIやAPIのテスト
- 03ユーザー向け機能のテスト網羅性の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ existential-birds/ beagle --skill gen-test-planインストール後、Claude Code で/gen-test-planを入力して読み込まれたか確認します。
同梱ファイルreferences/stack-discovery.mdreferences/test-case-generation.md
詳細
テスト計画の生成
リポジトリの技術スタックとデフォルトブランチとの差分を分析し、ユーザーに見える影響に焦点を当てた、実行可能な YAML テスト計画を生成します。
これは E2E テスト計画であり、自動テストのラッパーではありません。 生成された計画は、人間の QA テスターとまったく同じように行動する自律エージェントによって実行されます。実際のバイナリを起動し、実際のエンドポイントにアクセスし、実際のデータベースを操作して、実際に観測できる動作を検証します。
重要なルール:自動テストを重複させない
プロジェクトに既存の自動テストスイートを再実行するテスト手順を、決して生成しないでください。 これには次のことが含まれます。
- テスト手順として
cargo test、pytest、npm test、go test、mix test、または同等のコマンドを実行しない - 単体テスト/統合テストのモジュールをテストケースでラップしない
- 「テストを実行して合格を確認する」—これは QA ではなく CI の役割です
テストランナーを呼び出すテスト手順を書こうとしていることに気づいたら、手を止めて考え直してください。 「人間のテスターなら、この機能が動作することをどう確認するだろうか?」と考えてください。その答えが「単体テストを実行する」になることはありません。
E2E テスト手順の例:
- バイナリをビルドし、実際の引数で実行して、標準出力/標準エラー出力/終了コードを確認する
- サーバーを起動し、curl でアクセスする
- 実際のデータベースに書き込む CLI コマンドを実行し、その後データベースを照会して検証する
- TUI を起動し、(スクリーンショットまたはプロセスのライフサイクルを通じて)描画されることを確認する
- 複数のコマンドを連鎖させて、ユーザーのワークフロー全体をエンドツーエンドで実行する
必須ゲート
以下を順番どおりに完了してください。各ゲートの合格条件を満たすまで、次のゲートに進まないでください(各合格時には、コマンド出力の貼り付け、記述したリスト、またはディスク上に生成したファイルなど、後から取得できる証拠を残してください)。実施順序: ゲート 1 はステップ 2 の前、ゲート 2 はステップ 5 の前、ゲート 3 はステップ 7 の前、ゲート 4~5 は(ステップ 7 の概要の後に)ステップ 8 の間に実施します。
-
差分とベースを特定(ステップ 1 の後) —
--baseが指定されていればそこからベースブランチを特定し、指定されていなければリポジトリのデフォルトブランチ(ステップ 1 に従ってmainまたはmaster)を使用します。HEADと$(git merge-base HEAD origin/<base_branch>)(またはリモート参照が異なる場合は同等のもの)を比較します。合格:current_branch、base_branch、使用した merge-base の SHA または範囲、そしてgit diff --name-only <merge-base>..HEADによるchanged_filesを記録します(空のリストでもかまいません。その場合は出力を貼り付けるか引用し、「no file changes vs base」と明記してください)。 -
トレースを完了(ステップ 4 の後) — 合格: テスト対象となる影響を受ける各エントリーポイントを、コア機能または設定/管理に分類し、ステップ 4 の要件を満たします。つまり、少なくとも 1 つのテストがコアエントリーポイントを対象とするか、それが不可能な理由を記録して手動レビューが必要であることを明示します。
-
計画ファイルの検証(ステップ 6 の後、ステップ 7 の前) —
docs/testing/test-plan.yamlが存在し、次のコマンドが終了コード 0 を返すことを確認します(YAML を解析し、4 つすべてのトップレベルキーが存在することを検証します。選択肢を並べた単一のgrep -Eはどれか 1 つの一致だけでも通過してしまうため、代用しないでください)。python3 -c "import sys, yaml; d = yaml.safe_load(open('docs/testing/test-plan.yaml')) or {}; missing = [k for k in ('version', 'metadata', 'setup', 'tests') if k not in d]; sys.exit('Missing keys: ' + ', '.join(missing) if missing else 0)" -
自動テストの重複なし(ステップ 8) — 合格: すべての
run:ステップと、すべてのservices:のcommand:を対象に、プロジェクトのテストランナー(cargo test、pytest、npm test、go test、mix test、jest、vitest、mochaなど)を検索し、呼び出しがゼロであることを確認します。見つかった場合は、実際の E2E 操作に置き換えるか削除し、ゲート 3 を再実行してください。 -
動作の網羅性(ステップ 8) — 合格:
metadata.changes_summaryと最近のコミットメッセージを読み直し、少なくとも 1 つのテストのcontext/stepsが、そこで説明されている主要なユーザー向け動作を実行することを確認します。機能(たとえば新しいプロバイダー)について説明しているのに、それを呼び出す手順がない場合は、そのテストを追加するか、検証を不合格としてください。
引数
--base <branch>:差分を比較するベースブランチ(デフォルト:main)- Path:対象ディレクトリ(デフォルト:現在の作業ディレクトリ)
ステップ 1:リポジトリのコンテキストを収集する
# Get current branch
git rev-parse --abbrev-ref HEAD
# Resolve base branch: use --base if supplied, otherwise default (main → master)
BASE_BRANCH="${BASE_BRANCH:-$(git rev-parse --verify origin/main >/dev/null 2>&1 && echo main || echo master)}"
MERGE_BASE="$(git merge-base HEAD "origin/${BASE_BRANCH}")"
# Get changed files vs base
git diff --name-only "${MERGE_BASE}"..HEAD
# Get commit messages for context
git log --oneline "${MERGE_BASE}"..HEAD
取得する情報:
current_branch:ブランチ名base_branch:比較対象のデフォルトブランチchanged_files:変更されたファイルの一覧commit_messages:PR の内容
ステップ 2:技術スタックを検出する
スタック検出コマンド、エントリーポイントの特定、ポートの特定、トレースのルールについては、references/stack-discovery.md を参照してください。
ステップ 3:ユーザー向けのエントリーポイントを特定する
「ユーザー向けのエントリーポイント」とは、人が操作するものを指します。CLI サブコマンド、HTTP エンドポイント、UI ルート、TUI 画面、gRPC サービス、データベースのマイグレーション、実行時の動作に影響する設定ファイルなどです。
CLI アプリケーション(Rust/clap、Python/argparse/click、Go/cobra)
# Rust (clap) — look for Subcommand derives and command enums
grep -rn "Subcommand\|#\[command\]" --include="*.rs" | head -20
# Python (click/typer/argparse)
grep -rn "@click.command\|@app.command\|add_parser\|add_subparser" --include="*.py" | head -20
# Go (cobra)
grep -rn "cobra.Command\|AddCommand" --include="*.go" | head -20
以下のマップを作成します:
- CLI サブコマンド:コマンド名+説明+ファイル:行番号
- サブコマンドごとの必須引数とフラグ
- バイナリが読み取る環境変数(
env、std::env::var、os.Getenv、os.environを grep で検索)
HTTP/API サービス
Python(FastAPI/Flask):
grep -rn "@app\.\(get\|post\|put\|delete\|patch\)" --include="*.py" | head -20
grep -rn "@router\.\(get\|post\|put\|delete\|patch\)" --include="*.py" | head -20
Node.js(Express/Fastify):
grep -rn "app\.\(get\|post\|put\|delete\)" --include="*.ts" --include="*.js" | head -20
grep -rn "router\.\(get\|post\|put\|delete\)" --include="*.ts" --include="*.js" | head -20
Rust(axum/actix/rocket):
grep -rn "Router::new\|\.route(\|#\[get\]\|#\[post\]\|HttpServer" --include="*.rs" | head -20
Go(net/http、gin、chi):
grep -rn "http.HandleFunc\|r.GET\|r.POST\|router.Get\|router.Post" --include="*.go" | head -20
Elixir(Phoenix):
grep -rn "get \"/\|post \"/\|pipe_through\|live \"/\|scope \"/\"" --include="*.ex" | head -20
ブラウザー UI のルート
grep -rn "createBrowserRouter\|<Route\|path=" --include="*.tsx" --include="*.jsx" | head -20
データベースとマイグレーション
# SQL migrations
ls migrations/ db/migrate/ priv/repo/migrations/ 2>/dev/null
# Schema files
ls schema.sql schema.prisma 2>/dev/null
統合マップを作成します:
- CLI サブコマンド:名前+引数+ファイル:行番号
- API エンドポイント:メソッド+パス+ファイル:行番号
- UI ルート:パス+コンポーネント+ファイル:行番号
- データベースのマイグレーション:ファイル名+作成・変更する内容
- 設定:動作に影響する環境変数と設定ファイル
ステップ 4:変更をエントリーポイントまで追跡する
変更されたファイルごとに、ユーザー向けの機能に影響するかどうかを判断します。
- エントリーポイントへの直接的な変更 — ファイルにルート定義が含まれている
- インポートチェーンの分析 — 変更されたファイルをインポートしている箇所を特定し、エントリーポイントまでさかのぼる
- アーキテクチャを考慮したトレース — grep だけに頼らず、プロジェクトの CLAUDE.md、README、またはアーキテクチャのドキュメントを読み、データフローとモジュール間の関係を把握する
- トレース経路を記録する — テストコンテキストに記録する
エコシステムごとのインポートチェーン分析
# Rust — use/mod/crate references and workspace deps
grep -rn "use.*<crate>\|mod <module>" --include="*.rs"
grep -rn "<crate-name>" --include="Cargo.toml"
# Python — from/import
grep -rn "from.*<module>\|import.*<module>" --include="*.py"
# TypeScript/JavaScript — import/require
grep -rn "from.*<module>\|require.*<module>" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx"
# Elixir — alias/import/use
grep -rn "alias.*<Module>\|import.*<Module>\|use.*<Module>" --include="*.ex" --include="*.exs"
# Go — package references
grep -rn "<package>\." --include="*.go"
上記でエコシステムが扱われていない場合、または grep の結果から判断できない場合は、プロジェクトの CLAUDE.md、README、またはアーキテクチャのドキュメントを読み、モジュールグラフを把握して、変更されたファイルからユーザー向けのエントリーポイントまでデータフローをたどってください。
影響を受けるエントリーポイントを分類する
影響を受けるエントリーポイントをすべて特定したら、それぞれを分類します。
| カテゴリ | 説明 | 例 | 優先度 |
|---|---|---|---|
| コア機能 | エンドユーザー向けに機能が実際の処理を行うエントリーポイント | チャットエンドポイント、API アクション、データ処理パイプライン、生成フロー | 高 — 最初にテスト |
| 設定/管理 | 機能のセットアップ、切り替え、設定を行うエントリーポイント | 設定ページ、管理ダッシュボード、設定の切り替え、ドロップダウンの選択 | 低 — コア機能の後にテスト |
分類ルール:
- 「ユーザーがこの機能を使う(設定するのではなく)場合、どのエントリーポイントを操作するか」と考えてください。それがコア機能です
- 新しいドロップダウン項目を追加する設定ページは設定に分類されます。その項目を実際に使用するエンドポイントはコア機能です
- 同じ変更ファイル(例:新しいプロバイダーモジュール)が設定ページと機能を実行するエンドポイントの両方に影響する場合があります。両方を追跡する必要があります
要件: 設定/管理のテストを生成する前に、少なくとも 1 つのテストでコア機能のエントリーポイントを対象にする必要があります。コア機能のエントリーポイントを特定できない場合は、その理由を明記し、手動レビューが必要であることを示してください。
出力: 影響を受けるエントリーポイントごとに、以下を記録します:
- 影響する変更ファイル
- インポート/依存関係のチェーン
- 分類: コア機能または設定/管理
- このエントリーポイントにテストが必要な理由
ステップ 5:テストケースの生成
詳細な API/ブラウザーのテンプレート、優先順位付けのルール、テストケースのガイドラインについては、references/test-case-generation.md を参照してください。
ステップ 6:YAML テスト計画の作成
テスト計画ファイルを作成します。
mkdir -p docs/testing
docs/testing/test-plan.yaml に書き込みます。
version: 1
metadata:
branch: <current_branch>
base: <base_branch>
generated: <ISO timestamp>
changes_summary: |
<Summary of what this PR changes based on commit messages and diff>
setup:
stack:
- type: <rust|node|python|go|elixir|docker>
package_manager: <cargo|pnpm|npm|yarn|uv|poetry|mix|none>
prerequisites:
# Services or infrastructure the tests need running
- name: <e.g., PostgreSQL>
check: <command to verify it's available, e.g., "pg_isready -h localhost">
build:
# Commands to build the project artifacts (binaries, assets, etc.)
- <build command, e.g., "cargo build --workspace">
services:
# Long-running processes to start before tests (servers, watchers, etc.)
# Omit if the project is a CLI tool or library with no server component
- command: <start command>
health_check:
url: http://localhost:<port>/health
timeout: 30
env:
# Environment variables needed by tests (use ${VAR} for secrets)
DATABASE_URL: "${DATABASE_URL}"
tests:
# CLI test example — run the built binary with real arguments:
- id: TC-01
name: <CLI test name>
context: |
<Why this test exists, which changes affect it>
steps:
- run: <command that a human would type in their terminal>
- run: <follow-up command to verify the effect>
expected: |
<Expected behavior: exit code, stdout content, side effects>
# API test example:
- id: TC-02
name: <API test name>
context: |
<Why this test exists, which changes affect it>
steps:
- action: curl
method: GET
url: http://localhost:<port>/<path>
expected: |
<Expected behavior in natural language>
# Database verification example:
- id: TC-03
name: <Database test name>
context: |
<Why this test exists, which changes affect it>
steps:
- run: <command that writes to the database>
- run: psql "${DATABASE_URL}" -c "SELECT ... FROM ... WHERE ..."
expected: |
<Expected rows, schema state, or migration effect>
# Browser test example (always use agent-browser CLI commands):
- id: TC-04
name: <UI test name>
context: |
<Why this test exists, which changes affect it>
steps:
- run: agent-browser open http://localhost:<port>/<path>
- run: agent-browser snapshot -i
- run: agent-browser click @<ref>
- run: agent-browser snapshot -i
- run: agent-browser screenshot evidence/tc-04.png
expected: |
<Expected behavior in natural language>
evidence:
screenshot: evidence/tc-04.png
ステップ 7:概要の報告
テスト計画を生成した後、次の操作を行います。
## Test Plan Generated
**File:** `docs/testing/test-plan.yaml`
**Branch:** <current_branch> → <base_branch>
### Detected Stack
| Component | Type | Port |
|-----------|------|------|
| <component> | <type> | <port> |
### Tests Generated
| ID | Name | Type | Affected By |
|----|------|------|-------------|
| TC-01 | <name> | curl/browser | <files> |
### Entry Point Coverage
- **Covered:** <N> entry points with tests
- **Unchanged:** <M> entry points not affected by this PR
### Next Steps
1. Review the generated test plan at `docs/testing/test-plan.yaml`
2. Adjust test values and expectations as needed
3. Run the tests by invoking the **run-test-plan** skill ([run-test-plan](../run-test-plan/SKILL.md))
ステップ 8:検証
計画を完成として扱う前に、根拠を示して Hard gates 1~5 を満たしていることを確認します(上記の Hard gates を参照)。次に、以下を実行します。
# Verify file was created
ls -la docs/testing/test-plan.yaml
# Validate YAML syntax
python3 -c "import yaml; yaml.safe_load(open('docs/testing/test-plan.yaml'))" && echo "Valid YAML"
# Check required fields
grep -E "^version:|^metadata:|^setup:|^tests:" docs/testing/test-plan.yaml
検証チェックリスト:
-
docs/testing/test-plan.yamlにテスト計画ファイルが作成されている - YAML の構文が有効である
- テストケースが少なくとも 1 つ生成されている
- セットアップコマンドが検出されたスタックに一致している
- 各テストに id、name、steps、expected フィールドがある
- 自動テストの重複がない: 計画内のすべての
run:およびcommand:ステップについて、テストランナーの呼び出し(cargo test、pytest、npm test、go test、mix test、jest、vitest、mochaなど)を grep します。いずれかのステップがプロジェクトのテストランナーを呼び出している場合、計画は検証に失敗します。そのステップを削除し、実際の E2E 操作に置き換えてください。 - 動作の網羅性: 少なくとも 1 つのテストで、
changes_summaryに記載された主な動作変更を検証します。changes_summaryとコミットメッセージを再確認してください。たとえば、「新しい LLM プロバイダーを追加」といった機能が記載されているのに、その機能を呼び出すテスト(例:プロバイダー経由でメッセージを送信するテスト)がない場合、計画は検証に失敗します。完了前に、中核機能を対象とする不足テストを追加してください。 - 設定のみの計画ではない: すべてのテストが設定/管理のエントリーポイントを対象とし、中核機能のエントリーポイントを対象とするテストが 1 つもない場合、計画は未完成です。ステップ 4 に戻り、中核機能のエントリーポイントを特定して、テストを追加してください。
ルール
- E2E のみ — すべてのテストステップで、人が行うのと同じように、実際にビルドされた成果物(バイナリ、サーバー、UI)を操作する必要があります。自動テストスイートをラップして実行してはいけません。
docs/testing/ディレクトリが存在しない場合は、必ず作成してください。- 影響を受けるエントリーポイントごとに、少なくとも 1 つのテストを生成してください。
- 各テストが重要である理由を説明するコンテキストを含めてください(変更内容に紐付けます)。
expectedフィールドには自然言語を使用してください(エージェントが解釈します)。- CLI プロジェクト: テストステップでは、実際の引数を指定して実際のバイナリを呼び出し、標準出力、標準エラー、終了コード、副作用(作成されたファイル、書き込まれたデータベース行、起動されたプロセス)を検証してください。
- サーバープロジェクト: セットアップでサーバーを起動し、curl/agent-browser を使ってテストしてください。
- ライブラリのみのプロジェクト(バイナリもサーバーもない場合): 変更がユーザー向けのエントリーポイント(CLI、サーバー、UI)のないライブラリ内部コードのみの場合は、そのことを明記し、テストスイートを実行するのではなく、小さなドライバースクリプトを使って公開 API 経由でライブラリを操作するテストを生成してください。
- ポートの検出は、保守的なデフォルト値(API は 8000、フロントエンドは 5173/3000)を使用してください。
- ブラウザー自動化のステップでは、必ず
agent-browserCLI コマンドを使用してください(例:agent-browser open、agent-browser snapshot -i、agent-browser click @ref)。抽象的な操作構文は使用しないでください。 - 要素を操作する前、およびナビゲーション/DOM の変更後には、必ず
agent-browser snapshot -iを実行してください。 - ブラウザーテストの証拠を記録するには、
agent-browser screenshot <path>を使用してください。 - シークレットには
${ENV_VAR}形式を使用し、認証情報をハードコードしないでください。 - ユーザー向けの変更が検出されない場合は、その理由を説明し、手動での検証を提案してください。