Модульное тестирование - алгоритм или выборка? - PullRequest
8 голосов
/ 17 января 2011

Допустим, я пытаюсь протестировать простой класс Set

public IntSet : IEnumerable<int>
{
    Add(int i) {...}
    //IEnumerable implementation...
}

И предположим, что я пытаюсь проверить, что в наборе не может быть повторяющихся значений.Мой первый вариант - вставить некоторые образцы данных в набор и проверить их на наличие дубликатов, используя мои знания данных, которые я использовал, например:

    //OPTION 1
    void InsertDuplicateValues_OnlyOneInstancePerValueShouldBeInTheSet()
    {
        var set = new IntSet();

        //3 will be added 3 times
        var values = new List<int> {1, 2, 3, 3, 3, 4, 5};
        foreach (int i in values)
            set.Add(i);

        //I know 3 is the only candidate to appear multiple times
        int counter = 0;
        foreach (int i in set)
            if (i == 3) counter++;

        Assert.AreEqual(1, counter);
    }

Мой второй вариант - это общий тест на мое состояние:

    //OPTION 2
    void InsertDuplicateValues_OnlyOneInstancePerValueShouldBeInTheSet()
    {
        var set = new IntSet();

        //The following could even be a list of random numbers with a duplicate
        var values = new List<int> { 1, 2, 3, 3, 3, 4, 5};
        foreach (int i in values)
            set.Add(i);

        //I am not using my prior knowledge of the sample data 
        //the following line would work for any data
        CollectionAssert.AreEquivalent(new HashSet<int>(values), set);
    } 

Конечно, в этом примере мне удобно иметь реализацию набора для проверки, а также код для сравнения коллекций (CollectionAssert).Но что, если у меня тоже не было?Этот код определенно будет сложнее, чем в предыдущем варианте!И это ситуация, когда вы тестируете свою собственную бизнес-логику в реальной жизни.

Конечно, тестирование на ожидаемые условия обычно охватывает больше случаев - но это становится очень похоже на повторную реализацию логики (что утомительно и бесполезно - вы не можете использовать один и тот же код для проверки самого себя!).По сути, я спрашиваю, должны ли мои тесты выглядеть как «вставить 1, 2, 3, затем проверить что-то около 3» или «вставить 1, 2, 3 и проверить что-то в целом»

РЕДАКТИРОВАТЬ - Чтобы помочь мнеПоймите, пожалуйста, укажите в своем ответе, предпочитаете ли вы ВАРИАНТ 1 или ВАРИАНТ 2 (или ни того, ни другого, либо это зависит от конкретного случая и т. д.).Просто чтобы прояснить, довольно ясно, что в в этом случае (IntSet) вариант 2 лучше во всех аспектах.Однако мой вопрос относится к случаям, когда у вас нет альтернативной реализации для проверки, поэтому код в варианте 2 будет определенно более сложным, чем вариант 1.

Ответы [ 6 ]

2 голосов
/ 17 января 2011

Я обычно предпочитаю тестировать варианты использования один за другим - это хорошо работает в стиле TDD: «немного кодируй, немного тестируй».Конечно, через некоторое время мои контрольные примеры начинают содержать дублированный код, поэтому я выполняю рефакторинг.Реальный метод проверки результатов не имеет значения для меня, пока он работает точно, и не мешает самому тестированию.Так что, если есть «эталонная реализация» для тестирования, все будет лучше.

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

2 голосов
/ 17 января 2011

Если у вас есть альтернативная реализация, то обязательно используйте ее.

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

Я стараюсь использовать этот подход, когда это возможно.

Обновление: По сути, я выступаю за «Вариант № 2» ОП.При таком подходе есть только один выходной вектор, который позволит пройти тест.С «Вариантом № 1» существует бесконечное число приемлемых выходных векторов (он проверяет инвариант, но не проверяет какое-либо отношение к входным данным).

1 голос
/ 17 января 2011

В основном я спрашиваю, есть ли мои тесты должен выглядеть как «вставить 1, 2, 3 тогда проверить что-то около 3 "или" вставить 1, 2, 3 и проверить что-то в общий "

Я не пурист TDD, но кажется, что люди говорят, что тест должен прерваться, если нарушено условие, которое вы пытаетесь проверить. E.I. Если вы реализуете тест, который проверяет общее состояние, то ваш тест будет нарушен более чем в нескольких случаях, поэтому он не оптимален.

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

(Update)

ОК, теперь вы обновили код, и мне нужно обновить мой ответ.

Какой бы я выбрал? Это зависит от реализации CollectionAssert.AreEquivalent(new HashSet<int>(values), set);. Например, IEnumerable<T> сохраняет порядок, в то время как HashSet<T> нет, поэтому даже это может нарушить тест, а это не так. Для меня первый все же превосходит.

1 голос
/ 17 января 2011

Согласно xUnit Test Patterns , обычно более благоприятно тестировать состояние тестируемой системы. Если вы хотите проверить его поведение и способ работы алгоритма, вы можете использовать Mock Object Testing.

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

0 голосов
/ 17 января 2011

Я бы выбрал алгоритмический подход, но желательно, не полагаясь на альтернативную реализацию, такую ​​как HashSet.Вы на самом деле тестируете больше, чем просто «без дубликатов» с совпадением HashSet.Например, тест не пройден, если какие-либо элементы не попали в результирующий набор, и у вас, вероятно, есть другие тесты, которые проверяют это.

Может быть что-то более чистое подтверждение ожидания "нет дубликатов"как следующее:

Assert.AreEqual(values.Distinct().Count(), set.Count());
0 голосов
/ 17 января 2011

Первым шагом должна быть демонстрация правильности метода Add с использованием диаграммы / блок-схемы операций.Следующим шагом будет формально доказать правильность метода Add, если у вас есть время.Затем проведите тестирование с определенными наборами данных, где вы ожидаете дублирования и отсутствия дублирования (т.е. некоторые наборы данных имеют дублирование, а некоторые наборы не имеют, и вы видите, работает ли структура данных правильно - важно иметь случаи, которые должны быть успешными (без дубликатов) и проверить, правильно ли они были добавлены в набор, а не просто проверять наличие сбоев (случаи, в которых должны быть найдены дубликаты)).И, наконец, проверка в общем.Несмотря на то, что теперь это несколько устарело, я бы предложил создать данные, чтобы полностью использовать каждый путь выполнения в тестируемом методе.В любой момент вы внесли изменения в код, а затем начинаете все с применения регрессионного тестирования.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...