Apakah boleh mengulang kode untuk tes unit?

11

Saya menulis beberapa algoritma penyortiran untuk tugas kelas dan saya juga menulis beberapa tes untuk memastikan algoritma diimplementasikan dengan benar. Tes saya hanya seperti 10 baris panjang dan ada 3 dari mereka tetapi hanya 1 perubahan baris antara 3 sehingga ada banyak kode yang diulang. Apakah lebih baik untuk memperbaiki kode ini menjadi metode lain yang kemudian dipanggil dari setiap tes? Tidakkah saya kemudian perlu menulis tes lain untuk menguji refactoring? Beberapa variabel bahkan dapat dipindahkan ke level kelas. Haruskah pengujian kelas dan metode mengikuti aturan yang sama dengan kelas / metode biasa?

Ini sebuah contoh:

    [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); 
    }
Pete
sumber

Jawaban:

21

Kode uji masih berupa kode dan juga perlu dipertahankan.

Jika Anda perlu mengubah logika yang disalin, Anda harus melakukannya di setiap tempat Anda menyalinnya, secara normal.

KERING masih berlaku.

Tidakkah saya kemudian perlu menulis tes lain untuk menguji refactoring?

Maukah kamu? Dan bagaimana Anda tahu tes yang Anda miliki saat ini benar?

Anda menguji refactoring dengan menjalankan tes. Mereka semua harus memiliki hasil yang sama.

Oded
sumber
Tepat. Tes adalah kode - semua prinsip yang sama untuk menulis kode yang baik masih berlaku! Tes refactoring dengan menjalankan tes, tetapi pastikan bahwa ada cakupan yang memadai dan bahwa Anda memukul lebih dari satu kondisi batas dalam tes Anda (misalnya kondisi normal vs kondisi kegagalan).
Michael
6
Saya tidak setuju. Tes tidak harus harus KERING, lebih penting bagi mereka untuk menjadi DAMP (Frasa Deskriptif dan Bermakna) daripada KERING. (Secara umum, setidaknya. Dalam kasus khusus ini, menarik keluar inisialisasi berulang menjadi penolong jelas masuk akal.)
Jörg W Mittag
2
Saya belum pernah mendengar DAMP sebelumnya, tapi saya suka deskripsi itu.
Joachim Sauer
@ Jörg W Mittag: Anda masih bisa KERING dan basah dengan tes. Saya biasanya memperbaiki bagian ARRANGE-ACT-ASSERT (atau GIVEN-WHEN-THEN) yang berbeda dari tes untuk membantu metode dalam fixture tes jika saya tahu bahwa beberapa bagian dari tes berulang. Mereka biasanya memiliki nama DAMP, seperti givenThereAreProductsSet(amount)dan bahkan sesederhana actWith(param). Saya berhasil melakukannya dengan api fasih bijak (misalnya givenThereAre(2).products()) sekali, tetapi saya dengan cepat berhenti karena rasanya seperti berlebihan.
Spoike
11

Seperti yang sudah dikatakan Oded, kode tes masih perlu dipertahankan. Saya akan menambahkan bahwa pengulangan dalam kode uji mempersulit pengelola untuk memahami struktur tes, dan menambahkan tes baru.

Di dua fungsi yang Anda poskan, baris berikut ini benar-benar identik kecuali satu perbedaan ruang pada awal forloop:

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

Ini akan menjadi kandidat yang sempurna untuk pindah ke semacam fungsi pembantu, yang namanya menunjukkan bahwa itu adalah inisialisasi data.

Clare Macrae
sumber
4

Tidak, ini tidak baik. Anda harus menggunakan TestDataBuilder sebagai gantinya. Anda juga harus memperhatikan keterbacaan tes Anda: a? 1000? b? Jika besok kita harus mengerjakan implementasi yang Anda uji, tes adalah cara yang bagus untuk memasukkan logika: tulis tes Anda untuk Anda sesama programmer, bukan untuk kompiler :)

Berikut adalah implementasi tes Anda, "dirubah":

/**
* 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 ...
    }
}
Olivier
sumber
2

Bahkan lebih dari kode produksi, kode uji perlu dioptimalkan untuk keterbacaan dan pemeliharaan, karena harus dipertahankan sepanjang kode yang diuji dan juga dibaca sebagai bagian dari dokumentasi. Pertimbangkan bagaimana kode yang disalin dapat membuat pemeliharaan kode tes lebih sulit dan bagaimana hal itu dapat menjadi insentif untuk tidak menulis tes untuk semuanya. Juga, jangan lupa bahwa ketika Anda menulis fungsi untuk KERING tes Anda, itu juga harus diuji.

kasar
sumber
2

Kode duplikat untuk tes adalah jebakan yang mudah. Tentu itu nyaman, tetapi apa yang terjadi jika Anda mulai memperbaiki kode implementasi Anda dan semua pengujian Anda perlu diubah? Anda menjalankan risiko yang sama dengan yang Anda lakukan jika Anda menduplikasi kode implementasi Anda, karena kemungkinan besar Anda perlu mengubah kode pengujian Anda di banyak tempat juga. Ini semua menambah banyak waktu yang terbuang, dan semakin banyak poin kegagalan yang perlu ditangani, yang berarti bahwa biaya untuk mempertahankan perangkat lunak Anda menjadi sangat tinggi, dan karenanya mengurangi nilai bisnis keseluruhan dari perangkat lunak yang Anda gunakan. mengerjakan.

Pertimbangkan juga bahwa apa yang mudah dilakukan dalam tes akan menjadi mudah dilakukan dalam implementasi. Ketika Anda ditekan untuk waktu dan di bawah banyak stres, orang cenderung mengandalkan pola perilaku yang dipelajari dan umumnya mencoba dan melakukan apa yang tampaknya paling mudah pada saat itu. Jadi, jika Anda menemukan Anda memotong dan menempel banyak kode tes Anda, kemungkinan Anda akan melakukan hal yang sama dalam kode implementasi Anda, dan ini adalah kebiasaan yang ingin Anda hindari sejak awal dalam karir Anda, untuk menyelamatkan Anda banyak Kesulitan di kemudian hari ketika Anda harus mempertahankan kode lama yang Anda tulis, dan bahwa perusahaan Anda tidak dapat selalu mampu untuk menulis ulang.

Seperti yang orang lain katakan, Anda menerapkan prinsip KERING, dan Anda mencari peluang untuk memperbaiki kemungkinan duplikasi untuk metode helper dan kelas helper, dan ya, Anda bahkan harus melakukan ini dalam pengujian Anda untuk memaksimalkan penggunaan kembali kode dan menyimpan diri Anda menghadapi kesulitan dengan pemeliharaan nanti. Anda bahkan mungkin menemukan diri Anda secara perlahan mengembangkan API pengujian yang dapat Anda gunakan berulang-ulang, bahkan mungkin dalam banyak proyek - tentu saja itulah yang terjadi pada saya selama beberapa tahun terakhir.

S.Robins
sumber