生成AIにドキュメントからテストケースを生成してもらったり、仕様書からコードを生成してもらったりする機会が増えてきました。しかし、こんな経験はないでしょうか?
- 入力の理解が不十分 - AIが仕様を読み飛ばす、重要な制約を見落とす
- 出力フォーマットがバラバラ - 同じ入力でも毎回異なる構造で出力される
これらの問題により、生成物の品質が安定せず、結局人間が大幅に手直しすることになります。
この記事では、これらの課題を解決するために試験導入中の「中間表現(IR)」と「出力テンプレート」を組み合わせたパターンを、テストケースの生成を例に紹介します。
課題:自然言語から直接生成する限界
PRD(製品要求仕様書)やPDD(製品設計書)から直接テストケースを生成しようとすると、いくつかの問題が発生します。
PRD/PDD(自然言語) → 生成AI → テストケース
この単純なパイプラインでは:
- 入力の理解が不十分 - AIが仕様を読み飛ばす、重要な制約や代替フローを見落とす
- 出力フォーマットがバラバラ - 同じ入力でも毎回異なる構造・項目で出力される
解決策:中間表現とテンプレートを挟む
コンパイラが高級言語を機械語に変換する際、一度中間表現(IR)に変換してから最適化を行うように、生成AIにも中間表現を挟むことで品質が劇的に向上します。さらに、最終出力にテンプレートを適用することで、フォーマットも統一できます。
PRD/PDD(自然言語) → UseCase IR → Test Plan IR → テンプレート → テストケース
なぜこのアプローチが効くのか?
- IRスキーマによる強制抽出 - 必須フィールドに沿って情報を整理するため、抜け漏れが防げる
- テンプレートによるフォーマット統一 - 毎回同じ構造で出力されるため、品質が安定する
- 段階的な変換 - 一度に複雑な変換をせず、段階を踏むことで各ステップの品質を担保
- 再利用性 - 同じUseCase IRから複数の成果物(テストプラン、チェックリスト、E2Eテスト等)を生成可能
なぜ2つのIRに分けるのか?
1つのIRで済ませることもできますが、役割を分けることで品質が向上します。
| IR | 役割 | 内容 |
|---|---|---|
| UseCase IR | 仕様の構造化 | PRD/PDDから「何ができるか」を抽出。入力項目、制約、フローなど |
| Test Plan IR | テスト戦略の設計 | UseCase IRから「何をテストするか」を設計。境界値追加、優先度付けなど |
UseCase IRは仕様の忠実な構造化であり、テスト以外の用途(コード生成、ドキュメント生成等)にも再利用できます。Test Plan IRはテストに特化した変換を行い、境界値テストの自動追加や優先度の分類など、テスト設計のノウハウを注入します。
この分離により:
- UseCase IRは複数の用途で共有可能(Single Source of Truth)
- Test Plan IRでテスト固有の最適化が可能
- 各ステップの責務が明確になり、デバッグしやすい
品質向上の鍵:構造化フォーマットによる強制
生成AIに自由形式で出力させると、品質がばらつきます。構造化されたフォーマットを強制することで、この問題を解決できます。
IRスキーマ:「何を抽出するか」を強制
IRにはスキーマ(必須フィールド)を定義します。AIはこのスキーマに沿って出力するため、必要な情報を漏れなく抽出できます。
# スキーマで必須フィールドを定義 usecase: input_fields: [] # ← 必ず入力項目を列挙 constraints: [] # ← 必ず制約を列挙 alternative_flows: [] # ← 必ず代替フローを列挙
スキーマがなければ、AIは「重要そうな項目」だけを抽出し、制約や代替フローを見落とします。スキーマがあれば、空欄を埋める形で全項目を強制的に抽出できます。
出力テンプレート:「どう出力するか」を強制
最終成果物にもテンプレートを定義します。これにより、毎回同じフォーマットで出力されます。
# テストプラン: {機能名} ## 前提条件 ← 必ず記載 ## テストケース ← 必ず記載 ## 実行結果 ← 必ず記載
テンプレートがなければ、同じIRからでも毎回異なる構造のドキュメントが生成されます。テンプレートがあれば、どの機能でも同じ構造のドキュメントが得られます。
冒頭の課題との対応
| 課題 | 解決策 | 効果 |
|---|---|---|
| 入力の理解が不十分 | IRスキーマで必須フィールドを定義 | 全項目を強制抽出 |
| 出力フォーマットがバラバラ | 出力テンプレートで構造を定義 | 一貫したフォーマット |
パイプラインの全体像
flowchart LR
subgraph Input["📥 入力"]
PRD["📋 PRD"]
PDD["📝 PDD"]
Figma["🎨 Figma"]
PR["💻 GitHub"]
end
subgraph IR["🔄 中間表現"]
UC["📦 UseCase IR"]
TP["🧪 Test Plan IR"]
end
subgraph Output["📤 成果物"]
TestPlan["📑 テストプラン"]
Checklist["✅ チェックリスト"]
end
PRD --> UC
PDD --> UC
Figma --> UC
PR --> UC
UC --> TP
TP --> TestPlan
TP --> Checklist
UseCase IR:仕様を構造化する
まず、PRD/PDDから全仕様を体系的に抽出するUseCase IRを生成します。
UseCase IRのスキーマ
erDiagram
UseCase_IR {
string id
string name
array sources
}
UseCase_IR ||--o{ Precondition : has
UseCase_IR ||--o{ InputField : has
UseCase_IR ||--o{ Constraint : has
UseCase_IR ||--o{ MainFlowStep : has
UseCase_IR ||--o{ AlternativeFlow : has
UseCase_IR ||--o{ SystemBehavior : has
UseCase_IR ||--o{ Postcondition : has
InputField {
string name
string type
boolean required
}
Constraint {
string name
string condition
string behavior
}
AlternativeFlow {
string name
string condition
string outcome
}
UseCase IRが強制する抽出項目
| セクション | 抽出内容 | テストへの変換 |
|---|---|---|
input_fields (required) |
必須入力項目 | 各フィールドのバリデーションテスト |
input_fields (optional) |
任意入力項目 | オプション機能のテスト |
constraints |
制約条件 | ビジネスルールテスト + 境界値テスト |
main_flow |
メインフロー | ハッピーパステスト |
alternative_flows |
代替フロー | 各分岐のカバレッジ |
system_behaviors |
システム動作 | 非同期処理・インテグレーションテスト |
postconditions |
事後条件 | 結果検証テスト |
出力例: 配車予約機能
usecase: id: "UC-RESERVATION-001" name: "配車予約の作成" preconditions: - "ユーザーがログイン済み" input_fields: - name: "weekdays" type: "select" required: true - name: "time" type: "time" required: true - name: "start_date" type: "date" required: true - name: "end_date" type: "date" required: true constraints: - name: "期間制限" condition: "終了日が開始日から1ヶ月を超える" behavior: "エラーを表示" alternative_flows: - name: "複数選択" condition: "複数の曜日を選択" outcome: "選択した全曜日に予約が生成される" system_behaviors: - name: "非同期処理" description: "個別予約を非同期で生成" postconditions: - "配車予約パターンが保存されている" - "個別予約が生成されている"
Test Plan IR:テスト戦略に変換する
UseCase IRをそのままテストケースに変換すると、機械的な1:1マッピングになり冗長になります。Test Plan IRを挟むことで、インテリジェントなテスト設計が可能になります。
Test Plan IRが行う変換
| UseCase IRの記述 | Test Plan IRでの変換 |
|---|---|
type: time |
境界値テスト追加: 00:00, 23:59 |
type: date + 制約 |
境界値テスト追加: 上限値、上限+1 |
alternative_flows |
各フローのインタラクションテスト |
| 複数の類似項目 | 統合: 代表テストにまとめる |
| 仕様に明記なし | UI/UXテスト自動追加: 確認ダイアログ等 |
| 仕様に明記なし | 優先度を自動分類: High/Medium/Low |
Test Plan IRのスキーマ
erDiagram
TestPlan_IR {
string id
string name
string usecase_id
}
TestPlan_IR ||--o{ Category : has
Category ||--o{ TestCase : contains
Category {
string name
string description
}
TestCase {
string id
string name
string priority
}
TestCase ||--o{ Step : has
Step {
string action
string expected
}
出力例: 配車予約機能のTest Plan IR
test_plan: id: "TP-RESERVATION-001" name: "配車予約作成 テスト計画" usecase_id: "UC-RESERVATION-001" categories: - name: "基本フロー" test_cases: - id: "TC-001" name: "配車予約を作成できる" priority: "High" steps: - action: "必須項目を入力して登録" expected: "予約が作成される" # 境界値テスト(Test Plan IRで自動追加) - id: "TC-002" name: "時刻00:00で予約作成できる" priority: "Medium" - id: "TC-003" name: "時刻23:59で予約作成できる" priority: "Medium" - name: "バリデーション" test_cases: - id: "TC-010" name: "期間が1ヶ月を超えるとエラー" priority: "High" # UI/UXテスト(Test Plan IRで自動追加) - id: "TC-011" name: "確認ダイアログが表示される" priority: "Low"
出力テンプレート:フォーマットを統一する
IRが「何を抽出するか」を強制するのに対し、テンプレートは「どう出力するか」を強制します。Test Plan IRを最終的なテストプランに変換するためのテンプレートを定義します。
テストプランテンプレート
# テストプラン: {test_plan.name} **Test Plan ID:** {test_plan.id} **UseCase ID:** {test_plan.usecase_id} ## 前提条件 {for precondition in usecase.preconditions} - {precondition} {end} ## テストケース {for category in test_plan.categories} ### {category.index}. {category.name} {for test_case in category.test_cases} #### {test_case.id}: {test_case.name} `{test_case.priority}` | # | 操作 | 期待結果 | |---|------|----------| {for step in test_case.steps} | {step.index} | {step.action} | {step.expected} | {end} {end} {end}
このテンプレートにより、どのTest Plan IRからでも同じ構造のドキュメントが生成されます。
最終出力:テストプラン
上記のTest Plan IRとテンプレートから、以下のようなテストプランが生成されます。
# テストプラン: 配車予約機能 **Test Plan ID:** TP-RESERVATION-001 **UseCase ID:** UC-RESERVATION-001 ## 前提条件 - ユーザーがログイン済み ## テストケース ### 1. 基本フロー #### TC-001: 配車予約を作成できる `High` | # | 操作 | 期待結果 | |---|------|----------| | 1 | 必須項目を全て入力する | 各項目が正しく設定される | | 2 | 「登録」→「確定」をクリック | 予約が作成され、完了メッセージが表示される | #### TC-002: 時刻00:00で予約作成できる `Medium` | # | 操作 | 期待結果 | |---|------|----------| | 1 | 迎車希望時刻に「00:00」を設定 | 予約が正常に作成される(境界値テスト) | ### 2. バリデーション #### TC-010: 曜日未選択でエラー `High` | # | 操作 | 期待結果 | |---|------|----------| | 1 | 曜日を選択せずに登録 | エラーメッセージが表示される | ### 3. 制約 #### TC-020: 期間が1ヶ月を超えるとエラー `High` | # | 操作 | 期待結果 | |---|------|----------| | 1 | 終了日を開始日から1ヶ月+1日に設定 | エラーメッセージが表示される |
このように、IRとテンプレートを組み合わせることで、どの機能でも同じ構造のテストプランが生成されます。
検証結果:IRの有無でどれだけ差が出るか
実際の機能で、IR経由の生成と直接生成を比較しました。
| 指標 | IR経由 | 直接生成 | 改善率 |
|---|---|---|---|
| テストケース数 | 19 | 7 | +171% |
| バリデーションテスト | ✅ 5項目 | ❌ 0項目 | - |
| 境界値テスト | ✅ 4項目 | ❌ 0項目 | - |
| 代替フロー | ✅ 4項目 | ❌ 0項目 | - |
| 優先度分類 | ✅ H/M/L | ❌ なし | - |
直接生成で漏れた項目の例
❌ 必須項目未入力でエラーが表示される ❌ 境界値での動作確認 (00:00, 23:59) ❌ 代替フローのテスト ❌ 確認ダイアログの操作テスト
これらは全てUseCase IRの構造化によって強制的に抽出される項目です。
UseCase IR only vs Full IR(UseCase IR + Test Plan IR)
「UseCase IRだけで十分では?」という疑問に対する検証も行いました。
| 指標 | UseCase IR only | Full IR |
|---|---|---|
| テスト数 | 24-26(多い) | 19-29(適切) |
| 冗長性 | 高い(機械的1:1マッピング) | 低い(類似テスト統合) |
| 境界値テスト | ❌ なし | ✅ 自動注入 |
| ユーザー操作パス | トリガーのみ | 完全なインタラクション |
| 優先度 | なし | High/Medium/Low |
結論: Test Plan IRは単にテストを追加するのではなく、機械的な生成をインテリジェントなテスト設計に変換します。
比較サマリー
| アプローチ | テスト数 | バリデーション | 制約 | 境界値 | UI/UX | 優先度 |
|---|---|---|---|---|---|---|
| IRなし | 7-8 | ❌ | ❌ | ❌ | ❌ | ❌ |
| UseCase IR only | 24-26 | ✅ | ✅ | ❌ | ❌ | ❌ |
| Full IR | 19-29 | ✅ | ✅ | ✅ | ✅ | ✅ |
発展:他の用途への応用
UseCase IRの最大の価値は、一度作成すれば様々な用途で再利用できることです。
UseCase IRを共有するメリット
UseCase IRは仕様を構造化した抽象表現であり、特定の用途に依存しません。そのため、異なるチームや異なる目的で同じUseCase IRを活用できます。
flowchart TB
subgraph Input["📥 入力"]
PRD["PRD"]
PDD["PDD"]
Figma["Figma"]
PR["GitHub PR"]
end
UC["📦 UseCase IR"]
subgraph TestIRs["🧪 テスト系IR"]
TP["Test Plan IR"]
E2E["E2E Test IR"]
INT["Integration Test IR"]
end
subgraph ImplIRs["🔧 実装系IR"]
Schema["Schema IR"]
API["API IR"]
UI["UI Component IR"]
BL["Business Logic IR"]
end
subgraph TestOutputs["📋 テスト成果物"]
Manual["手動テスト"]
PlaywrightCode["Playwrightテスト"]
GoTests["Goテスト"]
end
subgraph ImplOutputs["💻 実装成果物"]
Migrations["SQLマイグレーション"]
ProtoFiles["Protoファイル"]
Resolvers["GraphQL Resolver"]
Handlers["gRPCハンドラ"]
Components["Reactコンポーネント"]
Services["Goサービス"]
end
Input --> UC
UC --> TestIRs
UC --> ImplIRs
TP --> Manual
E2E --> PlaywrightCode
INT --> GoTests
Schema --> Migrations
Schema --> ProtoFiles
API --> Resolvers
API --> Handlers
UI --> Components
BL --> Services
共有による具体的なメリット
| メリット | 説明 |
|---|---|
| 重複作業の削減 | QAチームがテスト用に仕様を読み解く作業と、開発チームが実装用に仕様を読み解く作業を一本化できる |
| 認識のズレ防止 | 同じUseCase IRを参照することで、「テストで想定した仕様」と「実装された仕様」の齟齬を防げる |
| 変更の追跡 | 仕様変更時にUseCase IRを更新すれば、そこから派生する全ての成果物に変更を反映できる |
| 専門IRへの最適化 | 各チームは自分たちの専門IR(Test Plan IR、Schema IR等)の品質向上に集中できる |
専門IRの役割
UseCase IRから各専門IRへの変換時に、用途に応じた最適化が行われます:
| 専門IR | 追加される情報 | 用途 |
|---|---|---|
| Test Plan IR | 境界値、優先度、テスト戦略 | 手動テスト計画 |
| E2E Test IR | ページセレクタ、テストフィクスチャ | Playwrightテスト生成 |
| Integration Test IR | DBフィクスチャ、モック定義 | Goインテグレーションテスト |
| Schema IR | データ型、制約、インデックス | SQLマイグレーション、Proto定義 |
| API IR | エンドポイント、リクエスト/レスポンス | GraphQL Resolver、gRPCハンドラ |
| UI Component IR | 状態管理、イベントハンドラ | Reactコンポーネント |
| Business Logic IR | ドメインルール、エラーハンドリング | Goサービス |
このように、UseCase IRを共有の土台として、各チームが専門IRを通じて最適化された成果物を生成する構造が実現できます。
まとめ
生成AIの入出力の品質を管理するには、構造化されたフォーマットを強制することが効果的です。
| 課題 | 解決策 | 効果 |
|---|---|---|
| 入力の理解が不十分 | IRスキーマで必須フィールドを定義 | 全項目を強制抽出 |
| 出力フォーマットがバラバラ | 出力テンプレートで構造を定義 | 一貫したフォーマット |
この2つを組み合わせたパイプラインにより:
- テストカバレッジが+171%向上
- バリデーション、境界値、代替フローの漏れがゼロに
- どの機能でも一貫した品質のドキュメントを生成
生成AIに自由形式で出力させず、フォーマットで縛る。これが入出力の品質を安定させる鍵です。
書いた人: Claude Code
書かせた人: @kitasuke