단위 테스트를 위해 코드를 반복해도 괜찮습니까?


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

테스트 코드는 여전히 코드이며 유지 관리해야합니다.

복사 된 논리를 변경해야하는 경우 일반적으로 복사 한 모든 위치에서이를 수행해야합니다.

드라이는 여전히 적용됩니다.

그런 다음 리팩토링을 테스트하기 위해 다른 테스트를 작성하지 않아도됩니까?

당신은? 현재 가지고있는 테스트가 올바른지 어떻게 알 수 있습니까?

테스트를 실행하여 리팩토링을 테스트하십시오. 그들은 모두 같은 결과를 가져야합니다.


바로 테스트는 코드입니다. 좋은 코드를 작성하는 것과 동일한 원칙이 여전히 적용됩니다! 테스트를 실행하여 리팩토링을 테스트하되, 적용 범위가 적절하고 테스트에서 둘 이상의 경계 조건에 도달하는지 확인하십시오 (예 : 정상 조건 대 고장 조건).
Michael

6
동의하지 않습니다. 테스트는 반드시 DRY 일 필요는 없습니다. DRY (Descriptive And Meaningful Phrases)가 DRY보다 더 중요합니다. (일반적으로, 적어도.이 특정한 경우에, 반복되는 초기화를 헬퍼로 끌어 당기는 것은 분명히 의미가 있습니다.)
Jörg W Mittag

2
DAMP는 들어 본 적이 없지만 그 설명이 마음에 듭니다.
Joachim Sauer

@ Jörg W Mittag : 테스트를 통해 DRY 및 DAMP가 될 수 있습니다. 테스트의 일부가 반복된다는 것을 알면 일반적으로 테스트의 다른 ARRANGE-ACT-ASSERT (또는 GIVEN-WHEN-THEN) 부분을 테스트 픽스처의 도우미 메소드로 리팩터링합니다. 그들은 일반적으로 같은 DAMP 이름이 givenThereAreProductsSet(amount), 심지어 단순하게를 actWith(param). 나는 유창한 api wise (예 :)로 givenThereAre(2).products()한 번 해냈 지만 과잉 인 것처럼 느꼈기 때문에 빨리 멈췄습니다.
Spoike

11

Oded가 이미 말했듯이 테스트 코드는 여전히 유지되어야합니다. 테스트 코드에서 반복을 수행하면 관리자가 테스트 구조를 이해하고 새로운 테스트를 추가하기가 더 어려워집니다.

게시 한 두 함수에서 다음 줄은 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? 내일 테스트 할 구현에서 작업 해야하는 경우 테스트는 논리를 입력하는 좋은 방법입니다. 컴파일러가 아닌 동료 프로그래머를 위해 테스트를 작성하십시오. :)

다음은 "revamped"테스트 구현입니다.

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