ミューテーションテストとは - テストの品質を数値で測定する手法

ミューテーションテストはソースコードに意図的な変異を注入し、テストが検出できるかで網羅性を評価する手法。Stryker/PIT の導入手順とスコアの読み方を解説

テスト品質

ミューテーションテストとは

ミューテーションテスト (Mutation Testing) は、ソースコードに意図的な変異 (ミュータント) を加え、既存のテストがその変異を検出できるかを検証する手法である。変異テストとも呼ばれる。テストの品質を測る「テストのテスト」と言える。

コードカバレッジが 100% でも、テストが実際にバグを検出できるとは限らない。ミューテーションテストは、テストの検出力 (バグを見つける能力) を定量的に評価する。

仕組み

元のコードに小さな変異 (ミュータント) を加え、既存のテストスイートがその変異を検出できるかを確認する。テストが失敗すればミュータントは「殺された」(テストが有効)、テストが通過すればミュータントは「生存」(テストが不足) と判定される。

1. 元のコード: if (age >= 18) return "adult";
2. ミュータント生成: if (age > 18) return "adult";  (>= を > に変異)
3. テスト実行: age=18 のテストケースがあれば失敗 → ミュータント「殺された」(検出成功)
              age=18 のテストがなければ通過 → ミュータント「生存」(テスト不足)

ミューテーションスコア

スコアは「殺された数 / 全ミュータント数」ではない。生成された変異のうちコンパイルエラーや実行時エラーになったもの、設定で除外したものは分母から外れ、タイムアウトは検出側に数える (変異が無限ループを作り、テストが異常として捉えた状態のため)。Stryker の定義では次のようになる。

検出   = 殺された + タイムアウト
未検出 = 生存 + テストが通っていない (no coverage)
有効   = 検出 + 未検出   ← コンパイルエラー・実行時エラー・無視された変異は含まない

ミューテーションスコア = 検出 / 有効 × 100%

テストが 1 度も通らないコードの変異 (no coverage) は未検出として分母に残るため、テストの無い領域を放置したままではスコアは上がらない。これを分母から外した「カバー済みコードに基づくスコア」(検出 / (検出 + 生存)) も併記されるので、どちらを見ているかを取り違えないこと。

しきい値も道具側に既定値がある。Stryker の既定は high 80 / low 60 で、レポートの色分けは次の 3 段になる。

スコアStryker の既定の扱い
80% 以上良好
60% 以上 80% 未満警告
60% 未満危険

ビルドを失敗させる break は既定で未設定 (null) のため、スコアが下がっても CI は通る。回帰を止めたい場合は break を明示する。

代表的な変異演算子

変異は「文法的に正しいまま、意味を 1 箇所だけ変える」ものが選ばれる。壊れたコードを作っても全テストが落ちるだけで情報にならないためである。

演算子元のコード変異後
条件境界>=>
否定if (x)if (!x)
算術演算子a + ba - b
戻り値return truereturn false
削除validate(input)(行を削除)

コードカバレッジとの違い

2 つの指標は代替関係ではない。カバレッジが低い箇所はミューテーションテストにかけても no coverage が並ぶだけなので、先にカバレッジで実行されていない領域を埋め、そのうえで検出力を測る順番になる。

指標コードカバレッジミューテーションスコア
測定対象コードの実行率テストの検出力
弱点実行しただけで検証していないコードも 100%計算コストが高い
add(1, 2) を呼ぶだけで行カバレッジ 100%add の結果を assertEquals で検証しないとミュータント生存

ツール

変異の注入は構文木やバイトコードへの操作になるため、実装は言語処理系ごとに分かれる。

言語ツール
JavaScript/TypeScriptStryker
JavaPIT (pitest)
Pythonmutmut
Rustcargo-mutants

導入手順の例 (Stryker)

JavaScript/TypeScript プロジェクトなら Stryker で試すのが手早い。2026 年 8 月時点の公式手順は npm init stryker@latest で、Stryker の導入と初期化ウィザードの実行がまとめて走り、テストランナーに合わせた設定ファイルが生成される。あとは npx stryker run で、ミュータントごとの生死と全体のスコアがレポートされる。Java の PIT も ant・maven・gradle から呼べるため、ビルド設定の数行で動き始める。まずは小さなモジュール 1 つに対して実行し、生存ミュータントのレポートを眺めて「テストが素通しにしている箇所」を体感するのが導入の第一歩になる。

実務での活用

全コードにミューテーションテストを適用すると実行時間が膨大になるため、以下のように対象を絞る。

  • ビジネスロジックの核心部分 (金額計算、権限チェック) に限定する
  • 新規コードの PR レビュー時に CI で実行する
  • コードカバレッジが高いのにバグが出る領域に適用する

関連書籍も参考になる。

よくある質問

スコアは何% を目指せばよいか。
一律の正解はないが、まず対象を絞ったうえで 80〜90% 台を目安にし、生存ミュータントを個別に確認して「殺す価値があるか」を判断するのが現実的だ。等価ミュータント (動作が変わらない変異) は殺せないため、100% を追うことは目的化しない。
コードカバレッジが高ければ不要か。
カバレッジは「実行したか」しか測れず、アサーションの質は測れない。カバレッジが高いのに本番バグが漏れる場合こそ、ミューテーションテストで検出力の穴を特定する価値がある。

この記事は役に立ちましたか?

関連用語

関連する記事