AI コーディングエージェントは、関数の実装やテストコードを高速に生成します。しかし「テストが通った」ことと「そのテストがバグを検出できる」ことは別の問題です。行カバレッジが 100% でも、assert が緩ければバグはすり抜けます。
この「テストの検出力」を測る手法が、ミューテーションテストです。本番コードに意図的なバグ(ミューテーション)を仕込み、テストがそれを検出して失敗するかどうかを確認します。CircleCI の chunk CLI は、この処理を AI エージェントから実行できる chunk-testing-gaps スキルを配布しています。
本記事では、Cursor 上の AI エージェントに chunk-testing-gaps スキルを渡し、人間が短い指示を数回出すだけで、テスト不足の洗い出しからテスト追加 PR の作成までを進めた実際の記録を、入出力つきで紹介します。AI が生成したテストの「何を足せばいいか」の根拠を、ミューテーション結果という形で渡せることを確認できます。
ミューテーションテストとは何か
ミューテーションテストでは、本番コードに小さな変更(ミューテーション)を機械的に加え、変更後のコードに対して既存のテストスイートを実行します。判定は 2 種類です。
| 結果 | 意味 | 次のアクション |
|---|---|---|
| Killed | 仕込んだバグに対してテストが失敗した | そのバグパターンはカバー済みです |
| Survivor | バグを仕込んでもテストが通ったままだった | テストを足す候補です。本番に同じバグが入っても CI は通ります |
ここで重要なのは Survivor の読み方です。Survivor が出たということは、その変更に相当するバグが本番に混入しても、現在のテストでは気づけないことを意味します。つまり Survivor の一覧は、そのまま「追加すべきテストケースの設計メモ」になります。行カバレッジの数値とは異なり、ミューテーションテストは「どんなバグがテストをすり抜けるか」という具体的なリストを返します。
chunk CLI と chunk-testing-gaps スキルの準備
chunk CLI は、AI コーディングエージェントの inner loop(コードを書いてローカルで検証し、コミットするまでのサイクル)に検証を組み込むためのツールです。テストや Lint をエージェントのライフサイクルに接続し、エージェント自身がコミット前に自己修正できるようにします。
chunk CLI が配布するスキルは 4 種類あります。1 つ目が chunk-review で、チームの過去の PR レビュー傾向に基づいて差分をレビューします。2 つ目が chunk-sidecar で、変更をリモート環境で検証します。3 つ目が本記事の主役である chunk-testing-gaps で、ミューテーションテストを実行します。4 つ目が debug-ci-failures で、CI の失敗調査を支援します。
chunk-testing-gaps によるミューテーションテスト
chunk-testing-gaps スキルでは、4 ステップでミューテーションテストが実行されます。
-
Discovery(探索): コードベースを調査し、ミューテーション候補を特定します。認証・権限チェック、制御フロー(条件式の反転、
if分岐の除去)、入力バリデーション、状態・値、エラーハンドリングといった、テストが手薄になりやすい箇所を優先します - Validation(検証): 候補ごとに git worktree でブランチを切り、ミューテーションを適用します。静的解析とローカルテストで落ちれば「Killed(ローカル)」、通ってしまえばブランチをリモートに push して CI を回し、CI でも通れば「Survivor」と判定します
- Production Cross-Reference(本番トラフィック照合): Survivor のコードパスが本番で実際に通っているかを、Honeycomb や Datadog などの観測性ツールで確認します
- Risk Assessment(リスク評価): 各 Survivor を、バグの深刻度・本番トラフィック・影響範囲・検知しやすさから Critical / High / Medium / Low に格付けし、リスクの高い順に提示します
今回は簡単な検証が目的のため、ステージ 3(本番トラフィック照合)とステージ 4(リスク評価)についてはスキップします。
chunk-testing-gaps は、Validation ステージで Survivor 候補のブランチをリモートに push し、CI パイプラインをトリガーします。実行時にブランチと CI 実行が作成され、CI クレジットを消費する点に注意してください。
インストールとスキルの導入
chunk CLI を Homebrew でインストールします。
brew install CircleCI-Public/circleci/chunk
インストール後、対象リポジトリのルートで chunk init を実行すると、テストコマンドの検出・フックの設定とあわせて、エージェント用スキルが導入されます。スキルを個別に導入・更新する場合は chunk skill install を使用します。
chunk init # テストコマンド検出・フック設定・スキル導入
chunk skill install # スキルの導入・更新
chunk skill list # エージェントごとの導入状況を確認
スキルは、マシン上に存在するエージェントの設定ディレクトリに導入されます。Claude Code は ~/.claude/skills/、Cursor は ~/.cursor/skills/、Codex は ~/.codex/skills/ です。導入後は、エージェントのセッションで /chunk-testing-gaps のようにスラッシュコマンドで起動するか、「mutation test」「find testing gaps」といった自然言語のトリガーで呼び出せます。
FizzBuzz を持つ Swift アプリに導入する
検証には、fizzBuzz 関数を持つ Swift アプリを使用します。本番コードは次の 4 分岐を持ちます。
func fizzBuzz(_ number: Int) -> String {
if number % 15 == 0 { return "FizzBuzz" }
if number % 3 == 0 { return "Fizz" }
if number % 5 == 0 { return "Buzz" }
return String(number)
}
このコードは「15 の倍数」「3 の倍数」「5 の倍数」「いずれでもない数値」の 4 分岐に分かれています。ミューテーションテストでは、この各分岐を壊すバグを仕込み、テストが検出できるかを確認します。
ミューテーション分析をかけた時点のテストは、このようになっていました。
@Test func testFizzBuzz() {
#expect(fizzBuzz(3) == "Fizz")
#expect(fizzBuzz(5) == "Buzz")
#expect(fizzBuzz(15) == "FizzBuzz")
#expect(fizzBuzz(4) == "4")
}
ある程度のケースは網羅されているようにも見えます。ここからミューテーションテストを実施し、抜け落ちているケースがないかを調査しましょう。
Step 1: スキルを起動してミューテーション分析を実行する
ミューテーションテストの実施には、ただスキルを起動するコマンドを渡すだけです。
/chunk-testing-gaps
エージェントは、コードベースを調査して fizzBuzz に対するミューテーションを設計し、git worktree でブランチを切って各変更を適用し、swift test で Killed / Survivor を判定しました。Survivor のブランチは push して CircleCI(macOS 環境)で再確認しています。
生成されたミューテーション一覧
| ID | 仕込んだバグ | 結果 |
|---|---|---|
| MUT-001 | 15 の倍数判定を壊す | Survivor |
| MUT-002 | 5 の倍数判定を壊す | Survivor |
| MUT-003 | デフォルト戻り値を空文字に | Survivor |
| MUT-004 | FizzBuzz 用の除数を 15 から 30 に | Survivor |
| MUT-005 | 3 の倍数判定を反転 | Killed |
| MUT-006 |
"Fizz" を "Buzz" に書き換え |
Killed |
| MUT-007 | Fizz / Buzz の判定順を入れ替え | Survivor |
| MUT-008 | フォールバックを "Fizz" 固定に |
Survivor |
| MUT-009 | 3 の倍数判定の除数を 3 から 6 に | Killed |
| MUT-010 |
"FizzBuzz" を "Fizz" に書き換え |
Survivor |
10 件中 7 件が Survivor です。つまりこれらのケースは現在のテストでは検知できないことがわかります。
Survivor から読み取れる「足すべきテスト」
Survivor を入力値の観点で整理すると、追加すべきテストケースが明確になります。
| Survivor | 検出するために足したい入力 |
|---|---|
| MUT-001 / 007 / 010 |
15 → "FizzBuzz"
|
| MUT-002 |
5 → "Buzz"
|
| MUT-003 / 008 | どの分岐にも当てはまらない数(1 や 4) |
| MUT-004 | FizzBuzz になる別の倍数(30) |
ここまでが chunk-testing-gaps の成果物です。「テストを充実させて」という曖昧な指示ではなく、「この入力に対する assert が欠けている」という具体的なリストが手に入ります。
Step 2: Survivor をもとに本命のテスト PR を作る
ギャップが可視化できたので、本命のテスト PR を作る作業に移ります。
では修正するPRを作成してください。
エージェントは、現在の実装とSurvivor 表と照合して、まだ存在しない入力((1,"1") と (30,"FizzBuzz"))を追加しました。あわせて、PR #1 の 4 つの assert を @Test(arguments:) の表形式に整理しています。
@Test(arguments: [
(1, "1"), // 追加
(3, "Fizz"),
(4, "4"),
(5, "Buzz"),
(15, "FizzBuzz"),
(30, "FizzBuzz"), // 追加
])
func testFizzBuzz(number: Int, expected: String) {
#expect(fizzBuzz(number) == expected)
}
上記のコードでは、@Test(arguments:) で入力と期待値の組を表形式にまとめています。これにより、新しい入力を 1 行追加するだけでテストケースを増やせます。コメントで示したとおり、(1,"1") と (30,"FizzBuzz") が新規に追加した分です。
AI駆動開発におけるミューテーションテスト
ミューテーションテストを利用することで、テストケースの抜け漏れを軽減できます。「テストを充実させて」という指示だけでは、エージェントは正常系のテストを 1 本足して終わることがあります。chunk-testing-gaps は「失敗しなかったバグのリスト」を返すため、次に書くべき assert が一意に定まります。曖昧なゴールが、検証可能なタスクのリストに変換されます。
加えて、chunk-testing-gapsスキルを利用することで、調査手順を固定できます。worktree による隔離、Killed / Survivor の分類、CI での確認といった手順を、毎回プロンプトで説明する必要がありません。スキルをアタッチするだけで、再現可能な手順としてエージェントが実行します。
CIと同等の検証をローカルで実施する
ここまでの作業では、ミューテーション判定とテストの検証を、ローカルの swift test と CircleCI(macOS 環境)で行いました。chunk CLI には、これとは別に「ローカルの変更をリモート環境に同期して検証する」chunk-sidecar スキルがあります。
sidecar は、CircleCI 上に作られる使い捨ての Linux 環境です。ローカルのファイルを sidecar に同期し、リモートで検証します。基本のサイクルは chunk sidecar sync(ローカルの変更を同期)と chunk validate --remote(リモートで検証)の繰り返しです。どのコマンドをリモートで動かすかは、.chunk/config.json のコマンド単位の remote: true ルーティングで制御します。
chunk sidecar sync # ローカルの作業ツリーを sidecar に同期
chunk validate --remote # sidecar 上で検証コマンドを実行
sidecar の利点は、ローカルでは捕まえられない差分を CI に到達する前に検出できる点です。たとえば macOS では通るが Linux では落ちるテストのような、環境差に起因する失敗を inner loop で拾えます。エージェントは sidecar 上のファイルを直接編集せず、ローカルで修正して再同期するため、「どこを触ったか分からない」という生成 AI の課題も抑えられます。
sidecar は2026/05時点でプレビュー機能として提供中です。無料プランを含むCircleCI ユーザーが利用可能です。
対応プラットフォームや最新の提供状況は chunk CLI の README を参照してください。
まとめ
ミューテーションテストは、行カバレッジの数値では測れない「テストの検出力」を、意図的なバグの検出可否という形で可視化します。chunk-testing-gaps スキルを使うと、AI エージェントがこの分析を実行し、Survivor の一覧として「どの入力に対する assert が欠けているか」を返します。これにより、AI にテストを書かせる際の「何を足せばいいか」の根拠を、具体的なリストとして渡せます。
本記事では FizzBuzz という小さな題材で示しましたが、chunk-testing-gaps は本来、本番トラフィックとの照合とリスク格付けまでを含む 4 ステージの処理です。観測性ツールに接続された実サービスに対して実行すれば、検出力の低いコードパスを「本番で実際に通っており、かつリスクが高い順」に並べた結果が得られます。
inner loop の検証をリモートに寄せる chunk-sidecar とあわせて、AI が生成するコードの量と速度に、検証の仕組みを追いつかせる手段が揃いつつあります。
参考
- リポジトリ: https://github.com/hidetaka-cci/first-swift-app
- 機能追加 PR(分析と並行して main へ): https://github.com/hidetaka-cci/first-swift-app/pull/1
- テスト整理 PR: https://github.com/hidetaka-cci/first-swift-app/pull/9
- ミューテーション分析のベースコミット:
7fba1de - chunk CLI: https://github.com/CircleCI-Public/chunk-cli
