Систематическое тестирование
Сохранность данных можно также проверять и внутри самой программы. Функция, подсчитывающая количество элементов в структуре, осуществляет простейший тест целостности данных. Для хэш-таблицы должно выполняться ее основное свойство: каждый записанный в нее элемент может быть считан. Проверить это свойство нетрудно – достаточно написать функцию, которая бы выводила содержимое таблицы в файл или массив. В любой момент для любой структуры данных разность числа включений и числа исключений должна равняться числу хранимых элементов. Проверить это условие совсем не сложно.
Сравните независимые реализации.
Независимые реализации библиотек или программ должны выдавать одни и те же результаты. Например, два компилятора должны из одного и того же текста создавать программы, которые на одной и той же машине будут вести себя одинаково, – по крайней мере, в большинстве случаев.
Иногда искомый ответ можно получить двумя различными способами, а иногда – написать тривиальную версию программы, не заботясь о быстродействии и используемых ресурсах, для сравнения результатов. Если две программы независимо друг от друга выдают одни и те же ответы, с большой долей уверенности можно считать, что обе они работают корректно; если же ответы их разнятся, значит, по крайней мере, в одной из них есть ошибки.
Один из нас однажды работал над компилятором для новой машины в паре с другим программистом. Мы разделили работу по отладке кода, генерируемого компилятором, таким образом: один писал программу, кодирующую инструкции для машины, а другой – дизассемблер для отладчика. При таком разделении ошибки интерпретации или реализации набора инструкций, для того чтобы остаться незамеченными, должны были возникнуть синхронно в обеих программах, иначе же, как только компилятор неправильно кодировал инструкцию, это сразу замечал отладчик.
На ранних стадиях весь вывод компилятора прогонялся через дизассемблер и сравнивался с распечатками собственно отладчика компилятора. Такая стратегия разделения давала хорошие результаты, и благодаря ей было найдено немало ошибок в обеих частях. Единственный сложный случай, затянувший работу, возник, когда оба программиста одинаково неверно истолковали громоздкую маловразумительную фразу из описания архитектуры машины.
Оценивайте охват тестов.
Одна из главных целей тестирования – убедиться, что каждое выражение в программе было выполнено хотя бы единожды при проведении последовательности тестов; тестирование нельзя считать завершенным, пока этого не произошло. Однако надо признать, что полного охвата добиться достаточно трудно. Не принимая даже во внимание выражений, обрабатывающих ситуации "не может быть", с помощью нормального ввода вынудить программу обойти все возможные ветки не так-то просто.
Для оценки охвата существуют специальные коммерческие утилиты. Профайлеры (программы-протоколисты), часто включаемые в комплект поставки компиляторов, предоставляют возможность осуществить подсчет частоты выполнения каждого выражения программы, – отсюда можно узнать и охват каждого теста.
Мы использовали комбинацию описанных выше методов для тестирования программы markov из главы 3, эти тесты будут подробно описаны в последнем разделе главы.
Упражнение 6.3
Опишите, как вы будете тестировать freq.
Упражнение 6.4
Спроектируйте и реализуйте версию freq, которая подсчитывала бы частоты для других типов данных – таких, как 32-битовые целые или числа с плавающей точкой. Сможете ли вы добиться того, чтобы программа элегантно обрабатывала данные различных типов?