Стрессовое тестирование
Еще один эффективный прием тестирования – проверка программ большими объемами вводимых данных, сгенерированных компьютером. Входные данные, сгенерированные машиной, оказывают на программы несколько иное влияние, чем созданные вручную. Большие объемы сами по себе могут стать причиной сбоев, вызывая переполнение буферов ввода, массивов, счетчиков; они весьма эффективны для поиска неоправданно ограниченных в размерах структур данных.
Кроме того, люди часто неосознанно избегают "невозможных" значений (вроде пустой строки ввода или ввода недопустимых значений) и нечасто вводят очень длинные имена или очень большие значения. Компьютеры же генерируют данные точно в соответствии с запрограммированными зада-; ниями; у них нет никаких личных предпочтений или антипатий.
Вот для иллюстрации одна строка вывода, произведенного компилятором Microsoft Visual C++ версии 5.0 при компиляции нашей программы markov (версия C++ с использованием STL):
Компилятор предупреждает нас, что им было сгенерировано имя переменной с замечательной длиной в 1594 символа, но только 255 из них были сохранены в отладочной информации. Не все программы защищают себя от таких необычно длинных строк.
Выбор вводимых значений (не обязательно корректных) случайным образом – еще один достойный способ испытания программы на прочность. Это как бы дальнейшее развитие подхода "человек бы так не сделал". Некоторые коммерческие компиляторы С, например, тестируются посредством сгенерированных случайным образом (но синтаксически корректных) программ. Смысл состоит в том, чтобы использовать спецификацию проблемы – в данном случае стандарт С – для создания программы, генерирующей допустимые, но неестественные тестовые данные.
Подобные тесты полагаются на встроенные в программу механизмы защиты; поскольку удостовериться в правильности результатов удается не всегда, главной целью может быть провоцирование сбоя или непредусмотренной ситуации. При этом также проверяется и код, обрабатывающий ошибки. Если вводить только осмысленные, реалистичные данные, большинство ошибок ввода никогда не случится, и, следовательно, обрабатывающий их код не будет исполнен, а в нем могут таиться ошибки. Надо сказать, что иногда тестирование случайными данными может зайти слишком далеко и обнаружить ошибки, которые настолько маловероятны в реальности, что их можно и не исправлять.
Некоторые виды тестов основаны на введении преднамеренно некорректных данных. При попытках взлома часто используют объемистый или некорректный ввод, который перезаписывает ценные данные: имеет смысл самому проверить свою программу на восприимчивость к такому вводу. Некоторые функции стандартных библиотек оказываются уязвимы для подобных атак.
Например, в функции gets из стандартной библиотеки не предусмотрено никакого способа ограничения размера вводимой строки, поэтому ее нельзя использовать никогда; вместо нее нужно применять функцию fgets(buf, sizeof(buf), stdin). В обычном своем простейшем формате функция scanf ("%s", buf) также не ограничивает размер вводимой строки, поэтому ее можно использовать, только указывая размер строки в явном виде: scanf ("%20s", buf). В разделе 3.3 мы показали, как решить эту проблему для буфера произвольного размера.
Любой блок, который может получать данные извне программы (прямо или косвенно), должен проверять полученные значения перед тем, как их использовать. Следующая программа, взятая из учебника, по идее должна читать целое число, введенное пользователем, и, если это число слишком велико, выдавать предупреждение. Цель создания этой программы – продемонстрировать, как можно справиться с проблемой gets, однако предложенное решение работает не всегда:
Если вводимое число состоит из 10 символов, оно перепишет последний нуль массива num ненулевым значением, и по теории это может быть замечено после возврата gets. К сожалению, подобное решение нельзя признать удовлетворительным. Злонамеренный взломщик введет еще более длинную строку, которая перепишет какие-нибудь критические значения, – может быть, адрес возврата вызова, – и тогда программа никогда не вернется к выполнению условия if, а выполнит вместо этого инструкции взломщика.