用語集 · テストの基礎
ミューテーションテスト(Mutation Testing)
英語: Mutation testing
別名: ミューテーション解析, mutation analysis, ミューテーションスコア, mutation score
定義
コードに小さな意図的バグ(ミュータント)を埋め込んでテストを再実行し、テストスイートが本当に欠陥を検出できるかを測る手法です。
ミューテーションテストは「テストをテストする」手法です。ツールがコードのコピーを大量に作り、それぞれに小さな変更を 1 つだけ加えます。たとえば >= を > に、+ を - に変えたり、メソッド本体を return null に置き換えたりします。変更を加えた各コピーをミュータントと呼びます。テストスイートはすべてのミュータントに対して実行されます。少なくとも 1 つのテストが失敗すれば、そのミュータントは**キル(killed)されたことになります。すべてのテストが成功したままなら、ミュータントは生存(survived)**したことになり、テストがそのバグに気づけないことを意味します。
ミューテーションスコア = キルされたミュータント数 / ミュータント総数(等価ミュータントを除く)
例
boolean isAdult(int age) {
return age >= 18;
}
テストは isAdult(30) が true、isAdult(10) が false の 2 つです。行カバレッジも分岐カバレッジも 100% です。
ツールが age > 18 というミュータントを作ります。2 つのテストはどちらも成功したままなので、このミュータントは生存します。これにより境界値テストの欠落が明らかになります。誰も isAdult(18) を確認していなかったのです。このテストを追加すればミュータントはキルされます。カバレッジではこれはわかりませんが、ミューテーションテストならわかります。
ツール
- Java: PIT(pitest)。Maven・Gradle プラグインがあり、JUnit 5 にも対応しています。
- JavaScript/TypeScript: StrykerJS。.NET 版と Scala 版もあります。
- Python: mutmut と Cosmic Ray。
実務上の限界
- コスト。 ミュータント 1 つごとにテストを 1 回実行します。ツールは、変更された行をカバーするテストだけを実行したり、変更されたコードに対して増分実行したりしてこれを軽減しますが、それでも通常のテスト実行よりはるかに時間がかかります。多くのチームは、コードベース全体に対してコミットごとに実行するのではなく、夜間バッチやプルリクエストの変更ファイルに限定して実行しています。
- 等価ミュータント。 振る舞いをまったく変えない変更もあります。たとえば、
iがn未満から常に 1 ずつ増えるループでi < nをi != nに変えた場合です。どんなテストでもキルできないため、手作業でレビューして除外する必要があります。 - ユニットレベルで最も効果的。 ミューテーションテストは高速で決定的なユニットテスト向けに設計されています。遅い UI テストに対して実行するのは、現実的でないことがほとんどです。
完璧なスコアを追い求めるのではなく、生存したミュータントを、足りないアサーション(特にビジネスロジック部分)の TODO リストとして活用しましょう。