0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cursor × chunk-testing-gaps スキルで Swift アプリにミューテーションテストを実施する

0
Posted at

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 ステップでミューテーションテストが実行されます。

  1. Discovery(探索): コードベースを調査し、ミューテーション候補を特定します。認証・権限チェック、制御フロー(条件式の反転、if 分岐の除去)、入力バリデーション、状態・値、エラーハンドリングといった、テストが手薄になりやすい箇所を優先します
  2. Validation(検証): 候補ごとに git worktree でブランチを切り、ミューテーションを適用します。静的解析とローカルテストで落ちれば「Killed(ローカル)」、通ってしまえばブランチをリモートに push して CI を回し、CI でも通れば「Survivor」と判定します
  3. Production Cross-Reference(本番トラフィック照合): Survivor のコードパスが本番で実際に通っているかを、Honeycomb や Datadog などの観測性ツールで確認します
  4. 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 環境)で再確認しています。

スクリーンショット: ミューテーション分析の実行結果。Killed / Survivor の判定一覧が出力された画面

生成されたミューテーション一覧

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 どの分岐にも当てはまらない数(14
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 が生成するコードの量と速度に、検証の仕組みを追いつかせる手段が揃いつつあります。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?