Можно ли повторять код для модульных тестов?

11

Я написал несколько алгоритмов сортировки для назначения класса, а также написал несколько тестов, чтобы убедиться, что алгоритмы были реализованы правильно. Мои тесты состоят всего из 10 строк, и их 3, но только 3 строки меняются, поэтому повторяется много кода. Что лучше сделать этот код в другой метод, который затем вызывается из каждого теста? Не нужно ли мне тогда написать еще один тест для проверки рефакторинга? Некоторые из переменных могут быть даже перемещены на уровень класса. Должны ли классы и методы тестирования следовать тем же правилам, что и обычные классы / методы?

Вот пример:

    [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

Тестовый код все еще является кодом, и его также необходимо поддерживать.

Если вам нужно изменить скопированную логику, вы должны делать это в любом месте, куда вы ее скопировали, обычно.

СУХОЙ по- прежнему применяется.

Не нужно ли мне тогда написать еще один тест для проверки рефакторинга?

Не могли бы вы? И как вы узнаете, что тесты, которые у вас сейчас есть, верны?

Вы проверяете рефакторинг, выполняя тесты. Все они должны иметь одинаковые результаты.

Одед
источник
Право на. Тесты - это код - все те же принципы написания хорошего кода все еще применяются! Протестируйте рефакторинг, запустив тесты, но убедитесь, что имеется достаточный охват и что вы используете более одного граничного условия в ваших тестах (например, нормальное состояние или условие отказа).
Майкл
6
Я не согласен. Тесты не обязательно должны быть СУХИМЫМИ, для них важнее быть DAMP (описательные и осмысленные фразы), чем DRY. (В общем, по крайней мере. В этом конкретном случае, однако, вытащить повторную инициализацию в помощника определенно имеет смысл.)
Йорг Миттаг
2
Я никогда не слышал DAMP раньше, но мне нравится это описание.
Иоахим Зауэр
@ Йорг W Mittag: Вы все еще можете быть СУХОЙ и DAMP с тестами. Я обычно делаю рефакторинг различных частей теста ARRANGE-ACT-ASSERT (или GIVEN-WHEN-THEN) для вспомогательных методов в тестовом приспособлении, если я знаю, что некоторая часть теста повторяется. Они обычно имеют имена DAMP, такие как givenThereAreProductsSet(amount)и даже такие простые, как actWith(param). Мне удалось сделать это с хорошим поведением (например givenThereAre(2).products()) один раз, но я быстро остановился, потому что это было похоже на излишество.
Спойк
11

Как уже сказал Одед, тестовый код еще нуждается в поддержке. Я бы добавил, что повторение в тестовом коде усложняет сопровождающим понимать структуру тестов и добавлять новые тесты.

В двух опубликованных вами функциях следующие строки абсолютно идентичны, за исключением одной разницы в начале 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 вместо этого. Вам также следует позаботиться о читабельности ваших тестов: a? 1000? б? Если завтра нужно работать над реализацией, которую вы тестируете, тесты - отличный способ ввести логику: пишите свои тесты для своих коллег-программистов, а не для компилятора :)

Вот ваши тесты реализации, "обновленные":

/**
* 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 ваших тестов, она также должна подвергаться тестам.

rbanffy
источник
2

Дублирование кода для тестов - это простая ловушка, в которую можно попасть. Конечно, это удобно, но что произойдет, если вы начнете проводить рефакторинг своего кода реализации и все ваши тесты начнут нуждаться в изменении? Вы рискуете теми же рисками, что и в случае дублирования кода реализации, поскольку вам, скорее всего, потребуется также изменить тестовый код во многих местах. Все это приводит к значительным потерям времени и увеличению количества точек сбоя, которые необходимо устранить, что означает, что затраты на обслуживание вашего программного обеспечения становятся неоправданно высокими, и, следовательно, снижает общую ценность программного обеспечения, которое вы используете. работа над.

Учтите также, что то, что легко сделать в тестах, станет легким в реализации. Когда вы испытываете нехватку времени и испытываете сильный стресс, люди склонны полагаться на усвоенные модели поведения и, как правило, стараются делать то, что кажется легким в то время. Итак, если вы обнаружите, что вы вырезали и вставили большую часть своего тестового кода, вероятно, вы будете делать то же самое в своем коде реализации, и это привычка, которую вы хотите избежать в начале своей карьеры, чтобы сэкономить вам много позже возникнут трудности, когда вы обнаружите, что вам нужно поддерживать старый код, который вы написали, и что ваша компания не может позволить себе переписать его.

Как уже говорили другие, вы применяете принцип DRY и ищите возможности для рефакторинга любых возможных дубликатов вспомогательных методов и классов помощников, и да, вы должны даже делать это в своих тестах, чтобы максимизировать повторное использование кода и сохранить позже вы столкнетесь с трудностями в обслуживании. Возможно, вы даже медленно разрабатываете API тестирования, который можете использовать снова и снова, возможно, даже в нескольких проектах - конечно, именно так все и происходило со мной в течение последних нескольких лет.

S.Robins
источник