テストの実行

拡散テスト

James McCaffrey

コード サンプルのダウンロード

James McCaffrey今月のコラムでは、私が「拡散テスト」と呼んでいるソフトウェア テスト手法を紹介します。拡散テストの基本的な考え方は、既存のテスト ケースが合格したら、そのテスト ケースから新たなテスト ケース データを自動作成するというものです。

拡散テストは、ほとんどのソフトウェア テスト シナリオには使用できない手法ですが、使用できれば、テスト作業の効率を大幅に向上できる手法です。私の経験によると、おそらく拡散テストがニッチな手法であることがその一因と考えられますが、拡散テストは主要テスト手法の中でもあまり知られていない手法の 1 つです。

拡散テストの例を紹介する前に、この手法の開発に至った動機を説明しておきましょう。一般に、テスト ケースは、テスト ケース ID、1 つまたは複数の入力値のセット、および想定結果から構成されます。たとえば、ベクター {001, 2, 3, 5} は、ID が 001、入力値が 2 と 3、想定結果が 5 から成る Sum 関数のテスト ケースを表します。テスト ケースの入力値をテスト対象のシステムに送信すると、関数によって実際の結果が生成され、それを想定結果と比較してテスト ケースの合否が判断されます。

多くのソフトウェア テストにおいて、テスト ケースの想定結果を決定する部分が難しく、時間もかかります。たとえば、入力された 2 つの割合値の調和平均を計算する、基本的な数学関数をテストするとします。時速 30.0 km (kph) と 60.0 kph の平均は、"(30.0+60.0)/2=45.0 kph" ではありません。この平均は 30.0 と 60.0 の調和平均で表され、"1/((1/30.0+1/60.0)/2)=40.0 kph" となります。この関数の想定結果を何百も生成するのは面倒な作業で、非常に時間がかかるうえに間違える可能性も高くなります。

テスト ケースの想定結果の決定が困難なことは、ソフトウェア テストでは周知の概念で、テストの「賢人に授けられし問題」と称されることもあります。実際のところ、ソフトウェア テストの聖杯たる至高の目標の 1 つに、テスト ケースを自動的に決定できる手法を見つけることが挙げられます。つまり、拡散テストの開発に至った動機は、新しいテスト ケース データを自動生成できれば、テスト プロセスの中で時間がかかる作業を避け、システムをより十分にテストできるという考えです。

拡散テストを使用してテスト ケース データを自動生成するという考え方は、原理としてはすばらしいものですが、どのように機能するのでしょう。拡散テストについて説明する最善の方法は、例を使用することです。図 1 をご覧ください。

image: Diffusion Testing Demo

図 1 拡散テストのデモ

ここでは、Choose(n,k) という関数をテストします。この関数は、n 個の項目から k 個の項目を順番に関係なく選択する方法が何とおりあるかを返します。この簡単な例には、既存のテスト ケースが 3 つあります。1 つ目のテスト ケースは、入力値 n が 8、k が 3 で、想定結果が 56 です。テスト ハーネスで 1 つ目のテスト ケースを実行すると合格になるため、拡散テストを使用して、入力値 n が 9、k が 3 で、想定結果が 84 の新しいテスト ケースが自動生成されます。すばらしいですね。テスト ケース 002 は不合格になるため、新しいテスト ケースを生成しません。

ところで、既存のテスト ケースから、どのようにして新しいテスト ケースを生成するのでしょう。Choose(n,k) 関数の場合、数学的には、"Choose(n+1,k)=Choose(n,k)*(n+1)/(n-k+1)" という計算式が成り立ちます。つまり、新しい入力値と古い戻り値の間には、既知の関係性があります。既存のテスト ケースから拡散してテスト ケースを生成するのに使用した関数を、図 2 に示します。図 1 に示した出力を生成する完全なプログラムは、code.msdn.microsoft.com/mag201103TestRun (英語) から入手できます。

図 2 新しいテスト ケースの生成

static string CreateDiffusedTestCase(string existingTestCase)

{

  // Assumes input format is CaseID:N:K:Expected

  string[] tokens = existingTestCase.Split(':');



  string oldTestCase = tokens[0];

  int oldN = int.Parse(tokens[1]);

  int oldK = int.Parse(tokens[2]);

  long oldExpected = long.Parse(tokens[3]);



  string newTestCase = oldTestCase + "-diffused";

  int newN = oldN + 1;

  int newK = oldK;

  long newExpected = (oldExpected * (oldN + 1)) / (oldN - oldK + 1);



  return newTestCase + ":" + newN + ":" + newK + ":" + newExpected;

}

他にいくつか例を紹介すれば、この考え方をさらに明確に理解できるでしょう。三角関数の正弦 (サイン) と余弦 (コサイン) を計算する関数をテストするとします。"sin 2t=2*sin t*cos t" という公式を覚えているでしょう。したがって、サインとコサインの入力値から合格する結果を生成するテスト ケースがあれば、拡散テストを使用して、サイン関数用に新しいテスト ケースを生成できます。

拡散テストは魔法ではありません。ある種の製品 ID を受け取り、SQL データベースを検索し、製品在庫があれば true を返し、在庫がなければ false を返す関数をテストするとします。入力値と結果の間には関係性がないため、このシナリオでは拡散テストを使用できません。この点において、拡散テストは、境界条件テスト、ペアワイズ テストなどの形式のテストに似ています。拡散テストは、特定の状況でのみ使用できる手法です。

拡散テストの例をもう 1 つ紹介します。Gauss(z) という関数を作成したとします。この関数は、標準正規分布値 z を受け取り、負の無限大から z までの標準正規分布 (釣鐘曲線) によって表される領域を返します。たとえば、"Gauss(-1.645)=0.0500"、"Gauss(1.645)=0.9500"、"Gauss(0)=0.5000" などとなります。拡散テストを使用するには、Gauss 関数の単調性と、負の無限大から 2.5 までの範囲の z 値では Gauss(z+0.1) の結果が Gauss(z) より大きくなければならないことに注目することが 1 つの方法です。また、Gauss 関数の対称性と、0.0 より小さい z 値では、"Gauss(-z)=1.0-Gauss(z)" とことに注目するのも 1 つの方法です。

これまで紹介した例は、拡散テストを使用できる最も一般的な 3 つのシナリオを示しています (ただし、シナリオはこれだけではありません)。1 つ目のシナリオでは、再帰関係として定義できる数学関数をテストしています。2 つ目のシナリオでは、単調性のある関係を持つ関数をテストしています。3 つ目は、対称性のある関係を持つ関数をテストしています。Sum(x,y) など、入力値の順番を入れ替えても戻り値が変わらない関数をテストすることも、関連性を利用するテスト形式ですが、拡散テストではありません。

数学関数の多くは、再帰性、単調性、または対称性を備えているため、拡散テストでメリットが得られる最も一般的なテスト対象のコンポーネントです。ただし、その他の状況にも注意すべきです。再帰関係を持つ数学関数は、多くの場合、1 つの既存のテスト ケースから新たに複数のテスト ケースを生成できるため、特に拡散テストに適しています。図 1 のデモで示すように、n が 8、k が 3、想定結果が 56 のテスト ケース 001 から、n が 9、k が 3、想定結果が 84 の新しいテスト ケースが生成されます。この新しいテスト ケースは、n が 10、k が 3、想定結果が 120 という別のテスト ケースの生成に使用でき、このテスト ケースが合格すれば、さらに別の新しいテスト ケースの生成にも使用できます。

拡散テストの説明を終える前に、さまざまなソフトウェア テスト手法や原理の名前に関する不満にお答えしておきます。今月のコラムで説明した手法は、既存のテスト ケースから拡散して新しいテスト ケースを生成するため、拡散テストと名付けました。この手法は、適応テストや自動生成テストなどと呼ぶこともできます。重要なのは名前ではなく、むしろ名前で表される手法の方です。

ソフトウェア テストなど多くの研究分野では、自称専門家がだれもが知っている手法に名前を付けることで、その分野の初心者が、名前自体にある程度重要な意味があるということを暗黙のうちに納得するようにしています。これは通常、新鮮で巧みな名前を会議で発表することで、トレーニングを直接販売したり、コンサルティング サービスを間接販売したいという願望が動機付けになっています。明らかに反則的な命名として、"探索的テスト" や "教育関連テスト" というのがありますが、このような例は他にもたくさんあります。そこで、ソフトウェア テスト手法について説明するうえでわかりやすい名前であり、テクニカル ツールキットに簡単に追加できる、「拡散テスト」という用語を採用しました。

Dr. James McCaffrey は Volt Information Sciences Inc. に勤務し、ワシントン州レドモンドにあるマイクロソフト本社で働くソフトウェア エンジニアの技術トレーニングを管理しています。これまでに、Internet Explorer、MSN サーチなどの複数のマイクロソフト製品にも携わってきました。また、『.NET Test Automation Recipes: A Problem-Solution Approach』(Apress、2006 年) の著者でもあります。連絡先は jammc@microsoft.com (英語のみ) です。

この記事のレビューに協力してくれた技術スタッフの Bj RollisonAlan Page に心より感謝いたします。