本文へスキップ
hdknr blog

ミューテーションテスト(mutation harness)

コードをわざと 1 箇所壊してテストが落ちるかを確かめ、テストの検出力を測る技法。専用ツールなしの 30 行のスクリプトでも価値が出る

概要

わざとコードを壊して、テストが落ちるかを確かめる技法(DeMillo, Lipton, Sayward, 1978)。コードを 1 箇所だけ変異させた 変異体(mutant) を作ってテストを回し、落ちれば「殺した(killed)」、通れば「生き残った(survived)」= テストにその変更を検出する力がない、と読む。

カバレッジが測るのは「実行されたか」、ミューテーションが測るのは「検査されたか」。assert が 1 つもないテストでも行カバレッジ 100% は達成できるので、その差を埋める実用的な方法が変異を当てることになる。

詳細

最小の実装

mutmut / cosmic-ray / Stryker / PIT のような専用ツールは必須ではない。手で狙いを定めた数個の変異と 30 行のスクリプトのほうが、自動生成された数千の mutant を眺めるより速く核心に届くことが多い。

  1. 対象ファイルの元の内容を保存する
  2. (説明, 置換前, 置換後) の表から変異を 1 つ当て、テストを回して RED / GREEN を記録する
  3. 各変異のあとで必ず元に戻す(finally や trap ... EXIT)

置換前のアンカーには行頭のインデントまで含める。含めないと削除跡のインデントが壊れて IndentationError になり、テストが 1 件も走らないのに FAILED 行も出ないので GREEN と表示される。pytest の終了コードが 0/1 以外なら BROKEN として弾く数行が要る。測る道具も測られなければならない。

結果の読み方

結果意味すること
REDテストが変異を検出したその検査には検出力がある。件数ではなく、どのテストが落ちたかを記録する
GREEN検出できなかった本当の穴/等価変異/到達不能な死んだコード、の 3 つに切り分ける
BROKENテストが走っていない変異が汚い。GREEN と混ぜない

GREEN の 3 分類のうち、一番よく出て一番価値があるのは死んだコード。「if False: に書き換えても全テスト緑」のガードは誰も守っていない。効かない防御は次に読む人を誤解させるので、足すのではなく削る。

ミューテーションスコア = 殺した数 ÷ 変異体の総数。ベースライン(変異なし)が緑であることとセットで読む。ベースラインが赤のスコアは 77% のような「それらしい中途半端な数字」になり、バグを固定したテストが強いテストに見える。

踏みやすい罠

同じ問いを共有する技法

どれも「その観測は、主張が偽のときに違う結果を出すか」という一つの問いの現れ方である。

主張の形効く検査
「このガード/検査は効いている」ミューテーション
「〜は起きない」「呼ばれない」「0 件」ポジティブコントロール(検出できるはずの対象で先に検出を確認)
「この入力範囲で正しい」property-based testing
期待値が書けないメタモルフィックテスティング
「落ちても復旧する」フォールトインジェクション/カオスエンジニアリング
「既存の挙動を変えていない」固定 SHA との比較 + 特性テスト
「性能は劣化していない」ベースライン付きベンチ(固定シード・相対値)

TDD で「まず RED を見る」のも、実装が無い状態という 1 個の変異を当てているのと同じ。レビューで一番効く質問「この行、消したら何が壊れますか?」も同じ発想である。

AI エージェントに検査させるとき

「適切なテストをして」では効かない。エージェントは妥当に見える検査を選んで緑を報告でき、その報告は検査が効いていなくても同じ形で出る。渡すべきは手法名ではなく次の 2 つ。

運用上の要点:

ミューテーションが測っていないもの

変異体は実装から機械的に作られるので、ミューテーションテストも仕様を見ていない。測っているのは「テストが実装を今の形にピン留めしているか」であって、「実装が仕様に合っているか」ではない。実装が正しいという前提の上でテストの強度を測る道具である(テストの導出元)。

関連ページ

ソース記事