web-dev-qa-db-ja.com

単体テストのコードを繰り返しても大丈夫ですか?

クラスの割り当てのためにいくつかの並べ替えアルゴリズムを作成し、アルゴリズムが正しく実装されていることを確認するためにいくつかのテストも作成しました。私のテストは長さが10行程度で3行ありますが、3行の間は1行しか変更されないため、コードの繰り返しがたくさんあります。このコードを別のメソッドにリファクタリングしてから、各テストから呼び出される方が良いですか?その後、リファクタリングをテストするために別のテストを作成する必要はありませんか?一部の変数は、クラスレベルに移動することもできます。クラスとメソッドのテストは、通常のクラス/メソッドと同じルールに従う必要がありますか?

次に例を示します。

    [TestMethod]
    public void MergeSortAssertArrayIsSorted()
    {
        int[] a = new int[1000];
        Random Rand = new Random(DateTime.Now.Millisecond);
        for(int i = 0; i < a.Length; i++)
        {
            a[i] = Rand.Next(Int16.MaxValue);
        }
        int[] b = new int[1000];
        a.CopyTo(b, 0);
        List<int> temp = b.ToList();
        temp.Sort();
        b = temp.ToArray();

        MergeSort merge = new MergeSort();
        merge.mergeSort(a, 0, a.Length - 1);
        CollectionAssert.AreEqual(a, b);
    }
    [TestMethod]
    public void InsertionSortAssertArrayIsSorted()
    {
        int[] a = new int[1000];
        Random Rand = new Random(DateTime.Now.Millisecond);
        for (int i = 0; i < a.Length; i++)
        {
            a[i] = Rand.Next(Int16.MaxValue);
        }
        int[] b = new int[1000];
        a.CopyTo(b, 0);
        List<int> temp = b.ToList();
        temp.Sort();
        b = temp.ToArray();

        InsertionSort merge = new InsertionSort();
        merge.insertionSort(a);
        CollectionAssert.AreEqual(a, b); 
    }
11
Pete

テストコードはまだコードであり、維持する必要もあります。

コピーしたロジックを変更する必要がある場合は、通常、コピーしたすべての場所で行う必要があります。

[〜#〜] dry [〜#〜] は引き続き適用されます。

その後、リファクタリングをテストするために別のテストを作成する必要はありませんか?

しますか?また、現在行っているテストが正しいことをどのようにして確認しますか?

テストを実行してリファクタリングをテストします。それらはすべて同じ結果になるはずです。

21
Oded

Odedが既に述べたように、テストコードはまだ維持する必要があります。テストコードで繰り返しを使用すると、メンテナがテストの構造を理解し、新しいテストを追加することが困難になることを付け加えておきます。

投稿した2つの関数では、forループの開始時の1つのスペースの違いを除いて、次の行はまったく同じです。

        int[] a = new int[1000];
        Random Rand = new Random(DateTime.Now.Millisecond);
        for (int i = 0; i < a.Length; i++)
        {
            a[i] = Rand.Next(Int16.MaxValue);
        }
        int[] b = new int[1000];
        a.CopyTo(b, 0);
        List<int> temp = b.ToList();
        temp.Sort();
        b = temp.ToArray();

これは、名前がデータを初期化していることを示す名前のようなヘルパー関数に移動するのに最適な候補です。

11
Clare Macrae

いいえ、大丈夫ではありません。代わりに TestDataBuilder を使用してください。テストの読みやすさにも注意する必要があります。 1000? b?明日、テストしている実装に取り​​組む必要がある場合、テストはロジックを入力するための優れた方法です。コンパイラではなく、仲間のプログラマのためにテストを作成してください:)

「実装済み」のテスト実装は次のとおりです。

/**
* Data your tests will exercice on
*/
public class MyTestData(){
    final int [] values;
    public MyTestData(int sampleSize){
        values = new int[sampleSize];
        //Out of scope of your question : Random IS a depencency you should manage
        Random Rand = new Random(DateTime.Now.Millisecond);
        for (int i = 0; i < a.Length; i++)
        {
            a[i] = Rand.Next(Int16.MaxValue);
        }
    }
    public int [] values();
        return values;
    }

}

/**
* Data builder, with default value. 
*/
public class MyTestDataBuilder {
    //1000 is actually your sample size : emphasis on the variable name
    private int sampleSize = 1000; //default value of the sample zie
    public MyTestDataBuilder(){
        //nope
    }
    //this is method if you need to test with another sample size
    public MyTestDataBuilder withSampleSizeOf(int size){
        sampleSize=size;
    }

    //call to get an actual MyTestData instance
    public MyTestData build(){
        return new MyTestData(sampleSize);
    }
}

public class MergeSortTest { 

    /**
    * Helper method build your expected data
    */
    private int [] getExpectedData(int [] source){
        int[] expectedData =  Arrays.copyOf(source,source.length);
        Arrays.sort(expectedData);
        return expectedData;
    }
}

//revamped tests method Merge
    public void MergeSortAssertArrayIsSorted(){
        int [] actualData = new MyTestDataBuilder().build();
        int [] expected = getExpectedData(actualData);
        //Don't know what 0 is for. An option, that should have a explicit name for sure :)
        MergeSort merge = new MergeSort();
        merge.mergeSort(actualData,0,actualData.length-1); 
        CollectionAssert.AreEqual(actualData, expected);
    }

 //revamped tests method Insertion
 public void InsertionSortAssertArrayIsSorted()
    {
        int [] actualData = new MyTestDataBuilder().build();
        int [] expected = getExpectedData(actualData);
        InsertionSort merge = new InsertionSort();
        merge.insertionSort(actualData);
        CollectionAssert.AreEqual(actualData, expectedData); 
    }
//another Test, for which very small sample size matter
public void doNotCrashesWithEmptyArray()
    {
        int [] actualData = new MyTestDataBuilder().withSampleSizeOf(0).build();
        int [] expected = getExpectedData(actualData);
        //continue ...
    }
}
4
Olivier

テスト用のコードを複製することは簡単に陥ります。確かに便利ですが、実装コードのリファクタリングを開始し、すべてのテストで変更が必要になった場合はどうなりますか?実装コードを複製した場合と同じリスクが発生します。多くの場合、テストコードを変更する必要もあるでしょう。これはすべて、膨大な時間の浪費と、対処する必要のある障害ポイントの増加につながります。つまり、ソフトウェアを維持するためのコストが不必要に高くなり、その結果、ソフトウェアの全体的なビジネス価値が低下します。取り組みます。

また、テストで簡単にできることは、実装でも簡単になることを考慮してください。時間とストレスがかかると、人々は習得した行動パターンに頼り、一般的には、そのときに最も簡単に思われることをしようとする傾向があります。したがって、テストコードの多くをカットアンドペーストする場合は、実装コードでも同じことを行う可能性が高く、これは多くの人を救うために、キャリアの早い段階で避けたい習慣です。後で自分が書いた古いコードを維持する必要があり、会社が必ずしも書き直す余裕がない場合に困難になります。

他の人が言ったように、あなたはDRYプリンシパルを適用し、ヘルパーメソッドとヘルパークラスへの可能性のある重複をリファクタリングする機会を探します、そしてもちろん、あなたはテストでこれを行うべきですコードの再利用を最大化し、後でメンテナンスで困難に直面するのを防ぐために。おそらく、複数のプロジェクトでさえ、何度も何度も何度も使用できるテストAPIをゆっくりと開発していることさえあるかもしれません。過去数年間の私。

2
S.Robins

テストコードは、テスト対象のコードに沿って維持し、ドキュメントの一部として読み取る必要があるため、本番用コードだけでなく、可読性と保守性のために最適化する必要があります。コピーされたコードがどのようにテストコードのメンテナンスを困難にするか、それがすべてのテストを記述しないインセンティブになるかを検討してください。また、テストをDRYする関数を記述する場合、テストの対象となることも忘れないでください。

2
rbanffy