はじめに
ミューテーションテストでは、プログラムに小さな変更(ミュータント)を意図的に加え、テストがその変更を検知できるかを確認します。これらの変更は、開発者が実際によく起こしがちなバグを模したものです。本記事では、代表的なミューテーションの種類を Python のコード例とともにご紹介します。
また、ミューテーションテストそのものの解説は以下の記事にて行ってますのでよろしければ合わせてご覧ください。
1.算術演算子の変更(Arithmetic Operator Replacement)
一つの算術演算子(+
, -
, *
, /
など)を別の算術演算子に置き換えるミューテーションです。例えば、加算(+
)を減算(-
)に変えると計算結果が変わり、テストが誤った計算を検出できるかを確認します。このような変更は、四則演算のミス(例えば足し算と引き算の取り違え)に対するテストの強さを評価します。
Pythonでの例:
# 元のコード
result = x + y
# ミュートされたコード(算術演算子を変更)
result = x - y # 「+」を「-」に置き換え
2.比較演算子の変更(Relational/Comparison Operator Replacement)
比較演算子(>
, <
, ==
, !=
など)を別の比較演算子に置き換えるミューテーションです。例えば、>
を >=
に変更して条件の境界をずらすことで、テストが境界条件の違いを検出できるか調べます。これにより、条件判定における「>=
と >
」や「==
と !=
」といった誤りに対するテストの網羅性を評価できます。
Pythonでの例:
# 元のコード
if x > 0:
do_something()
# ミュートされたコード(比較演算子を変更)
if x >= 0:
do_something() # '>' を '>=' に変更
3.条件文の反転(Negating Conditionals)
条件文を反転させるミューテーションです。これは条件式の真偽を逆にする変更で、if 文の条件を否定した形に置き換えます。例えば、if x > 0:
を if x <= 0:
に変えることで、本来とは逆の条件で処理が実行されるようになります。これにより、テストが条件の真偽の逆転(論理を反転させたバグ)を検知できるかを確認します。
Pythonでの例:
# 元のコード
if x > 0:
do_positive()
else:
do_non_positive()
# ミュートされたコード(条件を反転)
if x <= 0:
do_positive()
else:
do_non_positive()
4.リテラルの変更(Literal/Constant Replacement)
コード中の定数(リテラル値)を別の値に置き換えるミューテーションです。例えば、数値リテラルの1
を0
に変更したり、真偽値のTrue
をFalse
に変更したりするケースが該当します。このような変更により、定数の値が誤っていてもテストが検出できるか(例えば、境界値や初期値のバグに気づくか)を評価します。
Pythonでの例:
# 元のコード
threshold = 1
if value > threshold:
print("High")
# ミュートされたコード(リテラルを変更)
threshold = 0 # 1 を 0 に変更
if value > threshold:
print("High")
5.変数の置換(Variable Replacement)
使用されている変数を、同じスコープ内で互換性のある別の変数に差し替えるミューテーションです。例えば、変数x
をy
に置き換えると、計算や条件判断に別の値を使用することになります。これによって、誤った変数を参照した場合でもテストが失敗するか(=テストが適切な変数をチェックしているか)を確認します。
Pythonでの例:
x = 10
y = 5
# 元のコード
result = x * 2
# ミュートされたコード(変数を置換)
result = y * 2 # x を y に置き換え
6.関数呼び出しの変更(Function Call Replacement)
呼び出している関数を別の関数に変更するミューテーションです。例えば、リストの長さを取得するlen(lst)
という呼び出しを、リスト内の要素の合計を計算するsum(lst)
に置き換えるといった具合です。全く異なる関数を呼び出すことで処理結果が変化し、テストが誤った関数呼び出しの挙動を検知できるかを確認します。これは、APIの誤使用や関数の取り違えといったバグに対するテストの有効性を評価します。
Pythonでの例:
import math
# 元のコード
value = len([1, 2, 3, 4]) # 結果は4
# ミュートされたコード(関数呼び出しを変更)
value = sum([1, 2, 3, 4]) # 結果は10 (lenをsumに変更)
7.ループの変更(Loop Boundary/Behavior Change)
ループ処理に関する変更を行うミューテーションです。典型的にはループの繰り返し回数や境界を変更します。例えば、for i in range(10):
というループを for i in range(5):
に変更すると、ループの回数が減少します。これは内部的にはループ条件(例えば< 10
を< 5
にする等)の変更であり、オフ・バイ・ワンエラー(ループの回数が1回多い・少ないバグ)などを検出できるかテストします。また、必要に応じてループ内のbreak
やcontinue
の動作を変えるミューテーション(例えばbreak
をcontinue
に置き換える)もあります。
Pythonでの例:
# 元のコード
for i in range(10):
process(i)
# ミュートされたコード(ループの範囲を変更)
for i in range(5):
process(i) # 10 を 5 に変更(ループ回数を短縮)
8.例外処理の変更(Exception Handling Modification)
例外処理の部分を削除・変更するミューテーションです。例えば、try-except
ブロック内のexcept
節を削除してしまう(例外を捕捉しない)変更が挙げられます。この場合、もともと処理されていた例外がそのまま上位に伝播するようになるため、テストが例外未処理による異常動作を検知できるか確認します。Pythonでは、except
節の中身を空にして例外を無視してしまう(握りつぶす)ような変更も考えられます。これらは、例外ハンドリングの漏れや誤りに対するテストの網羅性を評価するものです。
Pythonでの例:
# 元のコード
try:
risky_operation()
except ValueError:
handle_error()
# ミュートされたコード(例外処理を削除)
risky_operation() # try-exceptブロックを削除(例外が捕捉されなくなる)
9.リターン値の変更(Return Value Mutation)
関数やメソッドが返す値を改変するミューテーションです。典型的なのはブーリアン(真偽値)の反転で、True
を返していた箇所を False
に変更する、といったものです。同様に、数値を返す場合に0
を1
に変えてみる、特定のオブジェクトをNone
(null
)に差し替える、などのパターンがあります。これによって、テストが誤った戻り値に反応して失敗するか(例えば、関数の結果を正しく検証しているか)を確認します。
Pythonでの例:
# 元のコード
def is_valid(data):
return True # 本来は True を返す
# ミュートされたコード(リターン値を変更)
def is_valid(data):
return False # True を False に変更
10.論理演算子の変更(Logical Operator Replacement)
論理演算子を別のものに置き換えるミューテーションです。Pythonの場合、論理積のand
と論理和のor
を入れ替えるのが代表的な例です。例えば、条件式内でcond_a and cond_b
としていた部分をcond_a or cond_b
に変えると、両方の条件が真の場合だけ真となっていた判定が、どちらか一方が真であれば真となるように動作が変わります。この変更により、複合条件に対するテスト(論理演算の組み合わせに対するテスト)が十分かを評価できます。
Pythonでの例:
# 元のコード
if cond_a and cond_b:
do_action()
# ミュートされたコード(論理演算子を変更)
if cond_a or cond_b:
do_action() # and を or に変更
さいごに
ここで紹介したミューテーション(小さな変更)は、実際に起こりうるバグの典型例を模擬しています。ミューテーションテストでは、これらの変更をコードに一つずつ加え、そのたびにテストを実行して以下を評価します。
- 変更を検知してテストが失敗する → ミュータントを殺した (killed)
- 変更を見逃してテストが通る → ミュータントが生き残った (survived)
生き残ったミュータントが多いほど、テストが不十分である可能性が高いと判断できます。逆にほとんどのミュータントが殺されるなら、それだけテストが堅牢に作り込まれているということです。
参考文献
- Patrick Viafore 著. 「ロバストPython」, 鈴木 駿 監訳、長尾 高弘 訳, O'REILY Japan, 2023, 384p.
- Michal Jaworski, Tarek Ziade 著,「エキスパートPythonプログラミング 改訂4版」,稲田 直哉 訳, 渋川 よしき 訳, 清水川 貴之 訳, 福田 隼也 訳, KADOKAWA出版, 2023, 584p.
- James White, "Mutation Testing - Who will test the tests themselves?", 25 Sep 2017,(アクセス日 2025-02-25)
- Pitest, Quickstart, ,(アクセス日 2025-02-25)
- Faculty of Computer Science Universitas Indonesia, "Software Quality Assurance-Exercise 4: Mutation Testing", (アクセス日 2025-02-25)
- K. Diallo, Z. Chen, W. E. Wong and S. -Y. Lee, "An Analysis and Comparison of Mutation Testing Tools for Python," 2024 11th International Conference on Dependable Systems and Their Applications (DSA), Taicang, Suzhou, China, 2024, pp. 161-169