#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%). Но если вы используете какую-то систему непрерывной интеграции, я не уверен, следует ли включать в нее тесты, основанные на данных, с огромным количеством случайно сгенерированных данных. Я бы предпочел сделать отдельное развертывание, которое периодически выполняет множество случайных тестов одновременно (поэтому вероятность обнаружения чего-то плохого должна быть довольно высокой при каждом запуске). Но это слишком ресурсоемко, как часть обычного набора тестов.