8

私のお気に入りのミューテーションテストフレームワーク( NinjaTurtles )用の「OffBy One」ミューテーションテスターを作成する過程で、実装の正確さを確認する機会を提供するために、次のコードを作成しました。

public int SumTo(int max)
{
    int sum = 0;
    for (var i = 1; i <= max; i++)
    {
        sum += i;
    }
    return sum;
}

これは十分に単純なように思えますが、IL内のすべてのリテラル整数定数を変更しようとしても問題が発生することはありませんでした。結局のところ、3つ(、、、、および)しかあり0ませ1++

間違い!

最初の実行で、この特定のインスタンスでは機能しないことが非常に明白になりました。なんで?コードをに変更するため

public int SumTo(int max)
{
    int sum = 0;
    for (var i = 0; i <= max; i++)
    {
        sum += i;
    }
    return sum;
}

合計に0(ゼロ)を追加するだけで、これは明らかに効果がありません。複数のセットだった場合は別の話ですが、この場合はそうではありませんでした。

これで、整数の合計を計算するためのかなり簡単なアルゴリズムがあります

sum = max * (max + 1) / 2;

いずれかの定数から1を加算または減算するとエラーが発生するため、ミューテーションは簡単に失敗する可能性があります。(それを考えるとmax >= 0

したがって、この特定のケースでは問題が解決しました。それは私が突然変異のテストに望んでいたことをしませんでしたが、それは私が失ったときに何が起こるかをチェックすることでした++-事実上無限ループ。しかし、それは別の問題です。

だから-私の質問: 0または1から始まるループが、同様の方法でリファクタリング(テスト中またはテスト中のコード)できない「1つずつ突然変異」テストの失敗につながる可能性がある、些細なまたは重要なケースはありますか?(例をお願いします)

:ミューテーションが適用された後にテストスイートが合格すると、ミューテーションテストは失敗します。

更新:ささいなことではないが、テストがリファクタリングされて失敗する可能性があるものの例は次のようになります

public int SumArray(int[] array)
{
    int sum = 0;
    for (var i = 0; i < array.Length; i++)
    {
        sum += array[i];
    }

    return sum;
}

このコードに対するミューテーションテストは、指定したテスト入力がであった場合にに変更var i=0すると失敗します。ただし、テスト入力をに変更すると、ミューテーションテストは失敗します。したがって、リファクタリングが成功すると、テストが証明されます。var i=1new[] {0,1,2,3,4,5,6,7,8,9}new[] {9,8,7,6,5,4,3,2,1,0}

4

4 に答える 4

4

この特定の方法では、2つの選択肢があると思います。この数学的異常のためにミューテーションテストに適していないことを認めるか、指定した形式にリファクタリングするか、他の方法(おそらく再帰的ですか?)を使用して、ミューテーションテストに安全な方法で記述しようとします。 )。

あなたの質問は本当にこれに要約されます:要素0がループの操作に含まれるか除外されるかを気にし、その特定の側面に関するテストを書くことができない実際の状況はありますか?私の本能はノーと言うことです。

あなたのささいな例は、 NinjaTurtlesについて書いている私のブログでテスト駆動性と呼んでいるものの欠如の例かもしれません。このメソッドを必要な範囲でリファクタリングしていない場合の意味。

于 2012-04-30T13:19:22.033 に答える
3

「ミューテーションテストの失敗」の自然なケースの1つは、行列の転置のアルゴリズムです。単一のforループにより適したものにするには、このタスクにいくつかの制約を追加します。行列を非正方形にし、転置をインプレースにする必要があります。これらの制約により、1次元配列は行列を格納するのに最適な場所になり、forループ(通常はインデックス「1」から開始)を使用して行列を処理できます。インデックス「0」から開始した場合、マトリックスの左上の要素は常にそれ自体に転置されるため、何も変更されません。

このようなコードの例については、他の質問への回答を参照してください(C#ではありません。申し訳ありません)。

ここで、「1つずつ突然変異」テストは失敗します。テストをリファクタリングしても、テストは変更されません。これを回避するためにコード自体がリファクタリングされる可能性があるかどうかはわかりません。理論的には可能かもしれませんが、難しすぎるはずです。


以前に参照したコードスニペットは、完全な例ではありません。forループが2つのネストされたループ(行と列の場合のように)に置き換えられ、これらの行と列が1次元インデックスに再計算された場合でも、リファクタリングされる可能性があります。それでも、リファクタリングできないアルゴリズムを作成する方法がわかります(あまり意味はありませんが)。

インデックスの昇順で正の整数の配列を反復処理し、インデックスごとにそのペアをとして計算しi + i % a[i]、範囲外でない場合は、次の要素を交換します。

for (var i = 1; i < a.Length; i++)
{
    var j = i + i % a[i];
    if (j < a.Length)
        Swap(a[i], a[j]);
}

ここでも、a [0]は「移動不可」であり、テストをリファクタリングしてもこれは変更されず、コード自体をリファクタリングすることは事実上不可能です。


もう1つの「意味のある」例。暗黙のバイナリヒープを実装しましょう。これは通常、インデックス「1」から始まる配列に配置されます(これにより、インデックス「0」から開始する場合と比較して、多くのバイナリヒープの計算が簡素化されます)。次に、このヒープのコピーメソッドを実装します。インデックスゼロは使用されておらず、C#はすべてのアレイをゼロで初期化するため、このコピー方法の「オフバイワン」の問題は検出できません。これはOPの配列の合計に似ていますが、リファクタリングすることはできません。

厳密に言えば、クラス全体をリファクタリングして、すべてを「0」から開始できます。ただし、「コピー」メソッドまたはテストのみを変更しても、「1つだけの変更」テストの失敗を防ぐことはできません。バイナリヒープクラスは、未使用の最初の要素を含む配列をコピーする動機として扱うことができます。

int[] dst = new int[src.Length];
for (var i = 1; i < src.Length; i++)
{
    dst[i] = src[i];
}
于 2012-05-03T13:03:16.697 に答える
1

はい、私があなたの質問を理解したと仮定すると、たくさんあります。

あなたの場合に似たものは次のとおりです。

public int MultiplyTo(int max)
{
    int product = 1;
    for (var i = 1; i <= max; i++)
    {
        product *= i;
    }
    return product;
}

ここで、0から始まる場合、結果は0になりますが、1から始まる場合、結果は正しいはずです。(ただし、1と2の違いはわかりません!)。

于 2012-04-30T11:54:27.700 に答える
1

正確に何を探しているのかはよくわかりませんが、sumの初期値を0から1に変更/変更すると、テストに失敗するはずです。

public int SumTo(int max) 
{ 
  int sum = 1; // Now we are off-by-one from the beginning!
  for (var i = 0; i <= max; i++) 
  { 
    sum += i; 
  } 
  return sum; 
}

コメントに基づいて更新:

ループは、インデックス0の処理で(またはインデックスがない場合に)ループ不変条件に違反した場合にのみ、ミューテーション後に失敗しません。このような特殊なケースのほとんどは、ループの外でリファクタリングできますが、1/xの合計を考慮してください。

for (var i = 1; i <= max; i++) {
  sum += 1/i;
}

これは正常に機能しますが、最初の境界を1から0に変更すると、1/0が無効な操作であるため、テストは失敗します。

于 2012-04-30T12:21:08.223 に答える