ミューテーション テスト(またはミューテーション分析、プログラム ミューテーション) は、新しいソフトウェア テストを設計し、既存のソフトウェア テストの品質を評価するために使用されます。ミューテーション テストでは、テスト対象のプログラムに小さな変更を加えます。[ 1 ]変更された各バージョンはミュータントと呼ばれます。テストは、テストが失敗したときにミュータントを検出し、拒否します。失敗は、テストがミュータントの動作が元のコードの動作と異なることを正しく識別したことを示します。拒否は、ミュータントの排除と呼ばれます。テスト スイートの価値は、排除したミュータントの割合によって測定されます。テスト スイートは、追加のミュータントを排除するように設計された新しいテストを追加することによって改善できます。
変異体の作成は、典型的なプログラミングエラー(誤った演算子や変数名の使用など)を模倣するか、有用なテストの作成を強制する(各式をゼロで割るなど)明確に定義された変異演算子を使用して行われます。
ミューテーションテストはホワイトボックステストの一種です。[ 2 ] [ 3 ]その目的は、プログラムのテストに使用されるテストデータの弱点を見つけ、実行中にほとんどまたはまったくアクセスされないテスト対象プログラムのコードセクションを発見することにより、テスターが効果的な回帰テストを開発するのを支援することです。
この記事の大部分は、プログラムの変更である「プログラム変異」についてです。変異解析のより一般的な定義は、構文構造に基づいて定義された明確なルールを使用して、ソフトウェア成果物に体系的な変更を加えることです。[ 4 ]変異解析は他の問題にも適用されていますが、通常はテストに適用されます。したがって、変異テストは、変異解析を使用して新しいソフトウェアテストを設計したり、既存のソフトウェアテストを評価したりすることと定義されます。[ 4 ]このように、変異解析とテストは、設計モデル、仕様、データベース、テスト、XML、およびその他の種類のソフトウェア成果物に適用できますが、プログラム変異が最も一般的です。[ 5 ]
特定のソフトウェアシステムの実装の正しさを検証するためのテストを作成することはできますが、テストの作成自体が、テストが正しく、実装に関連する要件を十分に網羅しているかどうかという疑問を依然として提起します。[ 6 ] (この技術的な問題自体が、「誰が監視者を監視するのか?」という、より深い哲学的問題の一例です。)
ミューテーションテストの基本的な考え方は、テスト対象のプログラムが意図どおりに動作しているという前提に基づいています。つまり、ミュータントが導入されて機能が変化した場合、それはバグが導入されたことを意味し、テストはそのバグを検出するはずです。このようにして、テストはテストされます。テストスイートによってミュータントが検出されない場合、通常はテストスイートがミュータントによって表される欠陥を特定できないことを示していますが、ミューテーションによって欠陥が全く発生していないことを示している場合もあります。つまり、ミューテーションは有効な変更であり、望ましい結果を生み出すか、機能に影響を与えないかのどちらかです。ミュータントが有効である(一般的な)方法の1つは、変更されたコードが「デッドコード」であり、決して実行されないことです。
大規模なミューテーションテストを実施するには、通常、多数のミュータントを導入する必要があり、その結果、プログラムの膨大な数のコピーがコンパイルおよび実行されます。ミューテーションテストのコストが高いという問題が、ソフトウェアテスト手法としての実用性を低下させていました。しかし、オブジェクト指向プログラミング言語や単体テストフレームワークの利用が増加したことで、アプリケーションの個々の部分をテストするミューテーションテストツールが開発されるようになりました。
変異検査の目的は複数ある。
突然変異検査は、もともと1971年に学生だったリチャード・リプトンによって提案され[ 8 ]、最初に開発され、デミロ、リプトン、セイワードによって発表されました[ 1 ] 。突然変異検査ツールの最初の実装は、1980年にイェール大学で博士論文(突然変異解析と題されたもの)の一部としてティモシー・バッドによって行われました[ 9 ]。
近年、膨大な計算能力が利用可能になったことで、コンピュータ科学コミュニティ内で突然変異解析が再び注目を集めており、オブジェクト指向プログラミング言語や、 XML、SMV、有限状態機械などの非手続き型言語に突然変異テストを適用する方法を定義する研究が行われています。
2004年、Certess Inc.(現在はSynopsysの一部)という会社が、これらの原則の多くをハードウェア検証の領域に拡張しました。ミューテーション解析は出力の差異を検出することのみを目的としていますが、Certessはテストベンチ内のチェッカーが実際に差異を検出できるかどうかを検証することで、この拡張を実現しました。この拡張により、検証の3つの段階、すなわちアクティベーション、伝播、検出のすべてが評価されます。彼らはこれを機能的適格性評価と呼びました。
ファジングは、ミューテーションテストの特殊なケースと考えることができます。ファジングでは、通信インターフェース内で交換されるメッセージやデータ(ソフトウェアインスタンス内およびインスタンス間)をミューテーションして、データの処理における障害や差異を検出します。Codenomicon [ 10 ] (2001) とMu Dynamics (2005)は、ファジングの概念を、プロトコル実装を徹底的にテストするためのモニターを備えた、完全なステートフルなミューテーションテストプラットフォームへと発展させました。
ミューテーションテストは、2 つの仮説に基づいています。1 つ目は、有能なプログラマー仮説です。この仮説は、有能なプログラマーはほぼ正しいプログラムを書くと述べています。[ 1 ] 「ほぼ」とは、構文ではなく動作に基づくことを意図しています。2 つ目の仮説は、カップリング効果と呼ばれます。カップリング効果は、単純な欠陥が連鎖したり結合したりして、他の新たな欠陥を形成する可能性があると主張しています。 [ 11 ] [ 12 ]
微妙で重要な欠陥も高次変異体によって明らかになり、それがさらに結合効果を裏付けている。[ 13 ] [ 14 ] [ 7 ] [ 15 ] [ 16 ]高次変異体は、複数の変異を持つ変異体を作成することによって可能になる。
ミューテーションテストは、一連のミューテーション演算子を選択し、ソースコードの該当箇所ごとにそれらを1つずつソースプログラムに適用することによって行われます。プログラムに1つのミューテーション演算子を適用した結果は「ミュータント」と呼ばれます。テストスイートがその変更を検出できた場合(つまり、いずれかのテストが失敗した場合)、そのミュータントは「キルされた」と言われます。
例えば、次のC++コード断片を考えてみましょう。
if ( a && b ) { c = 1 ; } else { c = 0 ; }条件変異演算子は、&&をに置き換え||、以下の変異体を生成します。
if ( a || b ) { c = 1 ; } else { c = 0 ; }さて、この変異体を死滅させるための試験では、以下の3つの条件を満たす必要があります。
a = 1、b = 0このようなことが起こります。これらの条件はまとめてRIPモデルと呼ばれています。[ 8 ]
弱いミューテーションテスト(または弱いミューテーションカバレッジ)では、最初の条件と2番目の条件のみが満たされればよい。強いミューテーションテストでは、3つの条件すべてが満たされればよい。強いミューテーションの方が強力で、テストスイートが実際に問題を検出できることを保証する。弱いミューテーションはコードカバレッジ手法と密接に関連している。弱いミューテーションテストでは、テストスイートが強いミューテーションテストを満たすために必要な計算能力ははるかに少ない。
しかし、この変異体を死滅させるテストケースが見つからない場合もある。その結果得られるプログラムは、元のプログラムと動作的に同等である。このような変異体は、等価変異体と呼ばれる。
同等の変異体の検出は、変異テストの実用化における最大の障害の 1 つです。変異体が同等かどうかをチェックするために必要な労力は、小規模なプログラムであっても非常に高くなる可能性があります。[ 17 ] 2014 年に行われた、同等の変異体問題を克服するための幅広いアプローチに関する体系的な文献レビュー[ 18 ]では、17 の関連技術 (22 の記事) と、検出 (DEM)、提案 (SEM)、同等の変異体生成の回避 (AEMG) の 3 つの技術カテゴリが特定されました。実験では、一般的に高次変異、特に JudyDiffOp 戦略が、同等の変異体問題に対する有望なアプローチであることが示されました。
同等のミュータントに加えて、サブサムされたミュータントというものがあります。これは、別のミュータントと同じソースコードの場所に存在するミュータントで、他のミュータントに「サブサムされている」と言われます。サブサムされたミュータントは、ミューテーションテストツールからは見えず、カバレッジメトリクスにも寄与しません。たとえば、コード行を同じように変更するミュータントAとBがあるとします。ミュータントAを最初にテストすると、コードが正しく動作しないという結果が出ます。次にミュータントBをテストすると、結果はミュータントAと同じです。この場合、ミュータントBのテスト結果がミュータントAのテスト結果と同じであるため、ミュータントBはミュータントAにサブサムされているとみなされます。したがって、結果がミュータントAと同じになるため、ミュータントBをテストする必要はありません。
プログラムの構文を変更するには、ミューテーション演算子がソースコードの一部を置き換えるガイドラインとして機能します。ミューテーションはこれらの演算子に依存するため、研究者たちはJavaなどのさまざまなプログラミング言語に対応するためにミューテーション演算子のコレクションを作成しました。これらのミューテーション演算子の有効性は、ミューテーションテストにおいて極めて重要な役割を果たします。[ 19 ]
研究者たちは数多くの変異演算子を研究してきました。以下に、命令型言語における変異演算子の例をいくつか示します。
goto fail;[ 20 ]+、*、-など。/>例えば>=、、==<=これらの変異演算子は、従来型の変異演算子とも呼ばれます。オブジェクト指向言語[ 22 ] 、並行構造[ 23 ] 、コンテナなどの複雑なオブジェクト[ 24 ]などにも変異演算子があります。
コンテナの演算子は、クラスレベルのミューテーション演算子と呼ばれます。クラスレベルの演算子は、検査対象の式を追加、削除、または変更することによって、プログラムの構造を変更します。変更の各カテゴリごとに特定の演算子が確立されています。[ 19 ]例えば、muJavaツールは、アクセス修飾子の変更、型キャスト演算子の挿入、型キャスト演算子の削除など、さまざまなクラスレベルのミューテーション演算子を提供します。プログラムのセキュリティ脆弱性テストを実行するために、ミューテーション演算子も開発されています。[ 25 ]
クラスレベルの演算子に加えて、MuJavaには、従来型演算子と呼ばれるメソッドレベルの変更演算子も含まれています。これらの従来型演算子は、手続き型言語でよく見られる機能に基づいて設計されています。これらは、プリミティブ演算子を追加、置換、または削除することによってステートメントに変更を加えます。これらの演算子は、算術演算子、関係演算子、条件演算子、シフト演算子、論理演算子、および代入演算子の6つのカテゴリに分類されます。[ 19 ]
変異検査には3種類あります。
ステートメントの変異とは、コードブロックを意図的に変更し、特定のステートメントを削除またはコピーするプロセスです。さらに、コードブロック内のステートメントの順序を変更して、さまざまなシーケンスを生成することもできます。この手法は、コードの潜在的な弱点やエラーを特定するのに役立つため、ソフトウェアテストにおいて非常に重要です。開発者は、コードに意図的に変更を加え、その動作を観察することで、通常のテストでは見落とされる可能性のある隠れたバグや欠陥を発見できます。[ 26 ]ステートメントの変異は、コードの堅牢性と回復力に関する洞察を提供する診断ツールのようなもので、プログラマがソフトウェアの全体的な品質と信頼性を向上させるのに役立ちます。
例えば、以下のコードスニペットでは、「else」セクション全体が削除されています。
function checkCredentials ( username , password ) { if ( username === "admin" && password === "password" ) { return true ; } }値の変更とは、コード内のパラメータ値または定数値に変更が加えられることを指します。通常、これは値を1加算または1減算して調整することを意味しますが、より大幅な変更を加える場合もあります。値の変更時に行われる具体的な変更には、主に次の2つのシナリオがあります。
まず、小さな値から大きな値への変換があります。これは、コード内の小さな値を大きな値に置き換えることを意味します。この変更の目的は、コードがより大きな入力に遭遇したときにどのように反応するかを評価することです。これにより、コードがエラーや予期せぬ問題に遭遇することなく、これらの大きな値を正確かつ効率的に処理できることを確認できます。
一方、2つ目のシナリオでは、より大きな値をより小さな値に変更します。この場合、コード内のより大きな値をより小さな値に置き換えます。このテストの目的は、コードが小さな入力値をどのように処理するかを評価することです。小さな値でもコードが正しく動作することを保証することは、そのような入力データを扱う際に予期せぬ問題やエラーを防ぐために不可欠です。
例えば:
// 元のコードfunction multiplyByTwo ( value ) { return value * 2 ; }// 値の変更: 小さい値を大きい値に変更する関数multiplyByTwoMutation1 ( value ) { return value * 10 ; }// 値の変更: 高い値を低い値に変更する関数multiplyByTwoMutation2 ( value ) { return value / 10 ; }決定ミューテーションテストは、コード内の設計エラーの特定に重点を置き、特にプログラムの意思決定ロジックにおける欠陥や弱点の検出に重点を置きます。この手法では、算術演算子と論理演算子を意図的に変更して潜在的な問題を明らかにします。これらの演算子を操作することで、開発者はコードがさまざまな意思決定シナリオにどのように応答するかを体系的に評価できます。このプロセスにより、プログラムの意思決定経路が堅牢かつ正確であることが保証され、ロジックの欠陥から生じる可能性のあるコストのかかるエラーを防ぐことができます。決定ミューテーションテストはソフトウェア開発において貴重なツールであり、開発者が意思決定コードセグメントの信頼性と有効性を向上させることを可能にします。
例えば:
// 元のコードfunction isPositive ( number ) { return number > 0 ; }// 決定変異: 比較演算子を変更する関数isPositiveMutation1 ( number ) { return number >= 0 ; }// 決定変異: 結果を否定する関数isPositiveMutation2 ( number ) { return ! ( number > 0 ); }