Для функции
binary_search
можно себе представить следующие виды ошибок.
• Функция ничего не возвращает (например, из-за бесконечного цикла).
• Сбой (например, неправильное разыменование, бесконечная рекурсия).
• Значение не найдено, несмотря на то, что оно находится в указанной последовательности.
• Значение найдено, несмотря на то, что оно не находится в указанной последовательности.
Кроме того, необходимо помнить о следующих возможностях для пользовательских ошибок.
• Последовательность не упорядочена (например,
{2,1,5,–7,2,10}
).
• Последовательность не корректна (например,
binary_search(&a[100],&a[50],77)
).
Какую ошибку (с точки зрения тестировщиков) может сделать программист, создающий реализацию функции, при простом вызове функции
binary_search(p1,p2,v)
? Ошибки часто возникают в особых ситуациях. В частности, при анализе последовательностей (любого вида) мы всегда ищем их начало и конец. Кроме того, всегда следует проверять, не пуста ли последовательность. Рассмотрим несколько массивов целых чисел, которые упорядочены так, как требуется.
{ 1,2,3,5,8,13,21 } // "обычная последовательность"
{ }
{ 1 } // только один элемент
{ 1,2,3,4 } // четное количество элементов
{ 1,2,3,4,5 } // нечетное количество элементов
{ 1, 1, 1, 1, 1, 1, 1 } // все элементы равны друг другу
{ 0,1,1,1,1,1,1,1,1,1,1,1,1 } // другой элемент в начале
{ 0,0,0,0,0,0,0,0,0,0,0,0,0,1 } // другой элемент в конце
Некоторые тестовые последовательности лучше генерировать программой.
•
vector<int> v1;
// очень длинная последовательность
for (int i=0; i<100000000; ++i) v.push_back(i);
• Последовательности со случайным количеством элементов.
• Последовательности со случайными элементами (по-прежнему упорядоченные).
И все же этот тест не настолько систематический, насколько нам бы хотелось. Как-никак, мы просто выискали несколько последовательностей. Однако мы следовали некоторым правилам, которые часто полезны при работе с множествами значений; перечислим их.
• Пустое множество.
• Небольшие множества.
• Большие множества.
• Множества с экстремальным распределением.
• Множества, в конце которых происходит нечто интересное.
• Множества с дубликатами.
• Множества с четным и нечетным количеством элементов.
• Множества, сгенерированные с помощью случайных чисел.
Мы используем случайные последовательности просто для того, чтобы увидеть, повезет ли нам найти неожиданную ошибку. Этот подход носит слишком “лобовой” характер, но с точки зрения времени он очень экономный.
Почему мы рассматриваем четное и нечетное количество элементов? Дело в том, что многие алгоритмы разделяют входные последовательности на части, например на две половины, а программист может учесть только нечетное или только четное количество элементов. В принципе, если последовательность разделяется на части, то точка, в которой это происходит, становится концом подпоследовательности, а, как известно, многие ошибки возникают в конце последовательностей.
В целом мы ищем следующие условия.
• Экстремальные ситуации (большие или маленькие последовательности, странные распределения входных данных и т.п.).
• Граничные условия (все, что происходит в окрестности границы).
Реальный смысл этих понятий зависит от конкретной тестируемой программы.
26.3.2.2. Схема простого теста
Существуют две категории тестов: тесты, которые должны пройти успешно (например, поиск значения, которое есть в последовательности), и тесты, которые должны завершиться неудачей (например, поиск значения в пустой последовательности). Создадим для каждой из приведенных выше последовательностей несколько успешных и неудачных тестов. Начнем с простейшего и наиболее очевидного теста, а затем станем его постепенно уточнять, пока не дойдем до уровня, приемлемого для функции
binary_search
.
int a[] = { 1,2,3,5,8,13,21 };
if (binary_search(a,a+sizeof(a)/sizeof(*a),1) == false) cout << " отказ";
if (binary_search(a,a+sizeof(a)/sizeof(*a),5) == false) cout << " отказ";
if (binary_search(a,a+sizeof(a)/sizeof(*a),8) == false) cout << " отказ";
if (binary_search(a,a+sizeof(a)/sizeof(*a),21) == false) cout << " отказ";
if (binary_search(a,a+sizeof(a)/sizeof(*a),–7) == true) cout << " отказ";
if (binary_search(a,a+sizeof(a)/sizeof(*a),4) == true) cout << " отказ";
if (binary_search(a,a+sizeof(a)/sizeof(*a),22) == true) cout << " отказ";
Это скучно и утомительно, но это всего лишь начало. На самом деле многие простые тесты — это не более чем длинные списки похожих вызовов. Положительной стороной этого наивного подхода является его чрезвычайная простота. Даже новичок в команде тестировщиков может добавить в этот набор свой вклад. Однако обычно мы поступаем лучше. Например, если в каком-то месте приведенного выше кода произойдет сбой, мы не сможем понять, где именно. Это просто невозможно определить. Поэтому фрагмент нужно переписать.
int a[] = { 1,2,3,5,8,13,21 };
if (binary_search(a,a+sizeof(a)/sizeof(*a),1) == false) cout << "1 отказ";
if (binary_search(a,a+sizeof(a)/sizeof(*a),5) == false) cout << "2 отказ";
if (binary_search(a,a+sizeof(a)/sizeof(*a),8) == false) cout << "3 отказ";
if (binary_search(a,a+sizeof(a)/sizeof(*a),21) == false) cout << "4 отказ";
if (binary_search(a,a+sizeof(a)/sizeof(*a),–7) == true) cout << "5 отказ";
if (binary_search(a,a+sizeof(a)/sizeof(*a),4) == true) cout << "6 отказ";
if (binary_search(a,a+sizeof(a)/sizeof(*a),22) == true) cout << "7 отказ";