Có thể lặp lại mã cho các bài kiểm tra đơn vị?


11

Tôi đã viết một số thuật toán sắp xếp cho một bài tập lớp và tôi cũng đã viết một vài bài kiểm tra để đảm bảo các thuật toán được thực hiện chính xác. Các thử nghiệm của tôi chỉ dài như 10 dòng và có 3 trong số chúng nhưng chỉ có 1 dòng thay đổi giữa 3 nên có rất nhiều mã được lặp lại. Có tốt hơn để tái cấu trúc mã này thành một phương thức khác sau đó được gọi từ mỗi thử nghiệm không? Sau đó tôi có cần viết một bài kiểm tra khác để kiểm tra tái cấu trúc không? Một số biến thậm chí có thể được chuyển lên cấp độ lớp. Các lớp và phương thức kiểm thử có nên tuân theo các quy tắc giống như các lớp / phương thức thông thường không?

Đây là một ví dụ:

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

Câu trả lời:


21

Mã kiểm tra vẫn là mã và cũng cần được duy trì.

Nếu bạn cần thay đổi logic đã sao chép, bạn cần phải làm điều đó ở mọi nơi bạn đã sao chép nó, bình thường.

DRY vẫn được áp dụng.

Sau đó tôi có cần viết một bài kiểm tra khác để kiểm tra tái cấu trúc không?

Bạn sẽ Và làm thế nào để bạn biết các bài kiểm tra bạn hiện có là chính xác?

Bạn kiểm tra tái cấu trúc bằng cách chạy các bài kiểm tra. Tất cả họ nên có kết quả như nhau.


Ngay trên. Các thử nghiệm là mã - tất cả các nguyên tắc tương tự để viết mã tốt vẫn được áp dụng! Kiểm tra tái cấu trúc bằng cách chạy thử nghiệm, nhưng hãy chắc chắn rằng có phạm vi bảo hiểm đầy đủ và bạn đang đạt nhiều hơn một điều kiện biên trong các thử nghiệm (ví dụ: điều kiện bình thường so với điều kiện thất bại).
Michael

6
Tôi không đồng ý. Các xét nghiệm không nhất thiết phải là DRY, điều quan trọng hơn đối với chúng là DAMP (Cụm từ mô tả và có ý nghĩa) so với DRY. (Nói chung, ít nhất. Trong trường hợp cụ thể này, rút ​​ra việc khởi tạo lặp đi lặp lại vào một người trợ giúp chắc chắn có ý nghĩa.)
Jörg W Mittag

2
Tôi chưa bao giờ nghe DAMP trước đây, nhưng tôi thích mô tả đó.
Joachim Sauer

@ Jörg W Mittag: Bạn vẫn có thể DRY và DAMP với các bài kiểm tra. Tôi thường cấu trúc lại các phần ARRANGE-ACT-ASSERT (hoặc GIVEN-WHEN-THEN) khác nhau của thử nghiệm thành các phương thức trợ giúp trong thiết bị thử nghiệm nếu tôi biết rằng một phần của thử nghiệm lặp lại. Họ thường có tên DAMP, chẳng hạn như givenThereAreProductsSet(amount)và thậm chí đơn giản như actWith(param). Tôi đã xoay sở để làm điều đó với api thông thạo (ví dụ givenThereAre(2).products()) một lần, nhưng tôi nhanh chóng dừng lại vì cảm thấy nó quá mức cần thiết.
Spoike

11

Như Oded đã nói, mã kiểm tra vẫn cần được duy trì. Tôi đã thêm rằng sự lặp lại trong mã kiểm tra khiến cho các nhà bảo trì khó hiểu hơn về cấu trúc của các thử nghiệm và thêm các thử nghiệm mới.

Trong hai chức năng bạn đã đăng, các dòng sau hoàn toàn giống nhau ngoại trừ một khác biệt về không gian ở đầu forvòng lặp:

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

Đây sẽ là một ứng cử viên hoàn hảo để chuyển sang một loại chức năng của trình trợ giúp, có tên cho biết đó là dữ liệu khởi tạo.


4

Không ổn. Bạn nên sử dụng TestDataBuilder thay thế. Bạn cũng nên quan tâm đến khả năng đọc các bài kiểm tra của bạn: a? 1000? b? Nếu ngày mai người ta phải làm việc với việc triển khai mà bạn đang kiểm tra, các bài kiểm tra là một cách tuyệt vời để nhập logic: viết bài kiểm tra của bạn cho các lập trình viên đồng nghiệp, không phải cho trình biên dịch :)

Dưới đây là bài kiểm tra của bạn, "tân trang":

/**
* 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

Thậm chí nhiều hơn mã sản xuất, mã kiểm tra cần phải được tối ưu hóa cho khả năng đọc và bảo trì, vì nó phải được duy trì dọc theo mã đang được kiểm tra và cũng được đọc như một phần của tài liệu. Xem xét làm thế nào mã được sao chép có thể làm cho việc bảo trì mã kiểm tra khó khăn hơn và làm thế nào điều đó có thể trở thành một động lực để không viết các bài kiểm tra cho mọi thứ. Ngoài ra, đừng quên rằng khi bạn viết một hàm để DRY các bài kiểm tra của mình, thì nó cũng phải chịu các bài kiểm tra.


2

Mã trùng lặp cho các bài kiểm tra là một cái bẫy dễ rơi vào. Chắc chắn nó thuận tiện, nhưng điều gì xảy ra nếu bạn bắt đầu cấu trúc lại mã thực hiện và tất cả các thử nghiệm của bạn bắt đầu cần thay đổi? Bạn cũng gặp rủi ro giống như khi bạn sao chép mã triển khai của mình, trong đó rất có thể bạn cũng cần thay đổi mã kiểm tra của mình ở nhiều nơi. Tất cả điều này làm tăng thêm rất nhiều thời gian lãng phí và ngày càng có nhiều điểm thất bại cần phải xử lý, điều đó có nghĩa là chi phí để duy trì phần mềm của bạn trở nên cao không cần thiết và do đó làm giảm giá trị kinh doanh chung của phần mềm bạn làm việc trên

Cũng xem xét rằng những gì dễ làm trong các bài kiểm tra sẽ trở nên dễ thực hiện trong quá trình thực hiện. Khi bạn bị ép thời gian và chịu nhiều căng thẳng, mọi người có xu hướng dựa vào các kiểu hành vi đã học và thường cố gắng và làm những gì có vẻ dễ nhất vào thời điểm đó. Vì vậy, nếu bạn thấy bạn cắt và dán nhiều mã kiểm tra, có thể bạn sẽ làm tương tự trong mã thực hiện của mình và đây là thói quen bạn muốn tránh sớm trong sự nghiệp, để tiết kiệm cho bạn rất nhiều về khó khăn sau này khi bạn thấy mình phải duy trì mã cũ hơn mà bạn đã viết và công ty của bạn không nhất thiết đủ khả năng để viết lại.

Như những người khác đã nói, bạn áp dụng hiệu trưởng DRY và bạn tìm kiếm cơ hội để cấu trúc lại bất kỳ sự trùng lặp nào có khả năng cho các phương thức của trình trợ giúp và các lớp của trình trợ giúp, và vâng, bạn thậm chí nên thực hiện điều này trong các thử nghiệm của mình để tối đa hóa việc sử dụng lại mã và lưu bản thân phải đối mặt với khó khăn với bảo trì sau này. Bạn thậm chí có thể thấy mình chậm phát triển API thử nghiệm mà bạn có thể sử dụng nhiều lần, thậm chí có thể trong nhiều dự án - chắc chắn đó là cách mọi thứ đã xảy ra với tôi trong vài năm qua.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.