Арнольд Роббинс - Linux программирование в примерах
Таблица 15.1. Сводка особенностей инструментов памяти
Инструмент ОС Заголовочный файл Модуль/ программа Многопоточность ccmalloc Многотипная Нет Программа Нет dmalloc Многотипная Необязательно Программа Да efence Многотипная Нет Программа Нет memwatch Многотипная Да Программа Нет Moraes Многотипная Необязательно Программа Нет mpatrol Многотипная Нет Программа Да mtrace Linux (GLIBC) Да Модуль Нет njamd Многотипная Нет Программа Нет valgrind Linux (GLIBC) Нет Программа Да yamd Linux, DJGPP Нет Программа НетКак видно, для отладки проблем динамической памяти доступен ряд выборов. На системах GNU/Linux и BSD один или более из этих инструментов, возможно, уже установлены, что избавляет вас от хлопот по их загрузке и построению.
Полезно также использовать для своей программы несколько инструментов подряд. Например, mtrace для обнаружения не освобождаемой памяти, a Electric Fence для перехвата доступа к недействительной памяти.
15.5.3. Современная lint
В оригинальном С компилятор не мог проверить, соответствуют ли параметры, переданные в вызове функции, списку параметров в определении функции; прототипов не было. Это часто вело к неуловимым ошибкам, поскольку ошибочный вызов функции мог вызывать лишь частично ошибочные результаты, которые проходили незамеченными во время тестирования, или мог даже вообще не появиться во время тестирования. Например:
if (argc < 2)
fprintf ("usage: %s [ options ] filesn", argv[0]);
/* отсутствует stderr */
Если программа, содержащая этот фрагмент, никогда не вызывается с ошибочным числом аргументов, fprintf(), в которой отсутствует первый аргумент FILE*, также никогда не вызывается.
Программа V7 lint была предназначена для решения таких проблем. Она делала два прохода через все файлы программы, сначала собирая сведения об аргументах функций, а затем сравнивая вызовы функций с собранной информацией. Особые файлы «библиотеки lint» предоставляли сведения о функциях стандартных библиотек, так что их также можно было проверить, lint проверяла также другие сомнительные конструкции.
С появлением в стандартном С прототипов необходимость в lint уменьшилась, но не исчезла совсем, поскольку C89 все еще допускает объявления функций в старом стиле.
extern int some_func(); /* Список аргументов неизвестен */
Вдобавок, многие другие аспекты программы можно проверять статически, т.е. путем анализа исходных текстов.
Программа splint (Secure Programming Lint — Lint для безопасного программирования)[186] является современным обновлением lint. Она предусматривает слишком много опций и возможностей, чтобы перечислять их здесь, но ее стоит исследовать.
Следует знать об одной особенности подобных lint программ, которая заключается в том, что они выводят целый поток предупреждающих сообщений. Многие из сообщаемых предупреждений в действительности безвредны. В таких случаях инструменты допускают использование специальных комментариев, сообщающих. «Да, я знаю об этом, это не проблема», splint лучше всего работает, когда вы предоставляете в своем коде большое количество подобных примечаний.
splint является мощным, но сложным инструментом; выделение некоторого времени на изучение его использования, а затем частое его использование поможет сохранить ваш код ясным.
15.6. Тестирование программ
Разработка программного обеспечения содержит элементы и искусства, и науки, это одна сторона того, что делает ее такой восхищающей и стимулирующей профессией. Данный раздел вводит в тему тестирования программного обеспечения, которая также включает в себя и искусство, и науку; таким образом, это несколько более общий и высокий уровень (читай: «на который можно махнуть рукой»), чем остальная часть данной главы.
Тестирование программ является неотъемлемой частью процесса разработки программного обеспечения. Весьма маловероятно, что программа заработает правильно на 100 процентов при первой компиляции. Программа не несет ответственности за свою правильность; за это отвечает автор программы. Одним из самых важных способов проверки того, что программа работает так, как предполагалось, является ее тестирование.
Один из способов классификации различных видов тестов следующий:
Тесты модулей (Unit tests)
Это тесты, которые вы пишете для каждого модуля или функционального компонента своей программы. В качестве части работы может потребоваться также создать окружение (scaffolding) — код, предназначенный для предоставления поддерживающего каркаса, чтобы запустить модуль в виде отдельной программы.
Важно спроектировать тесты для каждого функционального компонента во время его разработки. Это помогает прояснить проектирование особенностей; знание того, как это тестировать, помогает определить, что следует и что не следует делать в первую очередь.
Комплексные тесты (Integration tests)
Это тесты, которые применяются, когда все функциональные компоненты были написаны, протестированы и отлажены по отдельности. Идея в том, что все затем помешается на свое место в каркасе и тестируется все в целом, чтобы убедиться, что взаимодействия между компонентами работают.
Возвратные тесты (Regression tests)
Неизбежно вы (или ваши пользователи!) обнаружат проблемы. Это могут быть действительные ошибки, или ограничения дизайна, или неизбежные отказы в «пограничных случаях». Когда вы смогли воспроизвести и исправить проблему, сохраните первоначальные условия отказа в качестве возвратного теста.
Возвратный тест позволяет вам убедиться, что при проведении изменений не была повторена старая проблема. (Это случается довольно легко.) Пропустив программу после проделанных изменений через набор тестов, вы можете быть (более) уверены, что все работает таким образом, как предполагалось.
Тестирование следует по возможности автоматизировать. Это особенно легко сделать для программ, не содержащих графического пользовательского интерфейса (GUI), написанных в стиле инструментов Linux/Unix: читающих стандартный ввод или указанные файлы и записывающих в стандартный вывод и стандартную ошибку. По меньшей мере, тестирование можно осуществить с помощью простых сценариев оболочки. Более сложное тестирование осуществляется обычно с помощью отдельного подкаталога test и программы make.
Тестирование программного обеспечения само по себе является отдельной областью, и мы не предполагаем отдавать ей здесь должное; скорее, наше намерение дать вам знание, что тестирование является неотъемлемой частью разработки и часто движущей силой для использования ваших навыков в отладке! Вот очень короткий резюмирующий список.
• Проектируйте тест вместе с функциональностью
• Тестируйте пограничные условия: убедитесь, что функция работает внутри и на действительных границах и что она корректно выдает ошибку за их пределами. (Например, функция sqrt() должна потерпеть неудачу с отрицательным аргументом).