🧪 技術調査 - TestMap:AI生成テストを証拠付きで評価する基盤設計
目次

⚠️ 非公開(zenn 未公開)

🧪 技術調査 - TestMap:AI生成テストを証拠付きで評価する基盤設計

調査対象: Hunter Leary, Luke Hanuska, Chris Brown, TestMap: Evidence Infrastructure for Foundation-Model-Assisted Test Generation, arXiv 2606.10211(submitted 2026-06-08, cs.SE, CC BY 4.0、AIWare 2026 採録〔arXiv ページ記載〕)
調査日: 2026-06-11

概要

FM(Foundation Model)にユニットテストを書かせる実践は、もう珍しくありません。一方で「生成されたテストが良いテストかどうか」を判断する共通基盤は、まだ存在しません。多くのツールは「コンパイルが通った」「テストが green になった」「カバレッジが増えた」のいずれか一点だけを成果として報告します。しかし、通ること自体は品質を保証しません。assert を緩めればカバレッジは稼げますし、一度通っても flaky なテストかもしれないからです。

TestMap は、この問題に「評価指標を改善する」のではなく「証拠インフラ(evidence infrastructure)」という角度から答えるプロトタイプです。C#/.NET リポジトリを対象に、FM が生成した各テスト候補のライフサイクル全体を記録します。コンパイル失敗・修復・低インパクト・証拠陽性(evidence-positive)という状態を、カバレッジ差分・ミューテーション結果・修復履歴・プロンプト/レスポンスまでまとめて SQLite に構造化保存します。採用したテストだけでなく、捨てた候補とその理由まで残し、後から監査・再現できるようにする点が中核です。

著者の主張を一言にまとめると、こうなります。テスト生成エージェントの評価指標は、**生成成功率ではなく「証拠として採用できるテストの比率」**に寄せるべきだ、という主張です。

ただし注意点があります。本論文はプロトタイプの design case であり、定量的な有効性評価(実リポジトリでの採用率・カバレッジ改善幅)はありません。著者自身も「開発者がこの証拠をどう解釈・行動するかは未測定」と Section 5.1 に明記しています。効果の実証は今後の課題として残されています。本記事では TestMap の思想と機構を C4・データモデルとして整理しつつ、限界や反証も過信なく扱います。

特徴

TestMap が他の LLM テスト生成研究と異なるのは、「フィルタの出力(採否)」ではなく「判断材料(証拠)」を一級市民にした点です。

  1. 二値ではなくライフサイクルで記録する
    生成テストを pass/fail の二値で捨てません。4 つの pipeline outcome に分類します(ValidationFailed / Validated / ValidatedLowImpact / ValidatedEvidencePositive)。「通らなかった候補」も「通るが効果のない候補」も、状態と証拠ごと保存します。「通るだけの空テスト」を ValidatedLowImpact として可視化できる点が、中核の仕掛けです。

  2. 9 種類の証拠を構造化スキーマで保存する
    Repository / Target / Generation / Execution / Testing-impact / Quality / Failure / Repair / Strategy の 9 カテゴリです(論文 Table 1)。プロンプト・レスポンス・トークン数・実行時間・コンパイラ診断・修復試行までを SQLite に永続化します。CSV(1 行 = 1 生成または 1 修復試行、安定 ID で逆参照可能)でエクスポートします。

  3. 実験マトリクスを前提にした設計
    model × prompt × strategy × pass@k × repair loop × temperature × step ablation を experiment metadata として持ちます。MLflow / Weights & Biases / DVC の sweep(試行も棄却も全部残す)と構造的に同型です。失敗した run も安定 ID で証拠へ逆参照できます。

  4. 既存の .NET 品質ツール群を統合する
    Roslyn(静的解析・複雑度)、TRX(テスト結果)、Cobertura(カバレッジ)、Stryker.NET(ミューテーション)、xNose のカスタム版(C#/xUnit 向け test smell 検出)を 1 つのパイプラインに束ねます。特に Stryker.NET のミューテーション結果を ValidatedEvidencePositive の判定基準に組み込みます。

  5. 著者自身が 5 つの限界を明示している
    言語スコープ(C#/.NET 専用)、環境依存による失敗の混入、オラクル品質の限界、flakiness、そして「開発者がこの証拠をどう解釈・行動するかは未測定」という 5 点を Section 5.1 に列挙します。誇張のない設計提案です。

  6. Meta の TestGen-LLM / Assured LLMSE 思想を拡張する
    Meta の TestGen-LLM は「既存スイートを measurable に改善したか」でフィルタします。TestMap はこの「measurable improvement で評価する」思想を引き継ぎつつ、フィルタの「出力(採否)」ではなく「判断材料(証拠)」を一級市民にした点が差分です。Assured LLMSE が「採用テストを出す」のに対し、TestMap は「なぜ採用/不採用なのかを台帳に残す」設計です。

  7. provenance(来歴)の保全を設計思想の軸にする
    AI 生成コードは、コミットされた瞬間に「どのモデルが・どのプロンプトで・何を棄却して生まれたか」を失います。TestMap は棄却候補まで残して、この来歴の欠落を埋めます。イベントソーシング的な「状態だけでなく状態に至る出来事の列を保存し、replay 可能にする」発想と同型です。

構造

C4 model の各段階を「提案フレームワークの論理構造」に読み替えて示します。

システムコンテキスト図

TestMap がどのアクター・外部システムと接続するかを示します。

外部アクター TestMap 外部システム 証拠を確認 採否判断 パイプライン設定 実行 Evidence Store を監査 プロンプト送信 候補受信 ビルド テスト実行 コンテナ起動 テスト実行 clone provenance 取得 トリガー 結果受信 開発者テスト品質の確認採否判断 SRE / CI 担当者パイプライン組み込み実行管理 QA エンジニア証拠の監査戦略評価 TestMapFM 生成テストの証拠インフラC#/.NET 専用 FM APIテスト生成用Foundation Model .NET SDKrestore / build / testTRX・Cobertura DockerLinux / Windows再現性確保の実行環境 Git リポジトリC# ソース・テスト.sln CI システムGitHub Actions 等自動パイプライン統合先
要素 説明
開発者 FM 生成テストの採否を証拠に基づいて判断
SRE / CI 担当者 TestMap をパイプラインへ組み込み、実行管理
QA エンジニア Evidence Store を後から監査し、生成戦略を評価
TestMap FM 生成テスト候補のライフサイクル全体を証拠化・永続化
FM API テストコードを生成するモデル。model / provider / temperature を設定パラメータとして抽象化
.NET SDK restore → build → test を担当し、TRX・Cobertura XML を出力
Docker Linux / Windows コンテナで既存テスト・生成テストを再現可能に実行
Git リポジトリ C# ソース・テスト・.sln を提供。commit / branch が provenance として記録
CI システム TestMap をトリガーし、Evidence Store の CSV 結果を受信

コンテナ図

TestMap 内部の主要構成要素です。論文 Section 3.3 の 9 段パイプラインを機能ブロックに整理しています。

TestMap 内部 clone プロンプト 候補 コンテナ実行 コンテナ実行 Git リポジトリ FM API Docker RI Static Analysis複雑度・依存test smell Baseline ExecutionDocker で既存テスト実行TRX / Cobertura / Stryker.NET Result Attributionカバレッジ・TRX・mutant をsource method へマップ Target Selection生成対象メソッドを選択可変基準 Generation Pipeline多段 FM 生成 + repair全ステップ記録 Validation LayerPre 構文・Roslyn 診断Post 実行・delta 計算 Evidence StoreSQLite全 artifact を永続化 CSV Exporter1行 = 1生成 / 1修復安定 ID で逆参照
コンテナ(対応段) 説明
Repository Ingestion(段 1) リポジトリを clone し、Roslyn で .sln を発見。branch / commit を provenance として記録
Static Analysis(段 2) namespace / class / method / property を解析。LOC・循環的複雑度・test smell(xNose カスタム版)を記録
Baseline Execution(段 3) Docker で既存テストを実行。TRX・Cobertura XML・Stryker.NET(可能時)を収集
Result Attribution(段 4) カバレッジを source / method / line / branch へ、TRX をテストメソッドへ、mutation を source 箇所へマップ
Target Selection(段 5) 生成対象メソッドを可変基準(baseline coverage / surviving mutant / related tests / code metrics)で選択
Generation Pipeline(段 6、論文の段名は Test Generation) FM への多段プロンプト生成。各ステップを prompt / response / tokens / duration ごと記録
Validation Layer(段 7・8) Pre は構文抽出・Roslyn コンパイラ診断。Post は実行・coverage / mutation 収集・候補単位の evidence 計算
Evidence Store(段 9) SQLite。全 artifact を永続化
CSV Exporter(段 9 出力) 1 行 = 1 生成 or 1 修復試行。安定 ID で Evidence Store に逆参照可能

コンポーネント図

Generation Pipeline(段 6)の内部フローです。論文 Section 3.3 の記述から詳細化しています。

Evidence Package 構築 Scenario Planning 合成と修復 prompt response 各ステップ完了時 prompt response 診断 修復プロンプト 修復済み候補 各修復試行 Step Persister各ステップをprompt / response / tokensとして永続化 FM API Validation Layer へ Evidence Package ビルダーTarget / Repository / Baselineの証拠を束ねる Context Graph依存・呼び出し関係の任意グラフ構造 Scenario Plannerテストシナリオを設計 Method Name Plannerテストメソッド名を決定 Arrangement Planner前提状態を計画 Input Plannerテスト入力値を計画 Action Planner対象への操作を計画 Assertion Planner検証内容を計画 Test Synthesizerplanning 出力を統合しコード生成 Repair Engine任意の repair ループ診断を FM へ再送
コンポーネント 説明
Evidence Package ビルダー Target・Repository・Baseline の証拠を FM へのコンテキストとして束ねる
Context Graph メソッド間の依存・呼び出し関係を任意のグラフ構造で表現(実装詳細は論文記述から推測)
Scenario Planner テスト対象シナリオを設計。多段分解の第 1 ステップ
Method Name Planner テストメソッド名を決定
Arrangement Planner 前提状態・フィクスチャ・依存の注入方法を計画
Input Planner テスト入力値を計画
Action Planner テスト対象メソッドへの操作を計画
Assertion Planner assert の内容を計画
Test Synthesizer 各 planning ステップの出力を統合しテストコードを生成
Repair Engine Validation Layer の診断を FM へ再送し、任意回数の修復ループを実行
Step Persister 各ステップを prompt / response / tokens / duration ごと SQLite に保存

Context Graph の具体的な実装(AST ベース / 呼び出しグラフ / 依存グラフ等)は、論文に記載がありません。「arbitrary context graph」という表現のみで、上記は論文記述からの推測です。

データ

概念モデル

主要エンティティとその関連です。

Repository (no attributes) Project (no attributes) Experiment (no attributes) SourceMethod (no attributes) TestCandidate (no attributes) BaselineResult (no attributes) GenerationStep (no attributes) RepairAttempt (no attributes) PipelineOutcome (no attributes) Evidence (no attributes) CoverageResult (no attributes) MutationResult (no attributes) TestSmell (no attributes) contains runs declares targets measures produces triggers classifies associates generates includes includes delta delta detects
エンティティ 説明
Repository クローン元リポジトリ。provenance(ブランチ・コミットハッシュ)の起点
Project リポジトリ内の .sln / .csproj 単位。Roslyn で発見
SourceMethod テスト生成の対象メソッド(Target)。静的解析で抽出
TestCandidate FM が生成した 1 テストメソッド候補。ライフサイクル全体を保持
GenerationStep 多段分解の各ステップ。プロンプト/レスポンス/トークン数/所要時間を永続化
RepairAttempt コンパイル/実行エラーを診断フィードバックとして FM に再送した修復試行
Evidence 9 カテゴリを抽象化した証拠コンテナ。TestCandidate に 1 対多で関連
PipelineOutcome 4 値 enum。TestCandidate に 1 対 1
Experiment 実験マトリクス(model × approach × context_mode × budget × temperature × step_ablation)を定義
BaselineResult テスト生成前の既存スイートの実行結果(TRX/Cobertura/Stryker)。比較基準
CoverageResult メソッド・行・ブランチ単位のカバレッジ。Baseline と Candidate 差分を保持
MutationResult Stryker.NET によるミューテーション結果。Baseline と Candidate 差分を保持
TestSmell xNose カスタム版で検出したテストスメル。候補の品質証拠

Evidence は概念上の抽象コンテナです。実際の SQLite スキーマでは、9 カテゴリを各テーブル(candidates / generation_steps 等)への分散保存で表現します。

9 カテゴリの証拠(論文 Table 1)

各カテゴリが「どの問いに答えるか」を整理します。

カテゴリ 回答する問い
Repository どこで・どの条件で評価されたか
Target 何の振る舞いを意図して生成されたか
Generation どのように生成されたか
Execution プロジェクト内で実行できるか
Testing-impact baseline スイートを超える観測可能な価値があるか
Quality 保守可能でレビュー可能か
Failure 失敗候補から何がわかるか
Repair 有効化に何が必要だったか
Strategy このリポジトリでどのアプローチが有効か

情報モデル

TestCandidate を中心とした構造です。

TestCandidate + candidate_id: String + repository_id: String + experiment_id: String + target_method_id: String + model: String + provider: String + prompt_strategy: String + context_mode: String + attempt_number: Int + pass_at_k: Int + generated_code: String + pipeline_outcome: PipelineOutcome + validation_status: ValidationStatus + coverage_delta: Float + killed_mutants_delta: Int + survived_mutants_delta: Int + failure_category: String + has_repair: Bool + repair_attempts_count: Int + loc: Int + cyclomatic_complexity: Float + has_test_smells: Bool + created_at: String <<enumeration>> PipelineOutcome ValidationFailed Validated ValidatedLowImpact ValidatedEvidencePositive <<enumeration>> ValidationStatus PreExecution CompiledFailed ExecutionFailed Passed PassedLowImpact PassedEvidencePositive GenerationStep + step_id: String + candidate_id: String + step_type: StepType + status: String + prompt: String + response: String + format: String + prompt_token_count: Int + completion_token_count: Int + duration_ms: Float + errors: String <<enumeration>> StepType EvidencePackage ContextGraph ScenarioPlanning MethodNameGeneration ArrangementPlanning InputPlanning ActionPlanning AssertionPlanning FinalTestGeneration RepairGeneration RepairAttempt + repair_id: String + candidate_id: String + attempt_number: Int + diagnostics_provided: String + repair_prompt: String + repair_response: String + final_status: String + duration_ms: Float Experiment + experiment_id: String + repository_id: String + model: String + provider: String + approach: String + context_mode: String + temperature: Float + budget: Int + pass_at_k: Int + step_ablations: String classified by tracks has 1 0..* has 1 0..* typed by belongs to 0..* 1

ValidationStatus enum は、論文に明示の名称がありません。PipelineOutcome の検証フェーズを補完表現したものです(実装案)。

PipelineOutcome の判定ロジック

No Yes 未分類 改善なし 1 signal 以上を改善 TestCandidate 生成 コンパイル 実行検証に通る? ValidationFailed失敗理由を保存 Validated判定保留 baseline 比でcoverage か mutation を改善? ValidatedLowImpact通るが効果なし ValidatedEvidencePositive採用候補
Outcome 現場的な意味
ValidationFailed 「動かない」。理由は Failure evidence に保存。修復試行が記録
Validated 「通るが未分類」。Testing-impact 計算が未完または保留
ValidatedLowImpact 「通るだけ」の空テスト・自明テストを可視化する核心カテゴリ
ValidatedEvidencePositive coverage delta > 0 または killed_mutants_delta > 0。採用判断の入力になる候補

CSV 出力の列構成

論文 Section 3.3 段 9 の記述です。1 行 = 1 生成試行 or 1 修復試行で、安定 ID(candidate_id / repair_id)で SQLite の詳細 evidence へ逆参照できます。列名は論文に明示がなく、論文記述からの推測です。

列グループ 列名(推測含む)
Provenance repository_id, branch, commit_hash
Target target_method_id, class_name, method_name, namespace
Baseline state baseline_line_coverage, baseline_branch_coverage, baseline_killed_mutants
Generation config model, provider, approach, context_mode, temperature, budget, pass_at_k
Attempt attempt_number, candidate_id
Repair metadata has_repair, repair_attempts_count, repair_final_status
Validation status validation_status, pipeline_outcome
Testing-impact signals coverage_delta, killed_mutants_delta, survived_mutants_delta
Quality loc, cyclomatic_complexity, smell_types
Failure category failure_category, compiler_errors_summary

データ設計は 3 つの原則で貫かれています。

  • 採用候補だけでなく棄却候補も保存: ValidationFailed / ValidatedLowImpact も残し、「なぜ捨てたか」の監査証跡を形成
  • GenerationStep を step 単位で永続化: prompt ablation や strategy 比較の実験追跡が可能
  • 安定 ID による CSV ↔ SQLite の双方向参照: CSV は集計用の軽量ビュー、詳細は SQLite に保持

構築方法

TestMap 本体はプロトタイプ論文で、公開 OSS リポジトリは本調査時点(2026-06-11)で未確認です。以下の構築手順は、TestMap の思想(9 段パイプライン + 証拠インフラ)を自分の .NET プロジェクトに反映した実装案です。【実装案】 と明示します。論文に明記されたコンポーネント(Docker / dotnet test --collect / Stryker.NET / TRX / Cobertura / SQLite)は論文由来です。

前提ツールのインストール

# Stryker.NET — ミューテーションテスト(論文 段3 で使用)
dotnet tool install -g dotnet-stryker

# ReportGenerator — Cobertura XML を HTML レポートへ変換(任意)
dotnet tool install -g dotnet-reportgenerator-globaltool

coverlet.collectordotnet new xunit で生成したプロジェクトにデフォルトで含まれます(Microsoft Learn 記載)。

Step 1: Docker でベースライン実行環境を用意する【実装案】

論文 Section 3.3 段 3 は「Docker(Linux/Windows コンテナ)で既存テストを実行し、TRX と Cobertura を収集する」と記述しています。

# 【実装案】 .NET SDK 付き最小イメージ例(論文はイメージを指定していない)
FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /repo
COPY . .
RUN dotnet restore
# 【実装案】 Docker コンテナを起動してテスト実行
docker build -t testmap-baseline .
docker run --rm \
  -v "$(pwd)/results:/repo/results" \
  testmap-baseline \
  dotnet test --results-directory /repo/results \
              --logger "trx;LogFileName=baseline.trx" \
              --collect:"XPlat Code Coverage"

--logger "trx" は TRX 形式のテスト結果を、--collect:"XPlat Code Coverage" は Coverlet 経由で Cobertura XML を出力します。後者は Microsoft Learn 記載の正式フラグです。

Step 2: dotnet test で Cobertura カバレッジを収集する

dotnet test --collect:"XPlat Code Coverage"

出力ファイルは TestResults/{guid}/coverage.cobertura.xml です。XML 構造の主要フィールドは、論文 段 4「Result Attribution」で使用します。

<coverage line-rate="0.82" branch-rate="0.73">
  <packages>
    <package name="MyLib">
      <classes>
        <class name="MyLib.MyService" filename="src/MyService.cs">
          <methods>
            <method name="Calculate" signature="(System.Int32)">
              <lines>
                <line number="42" hits="5" branch="True" condition-coverage="100% (2/2)" />
              </lines>
            </method>
          </methods>
        </class>
      </classes>
    </package>
  </packages>
</coverage>

Step 3: Stryker.NET でミューテーションテストを実行する

論文 Section 3.3 段 3 は「可能な場合に Stryker.NET でミューテーションを実行する」と記述しています。

# テストプロジェクトのディレクトリで実行
dotnet stryker

設定ファイル stryker-config.json は公式ドキュメント記載の形式です。

{
  "stryker-config": {
    "reporters": ["progress", "html", "json"],
    "coverage-analysis": "perTest"
  }
}

慣例的なデフォルト出力先(StrykerOutput/ 配下、要確認)に JSON / HTML レポートが生成され、killed / survived / timeout の各 mutant が記録されます。

Step 4: 生成テスト候補を挿入して差分(Δ)を測る【実装案】

論文 Section 3.3 段 6〜8 の思想を反映した実装案です。論文はこの処理の内部実装詳細を公開していません。

# 【実装案】 生成テストファイルを挿入してから再計測
CANDIDATE_FILE="tests/Generated/MyServiceTests_candidate_001.cs"
TEST_RESULTS_DIR="results/candidate_001"

cp "$CANDIDATE_FILE" "tests/MyServiceTests/"
dotnet test --results-directory "$TEST_RESULTS_DIR" \
            --logger "trx;LogFileName=candidate_001.trx" \
            --collect:"XPlat Code Coverage"
dotnet stryker   # mutation delta を取得

rm "tests/MyServiceTests/$(basename "$CANDIDATE_FILE")"   # ベースラインに復元

このループを候補ごとに繰り返し、カバレッジ Δ・mutation score Δ を証拠台帳に記録します。

利用方法

証拠台帳のスキーマ【実装案】

論文 Section 3.3 段 9 / Section 3.2 の「全 artifact を SQLite に永続化し、CSV で 1 行 = 1 生成試行として出力する」記述を元にした、最小スキーマの実装案です。

-- candidates テーブル: 1行 = 1生成試行
CREATE TABLE candidates (
    id                  TEXT PRIMARY KEY,   -- 安定 ID(論文: stable ID)
    repo_revision       TEXT NOT NULL,      -- git commit hash
    target_method       TEXT NOT NULL,      -- 例: MyLib.MyService::Calculate
    model               TEXT,
    provider            TEXT,
    approach            TEXT,
    attempt             INTEGER NOT NULL,   -- 修復含む試行番号
    outcome             TEXT NOT NULL
                        CHECK (outcome IN (
                            'ValidationFailed',
                            'Validated',
                            'ValidatedLowImpact',
                            'ValidatedEvidencePositive'
                        )),
    baseline_line_rate  REAL,
    candidate_line_rate REAL,
    coverage_delta      REAL,               -- candidate - baseline
    new_lines_covered   INTEGER,
    baseline_mutation_score  REAL,
    candidate_mutation_score REAL,
    killed_mutants_delta     INTEGER,
    failure_category    TEXT,               -- CompileError / RuntimeError / AssertionFail 等
    repair_attempts     INTEGER DEFAULT 0,
    repair_succeeded    INTEGER,
    smell_count         INTEGER,
    created_at          TEXT NOT NULL DEFAULT (datetime('now'))
);

-- generation_steps テーブル: プロンプト・応答の永続化(論文 段6)
CREATE TABLE generation_steps (
    id              TEXT PRIMARY KEY,
    candidate_id    TEXT NOT NULL REFERENCES candidates(id),
    step_type       TEXT NOT NULL,   -- scenario / method_name / assertion / repair 等
    status          TEXT NOT NULL,   -- success / failed / skipped
    prompt          TEXT,
    response        TEXT,
    format          TEXT,
    token_count_in  INTEGER,
    token_count_out INTEGER,
    duration_ms     INTEGER,
    errors          TEXT
);

CSV 出力例(1 行 = 1 生成試行)の実装案です。

id,repo_revision,target_method,model,provider,approach,attempt,outcome,coverage_delta,new_lines_covered,killed_mutants_delta,failure_category,repair_attempts,smell_count,created_at
cand-001,abc1234,MyLib.MyService::Calculate,gpt-4o,openai,zero-shot,1,ValidatedEvidencePositive,0.12,3,2,,0,0,2026-06-11T08:00:00
cand-002,abc1234,MyLib.MyService::Calculate,gpt-4o,openai,zero-shot,2,ValidationFailed,,,,CompileError,0,0,2026-06-11T08:01:00
cand-003,abc1234,MyLib.MyService::Validate,gpt-4o,openai,few-shot,1,ValidatedLowImpact,0.00,0,0,,0,2,2026-06-11T08:02:00

アウトカム分類ロジック【実装案】

論文 Section 3.2 の 4 分類定義を Python で実装した擬似コードです。

# 【実装案】 TestMap アウトカム分類(論文 Section 3.2 の定義を表現)
from dataclasses import dataclass

@dataclass
class CandidateResult:
    compile_ok: bool
    run_ok: bool
    coverage_delta: float        # 新規カバレッジ増分(0.0 以上)
    killed_mutants_delta: int    # 新規 kill できた mutant 数(0 以上)


def classify_outcome(result: CandidateResult) -> str:
    # コンパイル or 実行に失敗 → ValidationFailed
    if not result.compile_ok or not result.run_ok:
        return "ValidationFailed"
    # 少なくとも 1 つの signal を改善 → EvidencePositive
    if result.coverage_delta > 0.0 or result.killed_mutants_delta > 0:
        return "ValidatedEvidencePositive"
    # 通るが observable な改善なし → LowImpact
    return "ValidatedLowImpact"

C# での同等実装案です。

// 【実装案】 TestMap アウトカム分類(C#)
public enum TestOutcome
{
    ValidationFailed,
    Validated,
    ValidatedLowImpact,
    ValidatedEvidencePositive
}

public record CandidateResult(
    bool CompileOk, bool RunOk, double CoverageDelta, int KilledMutantsDelta);

public static class OutcomeClassifier
{
    public static TestOutcome Classify(CandidateResult r)
    {
        if (!r.CompileOk || !r.RunOk)
            return TestOutcome.ValidationFailed;
        bool hasGain = r.CoverageDelta > 0.0 || r.KilledMutantsDelta > 0;
        return hasGain
            ? TestOutcome.ValidatedEvidencePositive
            : TestOutcome.ValidatedLowImpact;
    }
}

運用

証拠台帳を CI に組み込む【実装案】

TestMap の SQLite/CSV アウトカムを PR に見せると、「何件生成して何件が EvidencePositive だったか」が merge 判断の入力になります。

# 【実装案】 .github/workflows/testmap-evidence.yml
name: TestMap Evidence Report
on:
  pull_request:
    paths:
      - "src/**/*.cs"
      - "tests/**/*.cs"
jobs:
  testmap:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run TestMap pipeline
        run: dotnet run --project tools/TestMap -- --repo . --output /tmp/testmap-results.csv
      - name: Summarize outcome distribution
        run: python3 scripts/summarize_outcomes.py /tmp/testmap-results.csv > /tmp/testmap-summary.md
      - name: Post summary to PR
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const summary = fs.readFileSync('/tmp/testmap-summary.md', 'utf8');
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: summary
            });

運用のポイントは 3 点です。

  • ValidatedEvidencePositive 率(以下 EvidencePositive 率)をまず観測指標として追跡する: 生成候補のうち EvidencePositive になった割合を週次/sprint で追う。率の低下は、対象メソッドの複雑度上昇・プロンプト劣化・モデル変更のシグナル(有効性は未実証のため、強い KPI ではなく観測から始める)
  • ValidationFailed・ValidatedLowImpact はアーカイブして捨てない: 失敗カテゴリを CSV に蓄積すると「どのメソッドが何回失敗したか」を遡れ、再試行戦略の根拠になる
  • 棄却候補を再利用する: ValidationFailed の repair 試行ログを context として与え、generation matrix を引き継いだ ablation 比較ができる

mutation / 全証拠保存のコスト管理

ミューテーションテスト(Stryker.NET)は、大規模コードベースで高コストです。

  • 差分実行(incremental mutation): PR の変更行に対応するメソッドのみ mutation を走らせる。Stryker.NET の --since:<committish>(または config の "since": { "target": "..." })で diff-based フィルタを活用
  • サンプリング: 循環的複雑度が高い・既存カバレッジが低い・最近変更されたメソッドを優先的に Target Selection へ渡す
  • CI スケジュール分離: 差分 mutation は PR ごと、フル mutation は夜間 scheduled run に回す
  • 証拠ストレージ: 古い experiment(merge 済み)はアーカイブし、メイン SQLite を軽量に保つ

ベストプラクティス

「誤解 → 反証 → 推奨」で整理します。

誤解1: カバレッジが増えれば良いテスト

反証として、LLM 生成テストはミューテーションスコア(欠陥検出力)の面で人間記述テストに及ばない傾向が、複数の研究で報告されています(具体的な数値は研究・データセットにより幅があり、一次出典の再確認が必要)。assert を緩めてカバレッジだけ稼ぐテストが量産されうるからです。実際に Accenture Japan 有志の事例では、カバレッジ目標を与えた Claude Code が 1,300 件・5 万行超の「自作自演テスト」を生成し、ブランチ破棄に至ったと報告されています(Zenn, acntechjp, 2026/03)。

推奨は、ミューテーションを併用して ValidatedLowImpact を区別することです。EvidencePositive 率を主要な指標にし、カバレッジ % の数値目標は置きません。

誤解2: 全証拠を残せば安心

反証として、著者自身が「開発者が evidence をどう解釈・行動するか(trust calibration・採用判断改善)は未測定」と認めています(論文 Section 5.1 限界 5)。誰も見なければ、構造化ログは無駄なストレージコストになります。ミューテーションは長年「高コストで現場採用が進まない」と批判されており、全証拠保存はそこに運用負荷を上乗せします。

推奨は、アウトカム分布だけを常時可視化し、詳細ログはオンデマンド参照に留めることです。PR コメントには「EvidencePositive 率 + Outcome 分布」のサマリのみ出します。

誤解3: mutation score が高ければ安全

反証として、生成 mutant の一部が equivalent mutant(元プログラムと意味的に等価で理論上検出不能)になり、その等価判定は一般に決定不能です。mutation score は意味的正しさの完全な指標ではありません(論文 Section 5.1 限界 3 で著者自認)。

推奨は、mutation score を絶対視せず、coverage と mutation score の gap を診断シグナルとして使うことです。score 低下時は「equivalent mutant が多い可能性」と「品質低下」を切り分けます。

誤解4: C#/.NET のパターンがそのまま他言語に使える

反証として、TestMap は Roslyn / TRX / Cobertura / Stryker.NET に密結合し、著者が Section 5.1 限界 1 で「言語スコープは C#/.NET 固有」と明記しています。一般化可能性は未検証です。

推奨は、他言語には相当するツールチェーンに置き換えることです(後述の対応表)。4 分類アウトカムと difference signal のコンセプトは移植可能ですが、ツール結合部は別途実装が必要です。

国内事例: 食べログの三層品質保証

食べログ(Tabelog Tech Blog 2025/11)は、AI に直接テストを実行させず「コード生成のみ AI 化」する設計を採りました。生成後に複数の検証項目を機械チェックし、AI が「検証失敗 → 自動修正 → 再検証」を反復する三層構造です。この「AI に実行させない」設計は、flaky・偽陽性の混入リスクを下げる現実的な設計です。TestMap の pre-validation(段 7)の実運用版と見なせます。生成精度や実行工数削減についても、同社ブログが具体値を報告しています(数値は同ブログ参照)。

トラブルシューティング

環境依存でテスト失敗が混入する

  • 症状: Baseline Execution(段 3)や Post-Execution Validation(段 8)で、テスト品質と無関係に落ちる。SDK バージョン・OS 差異・外部サービス依存が原因
  • 対処: 論文 Section 5.1 限界 2 の通り「失敗がテスト品質由来か環境由来か」を切り分ける。Docker でベースライン実行を隔離し、外部サービスは test double で置換。環境起因の失敗は ValidationFailed でなく別カテゴリで記録する

flaky 生成テストが evidence positive に混入する

  • 症状: ValidatedEvidencePositive に分類されたテストが、CI で断続的に落ちる(タイミング・実行順序・共有状態・乱数・外部資源依存。論文 Section 5.1 限界 4)
  • 対処: 同一テストを複数回実行(例: 3〜5 回を目安)して pass/fail パターンを確認し、常に通るものだけ positive とする。flaky と判定した候補は ValidatedLowImpact に降格して記録に残す

equivalent mutant で mutation score が実態より低く出る

  • 症状: mutation score が期待より低いが、テスト内容はしっかりしているように見える
  • 対処: survived mutant を手動サンプリングして equivalent かを判断する。mutation score を絶対指標にせず「前回比の変化」と「coverage との gap」を診断シグナルに使う。TestMap の EvidencePositive 判定は mutation か coverage の一方でも改善すれば positive なので、mutation だけで判断しない

C#/.NET 以外のプロジェクトへ適用する

TestMap の C#/.NET コンポーネント 他言語での相当ツール
Roslyn(静的解析・コンパイル検証) Java: JavaParser、Python: ast / astroid、JS/TS: ts-morph
TRX(テスト結果フォーマット) JUnit XML(Java/Python)、TAP(Node.js)、pytest JSON report
Cobertura XML(カバレッジ) JaCoCo(Java)、coverage.py(Python)、Istanbul/nyc(JS/TS)
Stryker.NET(mutation testing) PIT(Java)、mutmut(Python)、Stryker JS(JavaScript/TypeScript)

4 分類アウトカムと evidence signal(coverage delta / killed mutant)のコンセプトは言語非依存です。上記ツールの出力を SQLite に収集する形で、同等の証拠インフラを構築できます。

まとめ

TestMap は、FM が生成したテストを「通った/通らない」の二値ではなく、候補ごとのライフサイクル(失敗・修復・低インパクト・証拠陽性)とカバレッジ・ミューテーションの証拠として構造化保存するプロトタイプです。評価の軸を「生成成功率」から「証拠として採用できる比率」へ移し、採用テストだけでなく棄却候補とその理由まで残す点に、現場へ転用できる思想があります。

この記事が少しでも参考になった、あるいは改善点などがあれば、ぜひリアクションやコメント、SNSでのシェアをいただけると励みになります!

参考リンク