Подтвердить что ты не робот

Неужели тестирование, основанное на данных, плохое?

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

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

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

Предположим, что мы имеем следующий код:

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

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

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