次の方法で共有


テストの実行

部分非ランダム文字列テスト

James McCaffrey

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

非ランダム (AR: Antirandom) テストは、純粋なランダム テストの 1 つのバリエーションです。ランダム テストとは、ランダム入力を生成して、この入力をテスト対象のシステムに送るプロセスです。ほとんどの場合、ランダム テストの入力は、明確に予想される戻り値やシステム状態に関連付けられません。このような場合のランダム テストの目的は、ハングやクラッシュなど、なんらかのシステム エラーを発生させることです。研究によると、純粋なランダム テストは、同値分割テストや境界値分析テストなどの他のテスト手法に比べて、バグの発見にはあまり有効ではないことが示されています。ただし、ランダム テストは、通常、すばやく簡単に実装できるため、魅力的です。AR テストという考え方は、ハードウェア テストの分野から導入されたようです。基本的に AR テストでは、一連のランダム入力値を生成しますが、このとき、値どうしが可能な限り異なるようにします。これは、同じような値をシステムに入力しても、見つかるのは同じ種類のバグなので、互いに大きく異なる値を含む入力セットを使用すれば、ソフトウェア システムのさまざまなエラーを検出できる可能性が高くなるという考え方です。


図1 部分 AR 文字列の生成

例を挙げて説明しましょう。ハードウェア システムに、3 ビットの値を受け取るパラメーターがあるとします。したがって、全入力範囲は {000, 001, 010, 011, 100, 101, 110, 111} という 8 つの値になります。また、なんらかの理由でこの 8 つの値をすべてテストすることができず、AR 入力値として 4 つの値を含むテスト セットを生成するとします。最初の AR テスト セットは空で、入力範囲から値 {0,0,0} を任意に選択し、テスト セットに追加します。AR テスト セットに追加する次の入力は、同じ入力範囲の値で、テスト セットに含まれている現在値と最も異なる値です。この例では、どのように違いを考えても、まず間違いなく、値 {1,1,1} が {0,0,0} と最も異なります。したがって、この時点では AR テスト セットには {0,0,0} と {1,1,1} が格納されることになります。次に、値 {0,0,1} が、以下で説明する手順を使用して、入力範囲から選択され、AR テスト セットに追加されるとします。これで、この時点のセットには {0,0,0}、{1,1,1}、および {0,0,1} が格納されます。入力範囲の残りの 5 つの値はいずれも、テスト セットの 4 番目で最後のメンバーの候補です。ただし、厳密にどの値が選ばれるかは、差異の判定に使用する関数によって決まります。候補となる関数の 1 つは、2 つの値のハミング距離です。ハミング距離とは、単に 2 つの値間でビットが異なっている位置の数のことです。各候補の値と、AR テスト セットの現在のメンバー間のハミング距離の合計は、次のとおりです。

010: 1 + 2 + 2 = 5
011: 2 + 1 + 1 = 4
100: 1 + 2 + 2 = 5
101: 2 + 1 + 1 = 4
110: 2 + 1 + 3 = 6

{1,1,0} が AR テスト セットの現在のメンバーと最も異なるため、これがテスト セットに追加されます。この例からは、純粋な AR テスト入力を生成するには、入力範囲内の値をすべて列挙する必要があることがわかります。これは、比較的入力ビット サイズが小さい特定の種類のハードウェア テストか、入力範囲が狭いソフトウェア システムであれば可能です。しかし、純粋な AR テストを複雑なソフトウェア システムで実行できることはほとんどありません。このコラムでは、私が部分 AR 文字列テストと呼んでいる方法を紹介します。これは、広い範囲のソフトウェア システムのテストに使用できます。

これから何をしようとしているかを説明するには、まず 2 つのスクリーンショットを見てもらうのが一番です。図 1 は、部分 AR 文字列テスト セットの生成方法の例を示しています。

図 1 の例は、7 枚のカードを使うポーカー システムのようなものを基盤としています。各文字列は 7 枚のカードを表していて、'A' はエース、'T' は 10、'c'、'd'、'h'、および 's' はそれぞれクラブ、ダイヤ、ハート、スペードを表しています。重複を許すとすると、入力文字列数の合計は、52 の 7 乗で 1,028,071,702,528 になり、ほとんどのテストにおいて手に負える数ではないでしょう。AR テスト セットを生成するには、まず代理入力範囲を作成します。これは、入力範囲全体からランダムに選択するサブセットです。ここでは、10 個の文字列だけに人為的にサイズを小さくした代理範囲を使用して、例を短く保つようにしています。AR テスト セットのサイズもデモ用なので、人為的にサイズを小さくし、3 に設定しています。AR テスト セットは最初は空です。代理範囲から取り出された最初の文字列値 "Ts Th 3h Qd Kd 4d 9d" が、AR セットに追加されます。次に、デモ プログラムは代理範囲内の各文字列を検査して、現在 AR セット内に 1 つだけある文字列と最も異なる文字列を見つけます。この場合は、"3c 9h 9s 3s As 9c 2c" が最も異なる値として選択されています。この差異の計算がどのように実行されるかについては、このあとすぐに説明します。次に、デモ プログラムは代理範囲を再度スキャンして、AR セットに現在保持されている 2 つの文字列と最も異なる文字列値を見つけます。最後の値 "9d Js Js Kd 7h 5d Jc" が特定され、AR セットに追加されたら、デモ プログラムは最終的なセットを表示します。文字列をランダムに選択した場合よりも、互いの差異が大きい文字列を含むテスト入力のセットが、結果として得られます。


図 2 部分 AR 文字列を使用したテスト

図 3 部分 AR 文字列テスト セットを生成するしくみ

using System;
namespace Demo
{
class Program
{
static Random r = new Random(0);
static void Main(string[] args)
{
try
{
Console.WriteLine(
"\nBegin partial antirandom string generation demo”);
long domainSize = (long)Math.Pow(52, 7); // 1,028,071,702,528
int surrogateDomainSize = 8;
int antiRandomTestSetSize = 3;
// display sizes of input domain, surrogate domain,
antirandom test set
// initialize and display surrogate domain
// create partial antirandom test set
// display partial antirandom test set
}
catch (Exception ex)
{
Console.WriteLine("Fatal: " + ex.Message);
Console.ReadLine();
}
} // Main()
} // class
} // ns

図 2 のスクリーンショットは、システムのテストに部分 AR 文字列を使用する方法の一例です。ここでは、前述のとおり 7 枚から成るポーカーの手を表す文字列を受け取る、架空の PokerLib.dll モジュールをテストします。このテストでは、部分 AR テスト セットを繰り返し生成し、各テスト セット内のすべての文字列値をテスト対象のシステムに送ります。ここでは、任意の最大サイクル数として 1,000,000 を設定しています。図 2 のデモでは、代理入力サイズに 100 を、部分 AR テスト セットのサイズに 10 を使用しています。テスト対象の架空の PokerLib.dll システムは、部分 AR テストを実行するときに発生が予想されるものを実際に確認できるように、例外をすぐに生成するようにコーディングされています。部分 AR テストはエラーを生成することが目的のテストです。

このコラムでは、AR テスト、部分 AR テスト、および部分 AR 文字列テストという用語をほぼ同じ意味で使用します。ただし、学術的な研究論文などでは、AR テストは通常、入力値がバイナリで、入力範囲全体をスキャンして AR テスト セットの値を決定するという前述のプロセスを指します。"部分" という語は、ここで説明する、代理範囲をスキャンする手法を "純粋な" AR テストから区別するために使用しています。

これ以降は、図 1図 2 のスクリーンショットを生成したコードを紹介しながら説明します。具体的には、2 つのの文字列間の差異の判定に使用できるさまざまな手法と、それぞれの手法が適している状況について説明します。また、代理範囲のサイズと AR テスト セットのサイズを決める方法についても、多少のガイドラインを示します。ここでは、読者に初級から中級レベルのプログラミング スキルがあることを想定しています。サンプルは C# でコーディングしていますが、このコードは VB.NET、Java、Perl、PowerShell など他の言語に簡単に書き換えることができるでしょう。皆さんが部分 AR テストは面白いトピックだと思われ、部分 AR 文字列を使用してシステムをテストできることは、皆さんのテスト ツール キットに貴重なツールを追加することになると確信しています。

部分 AR 文字列の生成

図 3 のコードは、図 1 のスクリーンショットを生成したプログラムの全体構造です。

Visual Studio 2008 を使用して、"Demo" という C# コンソール アプリケーション プロジェクトを作成します。このコラムの部分 AR 生成では、特殊な .NET Framework 名前空間を一切使用しないため、ルートの System 名前空間を参照するものを除き、ステートメントを使用して自動生成されたものを削除しました。また、シード値 0 を使用して、Random オブジェクトを初期化しています。

static Random r = new Random(0);

シード値を受け取らない既定のコンストラクターは使用せず、Random コンストラクターにシード値を渡して、出力を再現可能にしています。わかりやすさと簡潔さを考慮し、このコラムでは "ランダム" という用語を使用していますが、正確には "擬似ランダム" です。起動メッセージを表示したら、Math.Pow() メソッドを使用して、テスト シナリオの入力範囲内の文字列の合計数を計算します。通常、この情報は必要ありませんが、徹底的なテストを行うには入力数が多すぎるような状況では、部分 AR 文字列テストが通常は有用であることを指摘するために示しています。この例では、入力文字列は、7 枚のカードから成る手を表し、"Rs Rs Rs Rs Rs Rs Rs" の形式で表現します。R は 13 の並び値 ("A"、"T"、"2 ~ 9"、"J"、"Q"、"K") のいずれかで、"s" は 4 種類のスイート ("c"、"d"、"h"、"s") のいずれかです。各カードは、13 * 4 = 52 の値のいずれかになり、重複も許されます。したがって、とり得る文字列数の合計は、52 の 7 乗になります。

図 4 ランダムなカード文字列の生成

public static string MakeARandomCard()
{
string answer = "";
int rank = r.Next(1, 14); // 1-13
int suit = r.Next(1, 5); // 1-4
if (rank == 1) answer += "A";
else if (rank == 10) answer += "T";
else if (rank == 11) answer += "J";
else if (rank == 12) answer += "Q";
else if (rank == 13) answer += "K";
else answer += rank.ToString(); // 2 thru 9
if (suit == 1) answer += "c";
else if (suit == 2) answer += "d";
else if (suit == 3) answer += "h";
else answer += "s";
return answer;
}

次に、代理範囲とテスト セットのサイズをそれぞれ 8 と 3 に設定します。このサイズは、通常の運用環境で使用するサイズに比べると、かなり小さな値です。これらのサイズを選ぶ場合の問題については、このあとすぐに説明します。MakeARandomDomainString() というヘルパー メソッドを呼び出して、代理範囲を初期化します。

Console.WriteLine("\nGenerating surrogate domain");
for (int i = 0; i < surrogateDomain.Length; ++i)
  surrogateDomain[i] = MakeARandomDomainString();

このヘルパー メソッドのコードは、次のとおりです。

public static string MakeARandomDomainString()
{
  string answer = "";
  for (int c = 0; c < 6; ++c) // first 6 cards
    answer += MakeARandomCard() + " ";
  
  answer += MakeARandomCard(); // last card, no trailing space
  return answer;
}

部分 AR テストを使用するときは、テスト シナリオごとに入力文字列形式が異なります。極端な例では、長さを除いて入力文字列形式に対する制約がまったくない場合もあります。そのような場合は、完全なランダム文字列を単純に生成する必要があります。この例では、適切に構造化された形式を使用しています。一般に、部分 AR 文字列テストの有用性は、入力文字列の構造化が進むにつれて向上します。MakeARandomDomainString() メソッドは、MakeARandomCard() ヘルパー メソッドを繰り返し呼び出すことで、7 枚のカードを表す文字列を生成します(図 4 参照)。

前に初期化した Random オブジェクトを使用して、それぞれ範囲が [1,13] と [1,4] である 2 つの擬似ランダム整数を生成します。Random.Next() メソッドへのこの 2 つのパラメーターは、下限 (この値を含む) と上限 (この値を含まない) を表しているため、r.Next(1,14) は、1 ~ 14 のランダム整数ではなく、1 ~ 13 (13 を含む) のランダム整数を生成します。また、System.Text 名前空間のより効率的な StringBuilder クラスではなく、オーバーロードされた += 演算子を使用して、カード文字列を作成しています。今回のコラムでは、汎用プログラミング手法を使用して、サンプル コードを他のプログラミング言語にリファクタリングしやすくしています。また、従来の非 OOP スタイルを使用してサンプルをコーディングしているため、JavaScript や Perl など、OOP のサポートが限られているスクリプト言語にも簡単にリファクタリングできます。

代理範囲を作成したら、これを表示します。

Console.WriteLine("\nThe surrogate domain is: \n");
for (int i = 0; i < surrogateDomain.Length; ++i)
  Console.WriteLine("[" + i + "] " + surrogateDomain[i]);

この例では、代理範囲内に重複値があるかどうかは確認しません。通常、代理範囲内に重複値が含まれないようにするための処理は、あまりに手間がかかるため、それにより得られるメリットを正当化できません。ランダム テスト手法の最大の魅力は、簡潔なことです。また、これもすぐにわかることですが、代理範囲内の重複値が AR テスト セットに追加される可能性は非常に低くなります。これで、部分 AR テスト セットを生成する準備ができました。図 5 のコードは、その方法を示しています。まず、代理範囲の最初の値を選択し、これを AR テスト セットに追加します。

Console.WriteLine("Adding " + surrogateDomain[0] + " to AR test set");
antiRandomTestSet[0] = surrogateDomain[0];
int numberValuesInTestSet = 1;

代理範囲内の文字列はランダムに生成されているため、ほとんどの場合、特定の文字列を選択するメリットはありません。numberValuesInTestSet 変数を使用して、AR テスト セットに現在含まれている文字列の個数を追跡します。

図 5 部分 AR 文字列テスト セットを生成するコード

for (int k = 1; k < antiRandomTestSet.Length; ++k) {
int largestTotalDeltaForAllI = 0;
int indexOfBestCandidate = 0;
int totalDeltaForCurrentI = 0;
for (int i = 0; i < surrogateDomain.Length; ++i) {
totalDeltaForCurrentI = 0;
for (int j = 0; j < numberValuesInTestSet; ++j) {
totalDeltaForCurrentI +=
RotateDistance(surrogateDomain[i], antiRandomTestSet[j]);
} // j
if (totalDeltaForCurrentI > largestTotalDeltaForAllI) {
largestTotalDeltaForAllI = totalDeltaForCurrentI;
indexOfBestCandidate = i;
}
} // i
Console.WriteLine("\nDetermining antirandom value: [" + k + "]");
Console.WriteLine(
"String at [" + indexOfBestCandidate + "] in surrogate domain");
Console.WriteLine(
"is most different from strings curently in AR test set");
Console.WriteLine(“Adding " + surrogateDomain[indexOfBestCandidate] +
" to AR test set");
antiRandomTestSet[numberValuesInTestSet++] =
surrogateDomain[indexOfBestCandidate];
} // k

インデックス変数 k が指定されている最も外側の for ループは、AR テスト セットの各セルを指し、値を設定するために使用されます。インデックスが 0 の AR テスト セットの最初のセルに、代理範囲の最初の値を設定しているため、k = 1 という値から始めます。代理範囲内の文字列で、現在 AR テスト セットに含まれているすべての文字列と最も異なるものを見つけることが目標なので、largestTotalDeltaForAllI という変数を作成して、見つかった最も大きな差異を表す値を追跡します。また、変数 indexOfBestCandidate を初期化して、最も差異が大きい文字列値が見つかった代理範囲内の場所を記録します。

図 6 レーベンシュタイン距離

public static int LevenshteinDistance(string s, string t) // assume s, t
not null
{
int[,] dist = new int[s.Length + 1, t.Length + 1]; // distance
int subCost; // substitution cost
if (s.Length == 0) return t.Length;
if (t.Length == 0) return s.Length;
for (int i = 0; i <= s.Length; ++i)
dist[i,0] = i;
for (int j = 0; j <= t.Length; ++j)
dist[0,j] = j;
for (int i = 1; i <= s.Length; i++) {
for (int j = 1; j <= t.Length; j++) {
if (t[j-1] == s[i-1])
subCost = 0;
else
subCost = 1;
int temp = Math.Min(dist[i - 1, j] + 1, dist[i, j - 1] + 1);
dist[i, j] = Math.Min(temp, dist[i - 1, j - 1] + subCost);
}
}
return dist[s.Length, t.Length];
}

インデックス変数 i が指定されている次の for ループは、代理範囲内の候補の各文字列を反復処理します。変数 totalDeltaForCurrentI を 0 に初期化したら、インデックス変数 j を持つ入れ子にされた for ループを使用して、現在 AR テスト セット内にある各文字列を取得します。したがって、この時点で、インデックス k (および変数 numberValuesInTestSet) は、AR テスト セット内の次の空のセルを指し、インデックス i は代理範囲内の候補文字列を指し、インデックス j は AR テスト セットに現在含まれている文字列を指します。次に、i と j が指す 2 つの文字列間の差異を判定し、代理範囲内の現在の候補文字列との差異を、累積合計に追加します。

totalDeltaForCurrentI +=
RotateDistance(surrogateDomain[i], antiRandomTestSet[j]);

RotateDistance() ヘルパー メソッドへの呼び出しは重要です。これについては、このあとすぐに説明します。候補文字列と、AR テスト セット内のすべての文字列間の差異の合計が、現在の最大差異合計値よりも大きければ追跡用変数を更新します。

if (totalDeltaForCurrentI > largestTotalDeltaForAllI) {
largestTotalDeltaForAllI = totalDeltaForCurrentI;
indexOfBestCandidate = i;
}

部分 AR 文字列テストの眼目は、文字列どうしの差異が可能な限り大きい文字列を生成することでした。いくつか進行状態メッセージをコマンド シェルに出力したところで、代理範囲内で見つかった最適な候補を AR テスト セットに追加し、テスト セットに含まれる値の個数を更新します。

antiRandomTestSet[numberValuesInTestSet++] =
  surrogateDomain[indexOfBestCandidate];

インデックス k も変数 numberValuesInTestSet も同じ値を保持しているため、ここは k を代わりに使用することもできます。しかし、より説明的な変数を使用することで、コードをよりわかりやすくできると、個人的には考えています。

2 つの文字列間の差異計算

部分 AR 文字列テストを実行する際、2 つの文字列間の差異を計算するメソッドが必要です。実際にどのように 2 つの値を比較するかは、テスト シナリオで使用される文字列の形式によって決まります。比較対象の文字列の長さが必ず同じになる場合、簡単な方法としては、文字が異なる位置の数を表すだけの文字ベースのハミング距離を使用できるでしょう。たとえば、s = "car" と t = "bag" であれば、s と t のハミング距離は 2 です。2 つの文字列の長さが異なる場合、長い文字列と短い文字列間のサイズの差異を追加するように変更したハミング距離を使用できます。たとえば、s = "car" と t = "cable" であれば、変更したハミング距離は 1 ('r' と 'b' が異なる) + 2 ('l' と 'e' の長さの差異) = 3 です。次は、文字列のサイズが同じ場合のハミング距離を実装する方法の一例です。

public static int HammingDistance(string s, string t)
{
  if (s.Length != t.Length)
    throw new Exception("s and t must have same length in
      HammingDistance()");
  int ct = 0;
  for (int i = 0; i < s.Length; ++i)
    if (s[i] != t[i])
      ++ct;
  return ct;
}

2 つの文字列間の差異を計測するもう 1つの簡単な方法は、文字ベースのデカルト距離です。これは、高校の代数で教わった 2 点間の幾何学的距離に似ています。たとえば、s = "bat" と t = "car" という文字列を考えます。'b' の ASCII 値は 98 で、'c' の ASCII 値は 99 なので、この 2 つの文字の差は 1 です。同様に 't' と 'r' の差は 2 です。文字列 s と t の文字ベースのデカルト距離は、sqrt(1^2 + 0^2 + 2^2) = 2.24 です。2 つの文字列間の差異を計算する洗練された方法の 1 つに、レーベンシュタイン距離があります。2 つの文字列間のレーベンシュタイン距離は、片方の文字列をもう片方の文字列に変換する場合に最低限必要になる文字の挿入、削除、および置き換えの回数です。レーベンシュタイン距離は、それだけでも非常に面白いトピックですが、このコラムではレーベンシュタイン距離については詳しく説明しません。図 6 は、部分 AR 文字列テストに使用できるレーベンシュタイン距離の簡単な実装を示しています。

入力文字列が構造化されるほど、ハミング距離やレーベンシュタイン距離などの一般的な差異計測メソッドよりも、カスタム差異計測メソッドの有用性が高まる傾向があります。たとえば、ポーカー カードのシナリオを使って、次の 2 つの文字列を考えて見ましょう。

s = "Ac Kd Qh Js Tc 9d 8h"
t = "Kd Qh Js Tc 9d 8h 7c"

この 2 つの文字列は、非常によく似たポーカーの手を表しています。7 枚のうち 6 枚のカードが同じです。ただし、結果としては、s と t のハミング距離は 14 になり、レーベンシュタイン距離は 6 になります。このシナリオでは、RotateDistance() というカスタム差異計測メソッドを実装しています。RotateDistance() メソッドは、ポーカーの手の文字列の構造を利用して、カードの値に基づく比較を行います。RotateDistance() のコードは、図 7 のとおりです。このカスタム差異計測メソッドでは、まず、メソッド入力引数間のハミング距離が計算されます。

s = "Ac Kd Qh Js Tc 9d 8h"
t = "Kd Qh Js Tc 9d 8h 7c"
distance = 14

次に、RotateDistance(s,t) により、カードの位置を 1 つずらすことで文字列 s を左に回転し、再びハミング距離を計算します。

s = "Kd Qh Js Tc 9d 8h Ac"
t = "Kd Qh Js Tc 9d 8h 7c"
distance = 1

メソッドは、この回転して比較するという処理を、7 枚のカード位置のすべてで実行し、見つかった最小の距離 (この例では 1) を返します。RotateLeft() ヘルパー メソッドのコードでは、String.Substring() を使用して入力引数を 2 つの部分に分けたうえで、文字列の連結を使用して、再びこの 2 つの部分を結合しています。

public static string RotateLeft(string s)
{
  string firstPart = s.Substring(0, 3);
  string remainder = s.Substring(3, s.Length - 3);
  string answer = remainder + " " + firstPart.Trim();
  return answer;
}

個人的な経験では、テスト入力文字列がかなり構造化されている場合は、ここで説明した回転して比較するアルゴリズムのようなものが非常に有効になることが多くなります。テスト シナリオに非常に構造化された入力文字列が含まれている場合でも、ここで使用したようなカスタム差異計測メソッドを作成する義務はないことは指摘させてください。カスタム差異計測メソッドを使用すると、ハミング距離やレーベンシュタイン距離を使用して生成されたものに比べて、互いの差異が大きい AR 文字列が生成されやすくなりますが、そのメリットと、カスタム差異計測メソッドの作成にかかる手間とを比較検討する必要があります。

図 7 カスタム RotateDistance() メソッド

public static int RotateDistance(string s, string t)
{
int d = HammingDistance(s, t);
for (int compare = 1; compare <= s.Length / 3; ++compare)
{
s = RotateLeft(s);
int temp = HammingDistance(s, t);
if (temp < d)
d = temp;
}
return d;
}

部分 AR 文字列によるテスト

部分 AR 文字列テストは、まさしくメタ手法の 1 つです。つまり、特殊な方法ではなく、より一般的なガイドラインのセットそのものであり、あらゆるテストに適用できます。部分 AR 文字列テストを実行する方法は多数ありますが、その 1 つを 図 2 のスクリーンショットに示しています。図 8 に表示されているのは、図 2 のスクリーンショットを生成した主要なコードです。

まず、テスト対象のシステムの名前、代理範囲のサイズ、AR テスト セットのサイズなど、いつくかのログ メッセージをコマンド シェルに表示します。代理範囲のサイズが m で、AR テストのサイズが n のときに、前述の方法を使用した場合、AR テスト セットを生成するには (n * (n-1) * m) / 2 回の反復処理が必要です。また、文字列比較は反復処理ごとに実行されるため、1 回の比較に少なくとも min(length(s),length(t)) 文字の比較が必要です。重要な点は、代理範囲と AR テスト セットに大きなサイズを指定すると、部分 AR テスト セットの生成にかなりの処理時間が必要になる可能性があることです。この 2 つのサイズが大きくなればなるほど、テスト文字列入力の多様性という点で AR テスト セットの質が向上しますが、AR テスト セットの生成に時間が必要となるため、実際にテスト対象のシステムに送られる入力数が少なくなります。

図 8 部分 AR 文字列を使用したテストの例

int sdSize = 100;
int arSize = 10;
Console.WriteLine("\nBegin antirandom testing demo");
Console.WriteLine("System under test = PokerLib.dll module");
Console.WriteLine("Maximum number of cycles = 1,000,000");
Console.WriteLine("Surrogate domain size = " + sdSize);
Console.WriteLine("Antirandom test set size = " + arSize);
long maxNumberCycles = 100000;
int cycle = 0;
while (cycle < maxNumberCycles)
{
++cycle;
Console.WriteLine("=============================");
Console.WriteLine("Current antirandom cycle = " + cycle);
Console.WriteLine("Generating antirandom test set");
string[] arTestSet = MakeAntiRandomTestSet(sdSize, arSize);
Console.WriteLine("\nThe antirandom test set is: \n");
Console.WriteLine("[0] " + arTestSet[0]);
Console.WriteLine("[1] " + arTestSet[1]);
Console.WriteLine(" . . . . . . .");
Console.WriteLine("[9] " + arTestSet[9]);
Console.WriteLine(
"\nSending each antirandom input to system under test\n");
for (int i = 0; i < arTestSet.Length; ++i) {
PerformTest(arTestSet[i]);
}
Console.WriteLine("All antirandom input accepted by SUT\n");
}

次に、AR テスト セットを生成するサイクルの最大数を指定します。多くのテスト シナリオでは、AR テストを連続実行します。このような場合は、制御している while ループを while(true) に置き換えるだけです。メインの AR 制御ループでは、AR テスト セットを生成して、テスト セット内の各文字列をテスト対象のシステムに入力として渡しています。ここでは、2 つの小さなヘルパー メソッドを作成しました。1 つ目のメソッド MakeAntiRandomTestSet() は、前述のコードをラップしているだけです。

public static string[] MakeAntiRandomTestSet(int surrogateDomainSize,
  int antirandomTestSetSize)
{
  string[] result = new string[antirandomTestSetSize];

  string[] sd = new string[surrogateDomainSize];
  // generate surrogate domain, sd
  // fill antirandom test set, result
  return result;
}

2 つ目のメソッド PerformTest() は、文字列を受け取り、テスト対象の架空の PokerLib.dll システムのテストのシミュレーションを行います。

public static void PerformTest(string s)
{
  if (s[1] == 'c' && s[4] == 'c') {
    throw new Exception("Exception thrown in system
      under test\n" + "for input = " + s);
  }
}

ここで、テスト ケース入力の最初の 2 つのカードがクラブであった場合に、テスト対象のシステムが失敗するような状況を作ります。システム エラーを生成するテスト ケース入力を表示して、問題を再現できるようにし、最終的には開発チームによって修正できるようにしています。大規模な AR テストの実行から数日後、または数週間や数ヶ月後であっても、テスト対象のシステムで致命的なエラーが発生した場合に、エラーを生成した入力を記録し忘れていたことがわかったら、不幸なことでしょう。

部分 AR 文字列に使用できるパターンは他にも多数あります。サイズの小さい代理範囲と AR テスト セットを多くのサイクル数で使用するのではなく、サイズの大きい代理範囲からサイズの大きい AR テスト セットを 1 つだけ生成して、テスト サイクルを少なくすることもできます。ここで説明している方法では、AR テスト セットを 1 つ生成し、そのセット内の文字列を使用しますが、AR テスト セットは保存していないことに注意してください。テスト セットを保存する場合は、文字列の生成に合わせて、各 AR テスト セットの文字列を、テキスト ファイルや Excel スプレッドシート、SQL テーブルなどの外部の保存メカニズムに書き込む方法があります。その後、再現テストを実行する場合は、テスト入力の準備は既にできているため、入力を再生成しなければならない場合にくらべて、はるかに短時間でテストを実行できます。

まとめ

このコラムで紹介した考え方をまとめます。部分 AR 文字列テストは、テスト対象のシステムが文字列入力を受け取る場合に、入力範囲のサイズが大きすぎて徹底的なテストを実施できないときに便利な手法です。部分 AR 文字列テストを実行するには、まず、入力範囲のランダムなサブセットであるサイズの大きな代理範囲を作成します。次に、AR テスト セットを生成します。これは、代理範囲のサブセットであり、このテスト セット内の文字列は互いに可能な限り異なるようにします。次に、AR テスト セット内の各文字列をテスト対象のシステムに渡します。ほとんどの場合、明確な予想値や、各 AR 文字列入力の状態を決めることはありません。どちらかと言えば、テスト対象のシステムがなんらかの形で失敗するようにします。ハミング距離とレーベンシュタイン距離は、2 つの文字列間の差異を計算する一般的な 2 つの方法です。テスト シナリオに、かなり構造化された文字列が含まれる場合は、独自のカスタム文字列差異計測メソッドを作成できます。このようなカスタム メソッドでは、多くの場合、文字列の回転を行います。

ソフトウェア テスト分野の学術的研究によると、唯一の最適なテスト手法というものは存在しないことを示す結果が、繰り返し提示されています。適切な場合は、部分 AR 文字列テストを自分のテスト プロジェクトに追加して、バグが少なく、信頼性と安全性の高いソフトウェアの開発に役立ててください。

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