Является ли тестирование, основанное на данных, плохим?

#c #testing #googletest #data-driven

#c #тестирование #googletest #управляемое данными

Вопрос:

Я начал использовать googletest для реализации тестов и наткнулся на эту цитату в документации, касающейся тестов с параметризацией значений

  • Вы хотите протестировать свой код на различных входных данных (также известное как тестирование на основе данных). Этой функцией легко злоупотребить, поэтому, пожалуйста, проявите свой здравый смысл при ее выполнении!

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

Предположим, у нас есть следующий код:

 template<typename T>
struct SumMethod {
     T op(T x, T y) { return x   y; }   
};

// optimized function to handle different input array sizes 
// in the most efficient way
template<typename T, class Method> 
T f(T input[], int size) {
    Method m;
    T result = (T) 0;
    if(size <= 128) {
        // use m.op() to compute result etc.
        return resu<
    }
    if(size <= 256) {
        // use m.op() to compute result etc.
        return resu<
    }
    // ...
}

// naive and correct, but slow alternative implementation of f()
template<typename T, class Method>
T f_alt(T input[], int size);
  

Хорошо, итак, с этим кодом, безусловно, имеет смысл протестировать f() (путем сравнения с f_alt() ) с различными размерами входного массива случайно сгенерированных данных, чтобы проверить правильность ветвей. Кроме того, у меня есть несколько structs подобных SumMethod , MultiplyMethod , и т.д., Поэтому я провожу довольно большое количество тестов и для разных типов:

 typedef MultiplyMethod<int> MultInt;
typedef SumMethod<int> SumInt;
typedef MultiplyMethod<float> MultF<
// ...
ASSERT(f<int, MultInt>(int_in, 128), f_alt<int, MultInt>(int_in, 128));
ASSERT(f<int, MultInt>(int_in, 256), f_alt<int, MultInt>(int_in, 256));
// ...
ASSERT(f<int, SumInt>(int_in, 128), f_alt<int, SumInt>(int_in, 128));
ASSERT(f<int, SumInt>(int_in, 256), f_alt<int, SumInt>(int_in, 256));
// ...
const float ep = 1e-6;
ASSERT_NEAR(f<float, MultFlt>(flt_in, 128), f_alt<float, MultFlt>(flt_in, 128), ep);
ASSERT_NEAR(f<float, MultFlt>(flt_in, 256), f_alt<float, MultFlt>(flt_in, 256), ep);
// ...
  

Теперь, конечно, мой вопрос: имеет ли это какой-то смысл и почему это плохо?

На самом деле, я обнаружил «ошибку» при выполнении тестов с float s where f() и f_alt() выдавал бы разные значения из SumMethod -за округления, которые я мог бы улучшить, предварительно отсортировав входной массив и т. Д.. Исходя из этого опыта, я считаю, что это действительно хорошая практика.

Ответ №1:

Я думаю, что основной проблемой является тестирование с «случайно сгенерированными данными». Из вашего вопроса неясно, генерируются ли эти данные повторно при каждом запуске вашего тестового жгута. Если это так, то результаты вашего теста не воспроизводимы. Если какой-то тест завершается неудачей, он должен завершаться неудачей при каждом его запуске, а не один раз в blue moon, при какой-то странной случайной комбинации тестовых данных.

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

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

Комментарии:

1. Случайно сгенерированные данные плохи по двум причинам: во-первых, потому что, как вы упомянули, тесты не воспроизводимы. И, во-вторых, потому что угловые случаи могут не покрываться случайно сгенерированными данными. Сохранение случайных векторов ничего не делает для второго недостатка.

2. @haimg — Если вы проводите тестирование черного ящика, откуда вы знаете используемый алгоритм и его угловые случаи? 🙂

3. Ну, может быть, я неправильно понимаю исходный вопрос, но там нет ничего, что говорило бы о том, что тестирование не должно использовать знания о реализации. Более того, en.wikipedia.org/wiki/Black_box_testing в частности, говорится, что «Элементы на краю домена выбираются и тестируются», что, по сути, является тестированием угловых случаев.

4. 1 за «дополнительное тестирование со случайными данными также полезно, когда / если вы не уверены, что знаете все свои угловые случаи» недавно у нас возникла проблема с неопознанным угловым случаем, и я добавил несколько (псевдо) случайных тестовых случаев, чтобы попытаться обнаружить любые дополнительные неопознанные угловые случаи.

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

Ответ №2:

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

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

Я думаю, что я действительно «злоупотребляю» системой, когда выполняю следующее

Вы думали, что это плохо, прежде чем прочитать эту статью? Можете ли вы сформулировать, что в этом плохого?

Вы должны когда-нибудь протестировать эту функциональность. Для этого вам нужны данные. Где злоупотребление?

Комментарии:

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

Ответ №3:

Одна из причин, по которой это может быть плохо, заключается в том, что тесты, управляемые данными, сложнее поддерживать, и в течение более длительного периода времени легче вносить ошибки в сами тесты. Подробности смотрите здесь: http://googletesting.blogspot.com/2008/09/tott-data-driven-traps.html

Также, с моей точки зрения, unittests наиболее полезны, когда вы проводите серьезный рефакторинг и не уверены, не изменили ли вы логику неправильно. Если ваш тест с случайными данными завершится неудачей после таких изменений, то вы можете догадаться: это из-за данных или из-за ваших изменений?

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