Рассмотрим конкретный пример: протестируем программу для бинарного поиска. Ее спецификация из стандарта ISO приведена ниже.
template
bool binary_search(ForwardIterator first, Forwardlterator last, const T& value );
template bool binary_search(ForwardIterator first,
ForwardIterator last, const T& value, Compare comp);
Требует.
Элементы e из диапазона [first, last) разделены в соответствии с отношением e
Возвращает. Значение true, если в диапазоне [first,last) существует итератор i, удовлетворяющий условиям: !(*I
Сложность. Не более log(last - first) + 2 сравнения.
Нельзя сказать, что непосвященному человеку легко читать эту формальную (ну хорошо, полуформальную) спецификацию. Однако, если вы действительно выполнили упражнение, посвященное проектированию и реализации бинарного поиска, которое мы настоятельно рекомендовали сделать в начале главы, то уже должны хорошо понимать, что происходит при бинарном поиске и как его тестировать. Данная (стандартная) версия функции для бинарного поиска получает в качестве аргументов пару однонаправленных итераторов и определенное значение и возвращает значение true, если оно лежит в диапазоне, определенном указанными итераторами. Эти итераторы должны задавать упорядоченную последовательность. Критерием сравнения (упорядочения) является оператор <. Вторую версию функции binary_search, в которой критерий сравнения задается как дополнительный аргумент, мы оставляем читателям в качестве упражнения.
Здесь мы столкнемся только с ошибками, которые не перехватывает компилятор, поэтому примеры, подобные этому, для кого-то станут проблемой.
binary_search(1,4,5); // ошибка: int — это не однонаправленный
// итератор
vector v(10);
binary_search(v.begin(),v.end(),M7M); // ошибка: невозможно найти
// строку
// в векторе целых чисел binary_search(v.begin(),v.end()); // ошибка: забыли значение
Как систематически протестировать функцию binary_search() ? Очевидно, мы не можем просто перебрать все аргументы, так как этими аргументами являются любые мыслимые последовательности значений любого возможного типа — количество таких тестов станет бесконечным! Итак, мы должны выбрать тесты и определить некие принципы этого выбора.
• Тест на возможные ошибки (находит большинство ошибок).
• Тест на опасные ошибки (находит ошибки, имеющие наихудшие возможные последствия).
Под опасными мы подразумеваем ошибки, которые могут иметь самые ужасные последствия. В целом это понятие носит неопределенный характер, но для конкретных программ его можно уточнить. Например, если рассматривать бинарный поиск изолированно от других задач, то все ошибки могут быть одинаково опасными. Но если мы используем функцию binary_search в программе, где все ответы проверяются дважды, то получить неправильный ответ от функции binary_search может быть более приемлемым вариантом, чем не получить никакого, поскольку во втором случае возникает бесконечный цикл. В таком случае мы могли бы приложить больше усилий, чтобы найти трюк, провоцирующий бесконечный (или очень длинный) цикл в функции binary_search, по сравнению с исследованием вариантов, в которых она дает неправильный ответ. Отметьте в данном контексте слово “трюк”. Помимо всего прочего, тестирование — это занятие, требующее изобретательного подхода к задаче “как заставить код работать неправильно”.
Лучшие тестировщики не только методичные, но и изворотливые люди (в хорошем смысле, конечно).
|