Систематическое тестирование
Очень важно тестировать программу систематически – на каждом этапе надо четко представлять, что вы тестируете в данный момент и каких результатов ожидаете. Тестирование должно производиться последовательно, чтобы ничего не упустить: текущие результаты тестирования надо обязательно записывать, чтобы представлять, что уже сделано и что предстоит сделать.
Тестируйте по возрастающей.
Тестирование должно идти рука об руку с созданием кода. Тестирование методом "большого скачка", когда сначала пишется вся программа, а потом тестируется целиком, гораздо сложнее и отнимает гораздо больше времени, чем постепенное. Напишите часть программы, оттестируйте ее, напишите очередной кусок кода, оттестируйте его и т. д. Если у вас есть два блока, которые писались и тестировались раздельно, оттестируйте их взаимодействие.
Например, когда мы тестировали программу CSV из главы 4, на первом шаге было достаточно написать только код, читающий ввод, и отладить его. На следующем шаге мы разделяли вводимые строки запятыми. Добившись работоспособности этих кусков, мы перешли к полям с кавычками и так мало-помалу подошли к тестированию всего вместе.
Тестируйте сначала простые блоки.
Общий подход к тестированию по возрастающей применим и к отдельным деталям программы. В первую очередь тестированию подлежат самые простые и чаще всего исполняющиеся блоки; только после того, как вы удостоверитесь в их корректности, можно двигаться дальше. Таким образом, на каждом этапе вы увеличиваете объем тестируемого кода, будучи при этом уверенными в работоспособности основных его частей. Простые тесты обнаруживают простые ошибки. Каждый тест выполняет свой минимум по поиску новой потенциальной проблемы. И несмотря на то что каждую новую ошибку выловить труднее, чем предыдущую, вовсе не факт, что ее будет труднее и исправить.
В этом параграфе мы поговорим о путях выбора эффективных тестов и о порядке их применения, а в двух следующих параграфах обсудим способы механизации процесса для наиболее эффективного тестирования.
Первый шаг, по крайней мере для маленьких программ и отдельных функций, – расширение тестов на граничные условия, описанных в предыдущем разделе: систематическое тестирование отдельных случаев.
Предположим, что у нас есть функция, осуществляющая двоичный поиск в массиве целых чисел. Начнем со следующих тестов (как нетрудно заметить, расположены они в порядке увеличения сложности):
- поиск в пустом массиве;
- поиск в массиве с одним элементом – пробное значение:
- меньше чем элемент массива;
- равно элементу массива;
- больше чем элемент массива;
- поиск в массиве с двумя элементами – пробные значения:
- тестируем все пять возможных вариантов;
- проверяем поведение при дублировании элемента – пробные значения:
- меньше значения в массиве;
- равно значению в массиве;
- больше значения в массиве;
- поиск в массиве с тремя элементами (так же, как и с двумя);
- поиск в массиве с четырьмя элементами (так же, как с двумя и тремя).
Если функция пройдет эти тесты без ошибок, она, по всей видимости, находится в неплохой форме, однако ее можно тестировать и дальше.
Приведенный набор тестов достаточно мал, чтобы выполнять их все вручную, но лучше создать оснастку (test scaffold – подмости (строительные леса) тестирования) для механизации процесса. С этой целью мы напишем простейшую программу (по сути, драйвер). Она будет считывать строки, содержащие ключ, по которому будет производиться поиск, и размер массива; после этого будет создан массив указанного размера, содержащий значения 1, 3, 5 и т. п.; результат поиска будет выводиться на экран.
Простейшая программка, но утилиты для тестирования и не должны быть сложными (естественно, при желании можно расширить возможности этой утилиты), их единственная задача – избавить вас от монотонных ручных проверок.