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


11

クラス割り当て用のソートアルゴリズムをいくつか作成し、アルゴリズムが正しく実装されていることを確認するためのテストもいくつか作成しました。私のテストの長さは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); 
    }

回答:


21

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

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

DRYは引き続き適用されます。

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

しますか?そして、あなたが現在持っているテストが正しいことをどうやって知るのですか?

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


右に。テストはコードです-良いコードを書くためのすべての同じ原則がまだ適用されます!テストを実行してリファクタリングをテストしますが、十分なカバレッジがあり、テストで複数の境界条件(たとえば、通常の条件と失敗の条件)にヒットしていることを確認してください。
マイケル

6
同意しません。テストは必ずしもDRYである必要はありません。DRYよりもDAMP(記述的で意味のあるフレーズ)であることがより重要です。(一般に、少なくとも。この特定のケースでは、繰り返し初期化をヘルパーに引き出すことは間違いなく理にかなっています。)
ヨルグWミットタグ

2
DAMPを聞いたことがありませんが、その説明は気に入っています。
ヨアヒムザウアー

@JörgW Mittag:まだテストでDRYとDAMPになれます。通常、テストの一部が繰り返されることがわかっている場合、テストのさまざまなARRANGE-ACT-ASSERT(またはGIVEN-WHEN-THEN)部分をテストフィクスチャのヘルパーメソッドにリファクタリングします。通常、DAMPの名前は、などのgivenThereAreProductsSet(amount)単純なものでさえありactWith(param)ます。私はgivenThereAre(2).products()一度流なAPI(例えば)でそれをやったことがありましたが、やり過ぎのように感じたのですぐに止めました。
スポーク

11

Odedがすでに言ったように、テストコードを維持する必要があります。テストコードを繰り返すと、メンテナーがテストの構造を理解し、新しいテストを追加するのが難しくなります。

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

        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();

これは、データを初期化することを名前が示す何らかのヘルパー関数に移行するための完璧な候補です。


4

いいえ、大丈夫ではありません。代わりに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 ...
    }
}

2

テストコードはテスト対象のコードに沿って維持する必要があり、ドキュメントの一部として読む必要があるため、量産コードよりもさらに読みやすさと保守性のために最適化する必要があります。コピーされたコードがどのようにテストコードのメンテナンスを難しくし、それがすべてのテストを作成しないインセンティブになるかを検討してください。また、テストをDRYする関数を作成するときは、テストの対象となることも忘れないでください。


2

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.