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