Тестирование на основе данных плохо? - PullRequest
11 голосов
/ 06 октября 2011

Я начал использовать 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 result;
    }
    if(size <= 256) {
        // use m.op() to compute result etc.
        return result;
    }
    // ...
}

// 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> MultFlt;
// ...
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, где f()и f_alt() дал бы другие значения с SumMethod из-за округления, которое я мог бы улучшить, предварительно отсортировав входной массив и т. д. Из этого опыта я считаю это на самом деле несколько хорошей практикой.

Ответы [ 3 ]

10 голосов
/ 08 октября 2011

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

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

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

6 голосов
/ 06 октября 2011

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

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

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

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

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

0 голосов
/ 14 октября 2011

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

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

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

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