КулЛиб - Классная библиотека! Скачать книги бесплатно 

Тестирование и отладка программ для профессионалов будущих и настоящих. — 2-е изд. (эл.). [Михаил Александрович Плаксин] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

М. Плаксин

Тестирование
и отладка программ
для профессионалов будущих и настоящих

2-е издание (электронное)

Москва
БИНОМ. Лаборатория знаний
2013

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

УДК 004.42
ББК 32.973-018
П37

П37

Плаксин М. А.
Тестирование и отладка программ для профессионалов будущих и настоящих [Электронный ресурс] / М. А. Плаксин. — 2-е изд. (эл.). — М. : БИНОМ.
Лаборатория знаний, 2013. — 167 с. : ил.
ISBN 978-5-9963-0946-7
Изложена теория тестирования и отладки программ, причем рассматриваются как вопросы, интересные начинающим
программистам, так и вопросы, полезные профессионалам,
например вероятностные модели оценки количества ошибок в программе и количества необходимых тестов. Описание простой в использовании высокотехнологичной методики
тестирования учебных программ подкрепляется примерами
создания программ, в которых тестирование выступает как
неотъемлемый аспект разработки программы. Отдельная глава посвящена подробному описанию отладочных средств системы Турбо Паскаль, широко используемой в школах и вузах
для обучения программированию.
Для тех, кто изучает и учит программированию: старшеклассников, студентов, преподавателей вузов, учителей;
также полезна и для профессиональных программистов.
УДК 004.42
ББК 32.973-018

По вопросам приобретения обращаться:
«БИНОМ. Лаборатория знаний»
Телефон: (499) 157-5272
e-mail: binom@Lbz.ru
http://www.Lbz.ru, http://e-umk.Lbz.ru, http://metodist.Lbz.ru

ISBN 978-5-9963-0946-7

c БИНОМ. Лаборатория знаний, 2007

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Оглавление

Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Глава 1. В каком случае программа содержит ошибку? . .
Глава 2. Минимальные требования к программе: функциональность и удобство использования . . . . . . .
Глава 3. Понятия тестирования и отладки . . . . . . . . . . . . . .
Глава 4. Принципы тестирования . . . . . . . . . . . . . . . . . . . . . . .
Глава 5. Понятие полноты тестирования . . . . . . . . . . . . . . . .
Глава 6. Критерии черного ящика . . . . . . . . . . . . . . . . . . . . . . .
Глава 7. Критерии белого ящика . . . . . . . . . . . . . . . . . . . . . . . .
Глава 8. Минимально грубое тестирование . . . . . . . . . . . . . .
Глава 9. Ошибкоопасные ситуации . . . . . . . . . . . . . . . . . . . . . .
9.1. Обращение к данным . . . . . . . . . . . . . . . . . . . . . . . . . . .
9.2. Вычисления . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9.3. Передача управления. . . . . . . . . . . . . . . . . . . . . . . . . . .
9.4. Подпрограммы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9.5. Файлы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Глава 10. Безмашинное тестирование . . . . . . . . . . . . . . . . . . . .
Глава 11. Пример тестирования несложной программы . .
Глава 12. Порядок работы над программой . . . . . . . . . . . . . .
Глава 13. Нисходящее тестирование . . . . . . . . . . . . . . . . . . . . .
Глава 14. *Оценка количества ошибок в программе . . . . . .
14.1. Модель Миллса . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14.2. «Парная» оценка. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14.3. Исторический опыт . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Глава 15. *Оценка количества необходимых тестов . . . . . .
Глава 16. Отладка . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16.1. Место проявления ошибки и место нахождения
ошибки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16.2. Отладочные операторы . . . . . . . . . . . . . . . . . . . . . . . . .
16.3. Индуктивный и дедуктивный методы поиска
ошибки. Ретроанализ . . . . . . . . . . . . . . . . . . . . . . . . . .
16.4. Принципы отладки . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16.5. Анализ обнаруженной ошибки . . . . . . . . . . . . . . . . .

5
7
9
10
11
15
18
22
27
32
32
36
45
47
50
53
56
67
68
71
71
78
79
81
84
84
85
89
92
93

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

4

Оглавление

Отладочные средства системы Турбо Паскаль . . 94
Перечень отладочных средств Турбо Паскаля . . . 94
Пошаговое выполнение программы . . . . . . . . . . . . . 96
Контрольные точки . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Просмотр и вычисление значений переменных
и выражений. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
17.5. Наблюдение за стеком вызванных подпрограмм 107
17.6. Локальное меню окна редактирования программы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
Глава 18. Еще один пример тестирования программы . . . . 110
18.1. Построение тестов для критериев черного
ящика . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
18.2. Написание текста программы . . . . . . . . . . . . . . . . . . 118
18.3. Подготовка к тестированию по критериям белого ящика . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
18.4. «Сухая прокрутка» . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
18.5. Отладка на компьютере . . . . . . . . . . . . . . . . . . . . . . . . 150
18.6. Уроки данного примера . . . . . . . . . . . . . . . . . . . . . . . . 160
Глава 19. Что еще можно проверить в программе?. . . . . . . . 162
Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
Что читать дальше? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167

Глава 17.
17.1.
17.2.
17.3.
17.4.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Введение

Учебные планы программистских факультетов большинства
вузов подразумевают, что студенты-первокурсники уже умеют
программировать. Однако тот курс программирования, который входит в школьную программу, недостаточен. В частности,
в нем почти не уделяется внимания таким важным вопросам,
как тестирование и отладка. Профессионалы знают, что затраты на тестирование и отладку оцениваются в 50–60% всех
затрат на разработку программы. Но для подавляющего большинства школьников, да и многих студентов «написать программу» означает написать некий текст на языке программирования и, в лучшем случае, добиться того, чтобы в нем не было
ошибок трансляции. О проверке соответствия программы поставленной задаче, поиске и исправлении смысловых ошибок,
как правило, речь даже не заходит. Причиной этого в большой
степени является отсутствие наглядной и доходчивой методики
тестирования и отладки программ.
В данной книге изложена методика тестирования учебных
программ, основанная на классических научных исследованиях и опыте преподавания начального курса программирования в
старших классах средней школы и младших курсах университета. Подробно рассматриваются отладочные средства популярной
системы Турбо Паскаль. В главах 14 и 15 описываются статистические модели оценки количества ошибок в программе и количества тестов, необходимых для их обнаружения. Изучение
этого материала требует определенной математической подготовки. Данные главы отмечены звездочкой.
Классик жанра Гленфорд Майерс свою легендарную книгу
«Искусство тестирования программ» начал с того, что предложил читателям сформировать набор тестов для проверки простенькой программы. Программа читала 3 целых числа (у Майерса — с перфокарт, мы скажем — из файла; впрочем, кто не
умеет вводить числа из файла, может вводить их с терминала),
рассматривала их как длины сторон треугольника и печатала
сообщение о том, является ли этот треугольник разносторонним, равносторонним или равнобедренным. Далее набор тестов,
предложенный читателем, надо было сравнить с набором, предложенным самим Майерсом.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

6

Введение

Мы также используем майерсову задачу, но порядок работы
несколько изменим. Читателю предлагается составить набор
тестов для задачи про треугольники. Но обсуждать полученный
набор немедленно мы не будем. Вместо этого мы еще несколько
раз вернемся к этой задаче по ходу изучения темы с тем, чтобы
читатель мог на практике применить получаемые знания и оценить свой прогресс в этой области. И только после этого мы обсудим полученный результат.
Итак, перед дальнейшим чтением вам предлагается самостоятельно составить набор тестов для вышеуказанной программы.
Готово? Тогда — продолжим.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Глава 1

В каком случае программа
содержит ошибку?
Понятия тестирования и отладки связаны с процессом поиска и исправления ошибок в программе. Поэтому первый вопрос, на который надо ответить, будет звучать так: в каком
случае в программе есть ошибка?
Программа содержит ошибку, если она ведет себя неразумно
с точки зрения пользователя.
Это утверждение повергает новичков в замешательство: откуда же я знаю, какое поведение программы пользователь сочтет разумным? Но если вы не знаете, какое поведение программы разумно с точки зрения вашего заказчика, значит, вы
не понимаете, какую задачу решаете. Как можно писать программу, не понимая, что она должна делать?
Как определить разумность поведения программы?
Во-первых, естественно, программа должна быть верна синтаксически, т. е. при ее трансляции не должно быть ошибок.
Текст, содержащий синтаксические ошибки, вообще не имеет
права называться программой. Такая «программа» вообще никак себя не ведет.
Во-вторых, программа должна правильно решать поставленную перед ней задачу. То есть при вводе в нее корректных исходных данных она должна выдавать правильный результат.
Какой именно результат считать правильным, надо уточнить у
заказчика. Например, если вам заказывают программу для вычисления квадратного корня, то должна ли она выдавать оба
корня (положительный и отрицательный) или только арифметический? Корень из нуля — это ±0 или просто 0? Какова требуемая точность? Она всегда должна быть одна и та же или может меняться? Если меняться, то каким образом и по чьей
инициативе? Надо ли выявлять периодические дроби? Как программа должна реагировать на отрицательное подкоренное выражение? А на предложение извлечь корень из a 2 ? Вы можете
предложить свои ответы на все эти вопросы. Но в данном случае важно не то, что думаете по этому поводу вы, а то, что думает по этому поводу ваш заказчик. Ваша задача — выявить и
сформулировать все эти вопросы, а не придумывать на них
свои собственные ответы. Для выявления и формулировки вопросов полезно не хвататься за клавиатуру сразу же, как толь-

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

8

Глава-1

ко вам дадут задание на разработку программы, а сначала немножко подумать. Все эти вопросы все равно возникнут в процессе разработки программы. Но если их не оговорить заранее,
ответы на них вы будете придумывать сами, и нет никакой гарантии, что они покажутся заказчику разумными.
В-третьих, программа не должна делать ничего лишнего.
При вычислении квадратного корня не надо менять настройки
операционной системы и исполнять вашу любимую мелодию.
В-четвертых, результат должен быть получен через разумное время при разумных затратах других ресурсов. Если программа при вычислении квадратного корня из числа выдает десятистраничную распечатку, в ней, наверное, что-то не так.
И наконец, в-пятых, программа должна разумно реагировать на ввод некорректных входных данных и на непредусмотренные заранее ситуации. Например, если программа вычисляет квадратный корень, а пользователь вместо подкоренного
выражения ввел свою фамилию, то программа должна распознать, что введенная строка — не число1, и сообщить об этом
пользователю на его родном языке, а не заканчивать работу
аварийно. Если вы разработали программу для управления аэропортом, рассчитанную на то, что в небе могут быть одновременно не более 20 самолетов, а в какой-то момент их оказалось 21,
ваша программа должна отреагировать на это неким разумным
образом. Например, сообщить о чрезвычайной ситуации диспетчеру и экипажу и передать этот борт диспетчеру для ручного ведения. Вариант, когда появление 21-го самолета приведет к
тому, что программа потеряет один из ранее ведомых самолетов
или просто отключится, совершенно недопустим.
В поддержку данного выше утверждения об ошибках в программе в [2] упоминается следующая история. При разработке
системы противоракетной обороны США военные заказчики
потребовали от программистов, чтобы система подавала сигнал
тревоги, если обнаружит в небе враждебный летательный объект. Программисты спросили, какой объект считать враждебным. Военные ответили: любой, который не будет опознан как
дружественный. Программисты добросовестно заложили в систему полученные от военных правила распознавания дружественных летательных объектов. В ночь испытания системы над
горизонтом взошла Луна... Была ли в данном случае выдача
сигнала к началу III Мировой войны верным поведением программы, или в ней все-таки была ошибка?
1

В Турбо Паскале для преобразования последовательности литер, представляющей собой изображение числа, в число (и проверки правильности записи
числа) используется процедура Val. Для организации собственной диагностики любое значение следует вводить как строковое и преобразовывать в число с помощью процедуры Val.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Глава 2

Минимальные требования
к программе: функциональность
и удобство использования

Минимальные требования к любой программе — это обеспечение требуемой функциональности (реализация требуемых
функций) и удобство эксплуатации. Именно они должны быть
проверены в первую очередь.
Почти вся оставшаяся часть книги будет посвящена тестированию функциональности. Удобство взаимодействия человека
с программой останется вне нашего внимания. Относительно
него ограничимся только одним замечанием. Лозунг современного интерфейса: «Не пользователь должен приспосабливаться
к программе, а программа к пользователю». Даже самая простая программа должна обеспечить пользователю некоторый
уровень комфорта. Например, совершенно недопустима ситуация, когда после запуска программы перед пользователем возникает пустой экран с мигающим курсором и больше ничего.
Пользователю предлагается самому догадаться, что за программу он запустил и что он должен делать дальше. Как минимум,
программа должна сообщить о своей функциональности и объяснить свое поведение (выдать осмысленное приглашение к
вводу или сообщить, почему возникла пауза).

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Глава 3

Понятия тестирования
и отладки

Тестирование — это выполнение программы с целью обнаружения факта наличия в программе ошибки.
Отладка — определение места ошибки и внесение исправлений в программу.
В русском языке словосочетание «обнаружить ошибку» может иметь, по крайней мере, два смысла: «обнаружить факт наличия ошибки» и «обнаружить место, где допущена ошибка».
Под тестированием понимается обнаружение ошибки в первом
смысле, под отладкой — во втором.
Как правило, эти два процесса тесно взаимосвязаны, но могут быть и разделены. Например, в процессе приемки системы
заказчик занимается только тестированием — поиском несоответствий между заданием на разработку программы (спецификацией программы) и разработанной программой. Если его поиски увенчаются успехом, программисту придется заниматься
отладкой (определением места допущенных ошибок и исправлением программы).
Цель тестирования — обнаружение ошибок в программе.
Отметим важный психологический момент: цель тестирования — не доказать правильность программы, а обнаружить
в ней ошибки! Если вы ставите себе задачей показать, что программа правильная и ошибок не содержит, то (на подсознательном уровне) и тесты будете подбирать такие, которые ошибок
в программе не обнаружат.
Отсюда следующее неожиданное рассуждение. Зададим вопрос: какой тест считать удачным? Если цель тестирования —
найти в программе ошибку, то удачным должен считаться тест,
который обнаруживает наличие в ней ошибки. Это положение
противоречит интуитивному представлению о тестировании и
требует сознательного психологического настроя.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Глава 4

Принципы тестирования

1. Ошибки в программе есть.
Необходимо исходить из того, что ошибки в программе есть.
Иначе тестирование не будет иметь для вас никакого смысла,
и отношение к нему будет соответственное.
2. Тест — это совокупность исходных данных и ожидаемых
результатов.
Очень частая ошибка заключается в том, что на вход программе подаются данные, для которых заранее не известны
правильные результаты. Здесь в дело опять вступает психология. Человеческая психика устроена так, что наши глаза очень
часто видят не то, что есть на самом деле, а то, что нам хочется
видеть. Если заранее не зафиксировать ожидаемый результат,
то всегда возникает искус объявить, что полученные результаты — это и есть то, что должно было получиться.
3. Тестовые данные должны быть достаточно просты для проверки.
Прямое следствие предыдущего принципа.
4. Тесты готовятся заранее, до выхода на машину.
Это касается как исходных данных, так и ожидаемых результатов. Реально в подавляющем большинстве случаев тесты
придумываются на ходу, причем только исходные данные.
5. Первые тесты разрабатываются после получения задания
на разработку программы до написания программного кода.
Самые первые тесты следует продумать сразу же после постановки задачи до того, как начали писать программный код.
Ранняя разработка тестов позволяет правильно понять поставленную задачу. Даже в самых простых и, на первый взгляд,
очевидных заданиях часто встречаются тонкости, которые сразу не видны, но становятся заметны, когда вы пытаетесь определить результат, соответствующий конкретным входным данным. (Пример уточнений, которых потребовала программа для
извлечения квадратного корня, приведен в главе 1 «В каком

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

12

Глава-4

случае программа содержит ошибку?».) Цель ранней разработки
тестов — уточнить постановку задачи, выявить тонкие места.
Без этого вы рискуете написать программу, которая будет решать какую-то иную задачу, а не ту, которая была перед вами
поставлена. О методике разработки тестов до написания программы речь пойдет ниже в главе 6 «Критерии черного ящика».
6. Перед началом тестирования следует сформулировать цели,
которые должны быть достигнуты в ходе тестирования.
В частности, набор тестов должен быть полон с точки зрения выбранных критериев полноты тестирования. О критериях полноты тестирования речь пойдет в главе 6 «Критерии черного ящика», седьмой главе «Критерии белого ящика», главе 8
«Минимально грубое тестирование». Независимо от применяемых критериев разработка тестов должна вестись систематически, по определенной методике.
7. В процессе тестирования необходимо фиксировать выполненные тесты и реально полученные результаты.
К сожалению, обычной является ситуация, когда студент,
получив задание, сразу же садится за компьютер, вводит некоторый программный текст, после нескольких перетрансляций
избавляется от синтаксических ошибок, после чего запускает
полученную программу, на ходу придумывает и подает на вход
программы некие исходные данные, получает результаты, вводит новые данные, опять получает результаты и т. д. Тестовые
данные и результаты нигде не фиксируются. Для любых нетривиальных программ подобное тестирование почти бесполезно,
поскольку не позволяет отделить проверенные участки от непроверенных, не позволяет оценить количество ошибок в программе, не дает информации для принятия решения об окончании тестирования.
8. Тесты должны быть одинаково тщательны как для правильных, так и для неправильных входных данных.
На практике часто ограничиваются тестированием правильных входных данных, забывая о неправильных.
9. Необходимо проверить два момента: программа делает то,
что должна делать; программа не делает того, чего делать
не должна.
Особенно это важно для изменений в глобальной среде. Если
программа выдает правильные результаты, но при этом затирает
половину винчестера, то едва ли ее можно признать правильной.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Принципы-тестирования

13

10. Результаты теста необходимо изучать досконально и объяснять полностью.
11. Недопустимо ради упрощения тестирования изменять программу.
Тестировать после этого вы будете уже другую программу.
12. После исправления программы необходимо повторное тестирование.
Для того чтобы исправить обнаруженную ошибку, мы вносим изменения в программу. Но кто может гарантировать, что,
исправив одну ошибку, мы не внесем другую? Увы, никто! Вероятность внесения новой ошибки при исправлении старой оценивается в 20–50%. Для особо сложных систем эта вероятность
может быть значительно выше. Так, в знаменитой в свое время
ОС IBM/360 количество ошибок считалось постоянным и оценивалось примерно в 1000. Система была настолько сложна и
запутанна, что считалось невозможным «починить» ее в одном
месте и при этом не «сломать» в другом.
Итог: после внесения изменений в программу необходимо
заново прогнать весь пакет ранее выполненных тестов.
13. Ошибки кучкуются.
Чем больше ошибок обнаружено в модуле, тем больше вероятность, что там есть еще. Так, в одной из версий системы 370
(преемника IBM/360) 47% обнаруженных ошибок пришлось
на 4% модулей [1].
На первый взгляд это утверждение противоречит здравому
смыслу. «Здравый смысл» неявно исходит из предположения о
равномерном распределении ошибок по всему тексту программы. На чем основано такое предположение? На взгляде на
программу как на некую однородную сущность, все части которой обладают примерно одинаковыми свойствами. Реально программа устроена гораздо более сложно. В ней есть фрагменты
простые и фрагменты сложные. Есть функции, которые были
хорошо специфицированы, и функции, для которых спецификации были сформулированы нечетко. Есть модули, которые
были спроектированы добротно, и модули, которые были спроектированы небрежно. Есть части, которые писали опытные
программисты, и части, которые писали новички. Естественно,
что большая часть ошибок окажется в тех частях, которые более сложны, хуже специфицированы, хуже спроектированы,
написаны новичками. С учетом этих условий кучкование ошибок уже не выглядит странным.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

14

Глава-4

Данный принцип имеет одно неприятное последствие. Если
следовать ему строго, то количество ошибок в программе должно
возрастать до бесконечности. Ведь нахождение каждой следующей ошибки увеличивает вероятность существования других
еще ненайденных ошибок. К счастью, это не так. Подробнее мы
поговорим об этом в главе 14 «Оценка количества ошибок в
программе».
14. Окончательное тестирование программы лучше проводить
не ее автору, а другому человеку.
Тестирование программы — процесс разрушительный. Цель
его — выявление дефектов в программе. Психологически любой создатель всегда склонен в большей или меньшей степени
отождествлять себя со своим созданием, «вкладывать в него
душу». И чем больше усилий потрачено на работу, тем больше
степень отождествления. Программа начинает восприниматься
программистом как продолжение его самого. В восприятии автора обнаружение ошибок в программе приобретает трагический оттенок: «Программа — это продолжение меня. Программа — дефектна. Следовательно, я дефектен!». Если вы не
склонны к мазохизму, такая ситуация вам вряд ли понравится.
Выходов из нее два. Либо передать окончательное тестирование
вашей программы другому человеку. Либо четко отделить себя
от программы, перестать воспринимать ее как часть себя (не
«моя программа», а «программа, написанная мной»). Это, может быть, проще сделать в программистском коллективе, в котором все программы являются результатом коллективной работы и коллективной собственностью. Но то же самое необходимо и
при одиночной работе. Не отделив себя от своего детища, вы не
сможете объективно оценить его достоинства и недостатки.
Естественно, что человек, проверяющий вашу программу,
должен быть достаточно подготовлен, готов потратить на эту
нужное количество времени и усилий. Это не сможет сделать
первый встречный. Это нельзя сделать мимоходом.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Глава 5

Понятие полноты тестирования

Цель тестирования — обнаружить ситуацию, когда результаты работы программы не соответствуют входным данным. Самый
простой способ сделать это — перебрать все возможные варианты входных данных и проверить правильность получаемых результатов. К сожалению, воспользоваться этим способом почти
никогда не удается. Даже для простейших программ количество
вариантов входных данных оказывается астрономическим.
Например. Пусть наша программа решает очень простую задачу. Пользователь вводит с клавиатуры два целых числа, а
программа выдает их сумму. Зададим дополнительные ограничения. Будем рассматривать только шестнадцатибитные числа.
Сколько вариантов работы программы нам надо проверить?
Первых слагаемых у нас будет 216 . Вторых — столько же.
Их комбинаций: 216 × 216 = 232 . Поскольку 210 » 1000 , получаем
232 » 4 000 000 000.
Пусть на проверку одного варианта потребуется одна секунда. (Реально не любые два числа можно ввести за одну секунду, а тем более проверить полученный результат. Но для
простоты пусть будет так.) Тогда для выполнения всех вариантов потребуется более 4 000 000 000 с. = 66 666 666,(6) мин. =
1 111 111,(1) ч. = 46 296,(296) суток » 126,75 лет. Тестирование
вашей программы (для сложения двух шестнадцатиразрядных
чисел!) закончат только ваши правнуки! (Хотя откуда же им
взяться, ведь 126 лет потребуется при непрерывной работе по
24 часа в сутки.)
А если программа будет складывать не два числа, а три?
А ведь большинство самых элементарных программ выполняет
гораздо более сложные вычисления. Не говоря уже о том, что
наличие циклов while или repeat делает количество вычислений в программе потенциально бесконечным.
Отсюда печальный вывод: исчерпывающее тестирование
(т. е. перебор всех возможных вариантов выполнения) для любой нетривиальной программы невозможно.
Но проверять программы все-таки нужно. Как быть? Как
проверить все варианты, перебрав не все? Как решить, какие
из вариантов проверять надо, а какие нет? И сколько всего вариантов должно быть проверено?

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

16

Глава-5

Для уменьшения количества проверяемых вариантов все
множество возможных вариантов выполнения программы делят на подмножества. Все варианты, попавшие в одно подмножество, считаются в некотором смысле равнозначными. Считается, что, проверив один из вариантов данного подмножества,
мы тем самым проверили и все остальные варианты данного
подмножества.
В системологии деление множества объектов на подмножества с одинаковыми свойствами называется классификацией,
сами такие подмножества — классами, признак, по которому
один класс отличается от другого, — основанием классификации. Например, множество людей можно поделить на мужчин
и женщин по основанию «пол», на молодых, зрелых и старых
по основанию «возраст» и т. д.
При переводе на язык системологии: для уменьшения количества тестов нам необходимо классифицировать все возможные варианты выполнения программы, разбить их на классы,
эквивалентные с точки зрения проверки правильности программы. Нам необходимы основания для классификации возможных вариантов выполнения программы, необходимы критерии, по которым мы будем объявлять те или иные варианты
выполнения равнозначными друг другу. Если такие критерии
выбраны, если классификация проведена, достаточно взять по
одному тесту из каждого класса. Если все они окажутся неудачными (т. е. не смогут обнаружить в программе ошибки),
тестирование можно заканчивать.
Критерии, по которым проводится классификация всех
возможных вариантов выполнения программы с точки зрения проверки ее правильности, называются критериями полноты тестирования.
После того как критерий полноты тестирования выбран,
каждый тест должен анализироваться с двух точек зрения:
1) Удачен ли данный тест (удалось ли с его помощью обнаружить ошибки)?
2) Достаточен ли набор тестов с точки зрения применяемого
критерия или нужны еще какие-то тесты? Если нужны, то
какие? Что еще следует проверить?
Очевидно, что допущение об эквивалентности всех объектов одного класса требует большой аккуратности при проведении классификации. Для повышения надежности проводят
несколько классификаций, делят множество вариантов выполнения на классы несколькими разными способами.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Понятие-полноты-тестирования

17

Существует два подхода к формулированию критериев полноты тестирования: критерии «черного ящика» и критерии
«белого ящика». Критерии черного ящика описывают тестирование с точки зрения поставленной задачи без учета внутреннего устройства программы. Критерии белого ящика учитывают
структуру программы.
Термин «черный ящик» позаимствован из кибернетики.
В кибернетике представление системы в виде черного ящика
означает описание ее входов и выходов и связей между входными воздействиями и результатами работы системы. При этом
структура системы, ее внутреннее устройство никак не затрагиваются и считаются неизвестными. В качестве примера описания систем в виде черных ящиков могут служить инструкции
по использованию бытовой техники.
Термин «белый ящик» появился чисто формально как противовес «черному». (Содержательно антонимом «черному ящику» должен был бы выступить «прозрачный ящик».)
При проектировании тестов начинать следует с критериев
черного ящика. Собственно, это единственный возможный
способ, если вы начинаете придумывать тесты до того, как
написали программу (как было сказано в четвертой главе
«Принципы тестирования»). После подготовки текста программы тесты, разработанные исходя из критериев черного
ящика, примеряются на структуру программы. Если их оказывается недостаточно, они дополняются тестами, разработанными исходя из критериев белого ящика.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Глава 6

Критерии черного ящика

Существуют следующие критерии черного ящика:
1) тестирование функций;
2) тестирование классов входных данных;
3) тестирование классов выходных данных;
4) тестирование области допустимых значений (тестирование
границ класса);
5) тестирование длины набора данных;
6) тестирование упорядоченности набора данных.
Критерий тестирования функций актуален для многофункциональных программ. Он требует подобрать такой набор тестов, чтобы был выполнен хотя бы один тест для каждой из
функций, реализуемых программой.
Критерий тестирования классов входных данных требует
классифицировать входные данные, разделить их на классы таким образом, чтобы все данные из одного класса были равнозначны с точки зрения проверки правильности программы.
Считается, что если программа работает правильно на одном
наборе входных данных из этого класса, то она будет правильно работать на любом другом наборе данных из этого же класса. Критерий требует выполнения хотя бы одного теста для
каждого класса входных данных.
Критерий тестирования классов выходных данных выглядит аналогично предыдущему критерию, только проверяются
не входные данные, а выходные.
Надо отметить, что часто эти три критерия хорошо согласуются друг с другом. При применении одного из них остальные
будут удовлетворены автоматически. Если программа реализует несколько функций, то вполне естественно, что каждой из
этих функций будет соответствовать свой класс входных и свой
класс выходных данных. Часто существует соответствие между
классами входных и выходных данных. Например, для упоминавшейся ранее программы извлечения квадратного корня выходными классами можно считать вещественные числа, комплексные числа, аналитические выражения. Соответствующими
входными классами будут неотрицательные числа, отрицательные числа, аналитические выражения.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Критерии-черного-ящика

19

Рассмотрим программу для учета кадров предприятия. Скорее всего, она будет иметь следующие функции:
— принять на работу,
— уволить с работы,
— перевести с одной должности на другую,
— выдать кадровую сводку.
Классы входных данных:
— приказ о приеме,
— приказ об увольнении,
— приказ о переводе,
— заявка на кадровую сводку.
Классы выходных данных:
— запись о приеме,
— запись об увольнении,
— запись о переводе,
— кадровая сводка.
Этот пример хорошо демонстрирует соответствие между
функциями, классами входных и выходных данных.
Тестирование области допустимых значений (тестирование
границ класса). Если область допустимых значений переменной представляет собой простое перечисление (например, ноты,
цвет, пол, диагноз и т. п.), надо проверить, что программа правильно понимает все эти значения и не принимает вместо них
никаких иных значений. Например, как программа отреагирует
на попытку ввести несуществующую ноту или пол.
Если класс допустимых значений представляет собой числовой диапазон, то понадобится более серьезная проверка. В этом
случае выделяются:
1) нормальные условия (в середине класса);
2) граничные (экстремальные) условия;
3) исключительные условия (выход за границу класса).
Например, пусть программа предназначена для обработки деканатом информации об одной студенческой группе. Нормальное число студентов в группе — 20–25. Но на младших курсах
их часто бывает больше, а на старших — наоборот. Пусть максимальное число студентов в группе ограничено 30. В этом случае
имеет смысл проверить правильность работы программы с группой из 20 студентов (нормальные условия), с группой из 30 человек и с группой из одного человека (экстремальные условия).
Необходимо проверить, как программа отреагирует на приказ

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

20

Глава-6

о зачислении в группу 31-го студента и об отчислении последнего остававшегося в группе студента (исключительные условия).
Особенно интересна бывает проверка минимального выхода
за границу класса. Например, если значение некоторой переменной x должно лежать в промежутке [0,999; 1,999], стоит
проверить, что будет, если x = 0,998 или x = 1,9991.
Иногда требуется более тонкая градация. Возможна ситуация,
когда вся область допустимых значений делится на отдельные
подобласти, требующие от программы разной реакции. Например, пусть программа готовит сводный отчет о работе нескольких филиалов некоторой фирмы. Известно, что каждый из филиалов ежедневно продает продукции примерно на 10 тыс. руб.
Как должна реагировать программа на сообщение о том, что некий филиал за день получил 100 руб. или, наоборот, 1 000 000
руб.? Теоретически можно допустить, что этот филиал сработал
настолько плохо или, наоборот, потрясающе хорошо. Однако не
логичней ли предположить, что в этих случаях при вводе данных
возникли проблемы с клавиатурой? В результате вся область допустимых значений делится на 3 подобласти: подобласть нормальных значений, подобласть подозрительно больших значений
и подобласть подозрительно маленьких значений. Нормальные
данные программа должна просто принимать к обработке. Подозрительно большие и подозрительно малые значения, в принципе, допустимы. Однако при их получении имеет смысл затребовать подтверждения, действительно ли они так выглядят.
Впрочем, вместо деления области допустимых значений на
подобласти можно было бы просто выделить 3 класса входных
данных: нормальные, слишком маленькие, слишком большие.
Следующие два критерия являются частными случаями,
уточняющими ранее названные критерии. Но в силу их важности и частой применимости, они заслуживают отдельного
упоминания.
Тестирование длины набора данных можно считать частным
случаем тестирования области допустимых значений. В данном
случае речь пойдет о допустимом количестве элементов в наборе. Если программа последовательно обрабатывает элементы
некоторого набора данных, имеет смысл проверить следующие
ситуации:
1) пустой набор (не содержит ни одного элемента);
2) единичный набор (состоит из одного-единственного элемента);
3) слишком короткий набор (если предусмотрена минимально допустимая длина);

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Критерии-черного-ящика

21

4) набор минимально возможной длины (если такая предусмотрена);
5) нормальный набор (состоит из нескольких элементов);
6) набор из нескольких частей (если такое возможно. Например, если программа читает литеры из текстового файла
или печатает текст, то как она отнесется к переходу на следующую строку? На следующую страницу?);
7) набор максимально возможной длины (если такая предусмотрена);
8) слишком длинный набор (с длиной больше максимально допустимой).
Тестирование упорядоченности входных данных важно для
задач сортировки и поиска экстремумов. В этом случае имеет
смысл проверить следующие ситуации (классы входных данных):
1)
2)
3)
4)
5)
6)
7)
8)

данные неупорядочены;
данные упорядочены в прямом порядке;
данные упорядочены в обратном порядке;
в наборе имеются повторяющиеся значения;
экстремальное значение находится в середине набора;
экстремальное значение находится в начале набора;
экстремальное значение находится в конце набора;
в наборе несколько совпадающих экстремальных значений.

А сейчас сделаем паузу и вернемся к задаче тестирования
программы, анализирующей треугольники. Попробуйте посмотреть на составленный вами набор тестов с точки зрения критериев черного ящика. Дополните его, если в этом возникла необходимость.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Глава 7

Критерии белого ящика

Первый из критериев белого ящика — критерий покрытия
операторов. Он требует подобрать такой набор тестов, чтобы
каждый оператор в программе был выполнен хотя бы один раз.
В качестве примера рассмотрим следующий фрагмент Паскальпрограммы:
Пример 1
a:= 0;
if x>3 then a:= 10;
b:= 1/a;
Для того чтобы удовлетворить критерию покрытия операторов, достаточно одного выполнения. Такого, чтобы x был
больше 3. Очевидно, что ошибка в программе этим тестом обнаружена не будет. Она проявится как раз в том случае, когда
x £ 3. Но такого теста критерий покрытия операторов от нас
не требует.
Итак, мы имеем программу, оттестированную с точки зрения критерия покрытия операторов и при этом содержащую
ошибку. Попробуем разобраться, в чем дело. Для наглядности
перейдем с Паскаля на язык блок-схем.

Теперь причина видна сразу. Следуя критерию покрытия
операторов, мы проверили только положительную ветвь раз-

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Критерии-белого-ящика

23

вилки, но не затронули отрицательную. Сокращенная форма
условного оператора в Паскале этому весьма способствует.
Чтобы избавиться от указанного недостатка, введем второй
критерий белого ящика — критерий покрытия ветвей (иначе
его называют критерием покрытия решений). Он требует подобрать такой набор тестов, чтобы каждая ветвь в программе
была выполнена хотя бы один раз. Тестирование с точки зрения этого критерия обнаружит ошибку в предыдущем примере.
Рассмотрим другой пример. На Паскале он будет выглядеть
так:
Пример 2
a:= 7;
while a>x do a:= a-1;
b:= 1/a;
Мы уже знаем, что паскалевская запись может служить провокатором ошибок. Поэтому сразу составим блок-схему:

Для того чтобы удовлетворить критерию покрытия ветвей, в
данном случае достаточно одного теста. Например такого, чтобы x был равен 6 или 5. Все ветви программы будут пройдены
(при x = 5 одна из ветвей — тело цикла — даже 2 раза). Но
ошибка в программе обнаружена так и не будет! Она проявится
в одном-единственном случае, когда x = 0. Но такого теста от
нас критерий покрытия ветвей не потребовал.
Итак, мы имеем программу, оттестированную с точки зрения критерия покрытия ветвей и при этом содержащую ошиб-

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

24

Глава-7

ку. Причина в том, что некоторые ветви в программе могут
быть пройдены несколько раз, и результат выполнения зависит
от количества проходов. Для того чтобы учесть этот факт, введем третий критерий белого ящика — критерий покрытия путей. Он требует подобрать такой набор тестов, чтобы каждый
путь в программе был выполнен хотя бы один раз. Тестирование с точки зрения этого критерия обнаружило бы ошибку
в примере 2. Но из этого же примера виден принципиальный
недостаток данного критерия. Сколько всего путей возможно
в примере 2? Бесконечно много! Проверить их все невозможно.
Значит, как только в программе появляются циклы с пред- или
постусловием или цикл со счетчиком, но с вычисляемыми границами, количество путей в программе становится потенциально бесконечным, и критерий покрытия путей становится неприменимым. Необходим какой-то компромиссный критерий.
Более жесткий, чем покрытие ветвей, но менее жесткий, чем
покрытие путей. О нем мы поговорим в следующей главе.
Кроме проблем с проверкой циклов существенные проблемы
связаны с проверкой сложных условий — логических выражений, содержащих знаки дизъюнкции и/или конъюнкции.
Например:
if (a n.
Для того чтобы учесть подобные ситуации, были предложены следующие критерии:
— критерий покрытия условий;
— критерий покрытия решений/условий;
— критерий комбинаторного покрытия условий.
Критерий покрытия условий требует подобрать такой набор
тестов, чтобы каждое простое условие (слагаемое в дизъюнкции
и сомножитель в конъюнкции) получило и значение «истина»,
и значение «ложь» хотя бы один раз.
Критерий пытается «в лоб» исправить вышеуказанный недостаток в тестировании сложных условий. Однако сам оказывается

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Критерии-белого-ящика

25

весьма слаб. Дело в том, что выполнение критерия покрытия
условий не гарантирует покрытие ветвей. Пусть сложное условие
представляет собой дизъюнкцию двух слагаемых. Например,
if (ab

Тест 2

Тест 3

Тест 4

Тест 5

=0
=1
>1

Для каждого цикла с постусловием — 2 строки: для однократного и многократногоповторения тела цикла.
Тест 1
repeat
until a>b

Тест 2

Тест 3

Тест 4

Тест 5

=1
>1

Для каждого цикла со счетчиком — 3 строки: для нулькратного, однократного и многократного повторения тела цикла.
Тест 1
for k:=1 to n

Тест 2

Тест 3

Тест 4

Тест 5

=0
=1
>1

Если какая-либо из этих строк для данной программы не
имеет смысла (например, по условиям задачи тело цикла обязательно повторяется хотя бы один раз), соответствующая строка
все равно помещается в таблицу, но в ней записывается пояснение, что такая ситуация невозможна.
Для каждого оператора выбора записывается столько строк,
сколько он имеет ветвей (включая ветвь «иначе»):
Тест 1
case color

Тест 2

Тест 3

Тест 4

Тест 5

red
green
blue
else

Если в условном операторе, в цикле с пред- или постусловием стоит сложное условие, то к строкам, соответствующим самому оператору, добавляются по 2 строки на каждое простое
условие:

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Минимально-грубое-тестирование

29

Тест 1

Тест 2

Тест 3

Тест 4

Тест 5

Тест 1

Тест 2

Тест 3

Тест 4

Тест 5

+

if (a>b) and
(x=y)


+

a>b


+

x=y



while (c>d) or
(z=f)

=0
=1
>1
+

c>d


+

z=f



Для удобства ссылок все условия в таблице можно перенумеровать.
Каждая строка таблицы МГТ соответствует одной из ситуаций, которую надо проверить в процессе тестирования. Проверка может быть произведена одним или несколькими тестами.
Отметим в таблице те тесты, которые проверяют данную ситуацию. Для этого поставим плюс в ячейку, находящуюся на пересечении соответствующих строки и столбца.
Т1
У1

if (a>b) and
(x=y)

У2

a>b

У3

x=y

У4

while (a>b)
or (x=y)

У5

a>b

У6

x=y

+

+

+

Т3

+
+

+

+
+

+
+

+


=0
=1
>1
+

+


Т2

+
+
+
+
+
+

+
+
+

+
+
+
+
+

Т4

Т5

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

30

Глава-8

Такая запись означает, что при выполнении теста Т1 в развилке У1 выполнялась ветвь «то». При этом, естественно, условия У2 и У3 были истинны. Тело цикла У4 было повторено
1 раз. Условие У5 сначала было истинно, затем стало ложно.
Условие У6 было ложно все время.
При выполнении теста Т2 в развилке У1 выполнялась ветвь
«иначе». Произошло это за счет того, что условие У3 стало
ложным, хотя условие У2 осталось истинным. Цикл У4 повторялся более одного раза. Условие У5 при этом все время было
ложно. Условие У6 сначала было истинно, а затем стало ложным.
Тест Т3 придумывался уже прицельно, для того чтобы «закрыть» строку, соответствующую ложному значению условия
У2. Во всем остальном он перепроверяет то, что уже было проверено раньше.
Из таблицы видно, что плюс отсутствует в единственной
строке — строке, соответствующей нулькратному повторению
цикла У4. Это та ситуация, которая осталась непроверенной.
Значит, именно на проверку этой ситуации должен быть нацелен очередной тест.
Возможна ситуация, когда некоторая развилка данным тестом не затрагивается вообще. Например, в случае вложенных
условных операторов:
if a>b then … else if c=d then …
В случае выполнения внешнего оператора по ветви «то»,
вложенный оператор выполняться не будет вообще. В этом случае в МГТ-таблице просто остаются пустые ячейки:
Т1
У1

if a>b

У2

if c=d

Т2

Т3



+

+

+

+

+



Т4

Т5

+

+

В случае разбиения программы на процедуры, модули и т. п.
МГТ-таблицы строятся для каждой процедуры, модуля и пр.
Соответственно, тестирование проводится прицельно для данной процедуры, для данного модуля.
Критерий МГТ не идеален, как и все предыдущие. Он не гарантирует проверку комбинации всех простых условий. Он не
гарантирует надежную проверку циклов. Приведенный в преды-

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Минимально-грубое-тестирование

31

дущей главе пример 3, который «ломал» критерий решений/
условий, «сломает» и критерий МГТ. Выполнение требований
минимально грубого тестирования не гарантирует проверку всех
возможных комбинаций простых условий. Ошибка в примере 2,
выявление которой не гарантировали критерии покрытия ветвей, решений/условий и комбинаторного покрытия условий, может быть пропущена и критерием МГТ. Тем не менее критерий
МГТ представляется достаточно разумным компромиссом между
надежностью и сложностью тестирования.
Применение критерия МГТ связано с вопросом, который повергает в ужас новичков-программистов. Это вопрос о количестве «писанины», требуемой критерием. Неужели так уж необходимо строить и заполнять все эти таблицы? Ответ: да.
Особенно для новичков. Задача новичка добиться, чтобы соответствующие критерии перешли «в подкорку», «на уровень
подсознания», начали использоваться автоматически. А для
этого необходимо достаточно много раз применить их «врукопашную». Благо, первые учебные программы, с которых начинается курс программирования, достаточно просты, и МГТ-таблички для них совсем невелики. Кроме того, не возбраняется
для ведения МГТ-таблиц использовать компьютер, который
способен существенно упростить многие рутинные операции.
Как и в двух предыдущих разделах, с учетом полученных
знаний читателю еще раз предлагается вернуться к проверке
программы, анализирующей треугольники. Вдруг МГТ натолкнет на какие-то новые мысли по этому поводу.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Глава 9

Ошибкоопасные ситуации

Ни один из критериев полноты тестирования, описанных в
предыдущих главах и применимых на практике, не дает стопроцентной гарантии отсутствия ошибок. Поэтому в дополнение
к критериям рекомендуется еще один способ поиска ошибок
в программе. Это просмотр текста программы для выявления
и проверки ошибкоопасных ситуаций. Далее приводится список
таких ситуаций, на которые стоит обращать внимание при просмотре программы. Список, к сожалению, не исчерпывающий.
Каждый может продолжить его сам в соответствии с собственным опытом.

9.1. Îáðàùåíèå ê äàííûì
1. Использование значения переменной.
Опасность: Неинициализированная переменная. (Переменная используется до того, как ей было присвоено значение.)
Очень частая ошибка начинающих программистов. Признаком ее служит появление у переменных неожиданных «мусорных» значений (очень больших чисел, чисел с громоздкой дробной частью, строк, забитых «мусором», и пр.). К сожалению,
обнаруживать эту ошибку автоматически умеют очень немногие вычислительные системы.
2. Автоматическая инициализация переменных.
Опасность: Неверная инициализация.
Некоторые системы программирования автоматически заполняют выделяемую память стандартными значениями, чаще всего
нулями. Всегда или при включении соответствующего режима.
Проверьте, делает ли это ваша система. Использует ли она нужное вам значение? Можно ли управлять процессом инициализации? Если возможно, от данной услуги лучше отказаться. Сэкономите вы на ней мизер, зато рискуете не заметить ситуацию, когда
была необходима ручная инициализация, а вы о ней забыли.
Турбо Паскаль обнуляет память под переменные, описанные
в главной программе. Переменные, описанные в подпрограммах, не инициализируются и получают изначально некоторые
«мусорные» значения.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Ошибкоопасные-ситуации

33

3. Индексация массива.
Опасность: Выход индекса за границу измерения.
Как правило, существует возможность автоматического контроля выхода индекса за границу. Правда, начинающие программисты, уверенные в собственной гениальности, любят этот
контроль отключать. Ведь это позволит им сэкономить несколько микросекунд при выполнении программы, для которой
время выполнения вообще не играет никакой роли. Зато поиск
соответствующей ошибки превращается в увлекательную и,
часто, достаточно длительную процедуру.
В Турбо Паскале контроль индексации массивов регулируется двумя способами:
1) с помощью управляющих комментариев {$R+} и {$R–}.
Первый из них контроль включает, второй — выключает;
2) переключением опции компилятора Options/Compiler…/
Range checking (см. рис. 9.1).
По умолчанию контроль, к сожалению, выключен.

Рис. 9.1. Опции компилятора Турбо Паскаль

4. Изменение переменной внутри блока.
Опасность: Побочный эффект (изменение глобальной переменной при выполнении подпрограммы).
В языках с блочной структурой это не является ошибкой, но
требует повышенного внимания. Ведь по внешнему виду вызова процедуры или функции никак нельзя сказать, какие глобальные переменные будут изменены при ее выполнении.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

34

Глава-9

5. Использование значения ссылочной переменной.
Опасность: «Висячая ссылка».
Переменная ссылочного типа указывает на область памяти,
которая уже возвращена системе. Особенно неприятно, если эта
область уже будет перераспределена под другую переменную.
6. Схожие имена переменных.
Само по себе это не ошибка. Но может быть причиной описки. Соответственно требует повышенного внимания.
7. Использование записи с вариантами.
Опасность: Несколько полей записи с вариантами используются для обращения к одной и той же области памяти при отсутствии контроля за правильной типизацией.
Само по себе это не ошибка. Но может оказаться, что эти
поля имеют разные типы. В этом случае величина, записанная
как значение одного типа, может быть прочитана или откорректирована как значение другого типа. Пример см. на рис. 9.2.

Рис. 9.2. Нарушение типового контроля при обращении
к записи с вариантами

8. Использование нетипизированного указателя.
Опасность: Еще одна возможность обойти типовый контроль. Динамическая переменная, на которую ссылается данный указатель, в разных местах программы может трактоваться как переменная разных типов.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Ошибкоопасные-ситуации

35

9. Обращение к одной и той же области памяти из разных
модулей.
Само по себе это не ошибка. Но имеет смысл проверить,
все ли модули рассматривают переменную в одном и том же
смысле.
10. Использование статических переменных. Использование
типизированных констант в Турбо Паскале.
Опасность: Путаница между статическими и динамическими переменными.
Статические переменные создаются в единственном экземпляре и сохраняют значения между вызовами процедуры. Динамические переменные создаются при каждом входе в процедуру и уничтожаются при выходе из нее. Дело осложняется тем,
что в Турбо Паскале статические переменные официально отсутствуют, «спрятаны» под видом типизированных констант.
Пример см. на рис. 9.3.

Рис. 9.3. Типизированные константы (статические переменные)
в Турбо Паскале

11. Использование переменных, не имеющих явного описания.
Ряд языков не требует явного описания переменных. В любой точке программы вы можете вставить в текст программы
новое имя и тем самым создать новую переменную. Подобное
неявное описание требует повышенного внимания по трем причинам. Во-первых, оно опасно с точки зрения описок. Неверно
введенное имя будет просто воспринято как неявное описание
новой переменной. Во-вторых, имеет смысл уточнить, какой

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

36

Глава-9

тип приписывается по умолчанию автоматически создаваемой
переменной (если приписывается). В-третьих, стоит уточнить,
какова область действия (область видимости) автоматически созданной переменной.
К счастью, Паскаль неявных описаний не допускает.

9.2. Âû÷èñëåíèÿ
1. Выражение.
Опасность: Неверный порядок вычисления операций в выражении.
Как вы думаете, в каком порядке будет вычисляться выражение m < n or i = k? Математик будет ожидать дизъюнкции
двух сравнений. А в Паскале сначала будет выполнена дизъюнкция n or i, а потом ее результат будет сравниваться со
значением переменной m. К этому надо добавить, что в Турбо
Паскале (не в стандартном!) дизъюнкция применима к целым
числам.
Особенно опасно непонимание порядка выполнения операций в сочетании с отсутствием строгого типового контроля.
2. Логическое выражение.
Опасность: Путаница между полной и краткой схемами вычисления логических выражений.
Одно из отличий логических вычислений от арифметических в том, что возможных результатов всего два — «истина» и
«ложь». Поэтому очень часто результат выражения становится
ясен без выполнения всех вычислений. Если первое слагаемое в
дизъюнкции истинно или первый сомножитель в конъюнкции
ложен, остальные можно уже не вычислять. С точки зрения
математики совершенно неважно, прервем мы вычисления после первого слагаемого (сомножителя) или будем продолжать
дальше. Но в программировании это может быть важно. Например, если второй операнд — это вызов функции, которая
изменяет значения глобальных переменных или записывает
значения в файл: b and f(x). Если второй операнд будет вычисляться, глобальная переменная или файл будут изменены, не
будет — останутся прежними.
Краткая схема вычисления логических выражений означает, что вычисления будут прерваны, как только будет ясен результат. Полная — что выражение вычисляется до конца, независимо от промежуточных результатов. Схема вычисления

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Ошибкоопасные-ситуации

37

может быть зафиксирована в языке, а может регулироваться в
системе программирования.
В Турбо Паскале схема вычисления логических выражений
регулируется двумя способами:
1) с помощью управляющих комментариев {$B+} и {$B–}.
Первый из них включает полную схему вычисления логических выражений, второй — краткую;
2) переключением опции компилятора Options/Compiler…/
Complete Boolean eval (см. рис. 9.1).
По умолчанию устанавливается краткая схема.
К сожалению, переключение опции компилятора никак не
отражается в тексте программы. В результате в Турбо Паскале
появляется уникальная возможность писать программы, по
внешнему виду которых нельзя сказать, как они будут выполняться. Пример приведен на рис. 9.4 и 9.5. В логическом выражении (1 = 1) or f первое слагаемое всегда истинно. Значит,
дизъюнкция в целом будет истинна, независимо от второго слагаемого. При вычислении по краткой схеме оно вычисляться не
будет, при полной — будет. Но второе слагаемое — это вызов
логической функции, которая выдает сообщение («Вызов функции f») и меняет значение глобальной переменной m. В результате при краткой схеме сообщение выдано не будет, переменная m сохранит нулевое значение. При полной схеме сообщение
будет выдано и m станет равно единице. Выполнение одной и
той же программы дает разные результаты, хотя никаких изменений в тексте программы нет.

Рис. 9.4. Вычисление логического выражения по краткой схеме

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

38

Глава-9

Рис. 9.5. Вычисление логического выражения по полной схеме

3. Сравнения > / b then … else if a=b then … else {a и ³ , < и £ .
Опасность: Ошибки типа «±1».
Частая ошибка — путаница сравнений на строгое и нестрогое неравенство: > и ³ , < и £ . Если сравнение стоит в заголовке цикла, то он будет повторяться на один раз больше или
меньше требуемого при неправильном выборе знака сравнения.
Отсюда и название типа ошибки.
5. Деление.
Опасность: Деление на нуль.
Убедитесь (приведите правдоподобные рассуждения в пользу
того), что выражение в знаменателе не может оказаться равным нулю.
6. Извлечение квадратного корня.
Опасность: Корень из отрицательного числа.
Убедитесь, что аргумент не может получить недопустимое
значение.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Ошибкоопасные-ситуации

39

7. Взятие логарифма.
Опасность: Логарифм неположительного числа.
Убедитесь, что аргумент не может получить недопустимое
значение.
8. Использование в вычислениях данных «не того» типа.
Опасность: Неверное приведение типов данных.
Например, использование литерных значений в арифметических операциях или целочисленных значений — в логических. Строго типизированные языки такое запрещают. Но не
все языки строго типизированы. В слабо типизированных языках это не обязательно ошибка. Но имеет смысл уточнить, как
именно будут выполняться операции. Например, какой результат даст сумма 1 + ‘1’? Чему будет равна литерная единица:
целочисленной единице или коду литеры ‘1’?
Обратите внимание на то, что Турбо Паскаль (в отличие от
стандартного Паскаля) распространил логические операции на
целые числа. Попробуйте угадать, чему равно 724 or –5308.
9. Присваивание целой переменной вещественного значения.
Опасность: Способ преобразования вещественного числа в
целое.
Само по себе присваивание целой переменной вещественного значения может языком допускаться. При этом проводится
автоматическое преобразование вещественного числа в целое.
Проводиться оно может двумя путями: округлением или обрубанием дробной части. Какой способ будет использован в вашем случае? В зависимости от ответа на этот вопрос, следующий фрагмент даст разные результаты.
a: real;
n: integer;
...
a:= 0.9999;
n:= a;
...
В случае округления n получит значение 1, в случае обрубания — 0.
К счастью, Паскаль такое присваивание вообще запрещает.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

40

Глава-9

10. Вычисления с плавающей точкой (вещественная арифметика).
Опасность 1: Погрешности округления.
Машинные вычисления не всегда совпадают с арифметическими. В первую очередь это относится к вещественным значениям. Причин тому две. Во-первых, в машинной памяти числа
хранятся не в десятичном формате, а в двоичном. Преобразование выполняется с некоторым округлением. Во-вторых,
точность представления чисел в памяти ограничена. В результате вещественная единица вполне может оказаться равна 0,9999999999, а может — 1,0000000001 (количество знаков
после запятой зависит от точности представления). В качестве
примера приведем пару присваиваний:
a:= 1/3;
b:= a*3;
В математике b будет равно 1. В программировании для начала окажется
a=

1
1
1
1
1
1
1
1
+
+
+
+
+
+
+
4 16 64 256 1024 4096 16384 65536

и т. д. в зависимости от точности представления. Это не совсем
одна треть. В конце концов, наберется сумма, которая с заданной точностью даст a = 0,3333333333. Но тогда b = 0,9999999999,
а не 1.
Если таких значений в выражении будет несколько или выражение многократно перевычисляется в цикле, ошибка округления будет копиться. Автор этих строк сам как-то столкнулся
с ситуацией, когда сумма трех слагаемых, каждое из которых
было равно 0,1, оказалась больше 0,3! В программе на языке
Reduce тело цикла
for k:= 0 step 0.1 until 0.3 do …
было повторено только 3 раза. К счастью, в большинстве языков счетчик цикла не может быть вещественным. Но поскольку
в таких конструкциях есть объективная необходимость, его
приходится моделировать. Поэтому о накоплении погрешностей надо помнить при любых вещественных вычислениях.
Пример см. на рис. 9.6.
Опасность 2: Потеря значимости (получение чисел с очень
маленькой мантиссой).
Слишком маленькая мантисса в машинной арифметике может превратиться в нуль. Пример см. на рис. 9.7.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Ошибкоопасные-ситуации

41

Рис. 9.6. Накопление погрешности при вещественных вычислениях

Рис. 9.7. Потеря значимости

11. Сравнение вещественных чисел.
Опасность: Погрешности округления.
Погрешности округления чреваты ошибками не только
в арифметических операциях (о чем уже было сказано). В операциях сравнения они могут привести к тому, что «лобовое
сравнение» вещественных чисел даст неверный результат.
Пусть вещественные переменные q и r обе равны единице. Но
в одном случае вещественная единица будет представлена как

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

42

Глава-9

0,9999999999, а в другом — как 1,0000000001. В этом случае
сравнение q = r вполне может дать значение «ложь». Лучше
сравнивать модуль разности с некоторым e : abs(q - r) < eps.
12. Арифметические вычисления (как вещественные, так и
целые).
Опасность 1: Переполнение (получение очень больших чисел).
Еще одна особенность машинной арифметики: слишком большие числа ей противопоказаны. В лучшем случае произойдет аппаратное прерывание, но возможны более неприятные случаи,
когда сумма двух больших положительных целых чисел окажется числом отрицательным или положительным, но маленьким.
В Турбо Паскале вещественное переполнение всегда вызывает аппаратное прерывание. Что касается целочисленного переполнения, то контроль за ним регулируется. Делается это, как
обычно в Турбо Паскале, двумя способами:
1) с помощью управляющих комментариев {$Q+} и {$Q–}.
Первый из них включает контроль целочисленного переполнения, второй — отключает;
2) переключением опции компилятора Options/Compiler…/
Overflow checking (см. рис. 9.1).
По умолчанию контроль целочисленного переполнения отключен.
Примеры переполнений приведены на рис. 9.8–9.10. На
рис. 9.8 при вычислении цикла произошло прерывание из-за
вещественного переполнения.

Рис. 9.8. Вещественное переполнение

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Ошибкоопасные-ситуации

43

Рис. 9.9. Целочисленное переполнение
при отключенном контроле

На рис. 9.9 программа выполняется при отключенном контроле целочисленного переполнения. Переполнение происходит
при вычислении переменной n. Вычислительная система на
него никак не реагирует. На примере хорошо видно, как меняется значение целочисленной переменной n в результате многократного умножения:
100 × 100 = 10 000,
10 000 × 100 = 16 960,
16 960 × 100 = -7 936,
-7 936 × 100 = -7 168
и т. д.
На рис. 9.10 та же самая программа выполняется при
включенном контроле. В этом случае целочисленное переполнение приводит к аппаратному прерыванию так же, как и вещественное.
Надо подчеркнуть, что диапазон значений типа integer
в Турбо Паскале весьма невелик: от -32 768 до 32 767. Но в
Турбо Паскале (в отличие от стандартного Паскаля) существует еще несколько целых типов, как большего, так и меньшего размера. В частности, тип longint охватывает значения от -2 147 483 648 до 2 147 483 647.
Опасность 2: Переполнение или потеря значимости в промежуточных вычислениях.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

44

Глава-9

Рис. 9.10. Целочисленное переполнение
при включенном контроле

Конечный результат может иметь нормальный вид, но промежуточные могут оказаться слишком большими или слишком
маленькими. Естественно, о правильности конечного результата в данном случае говорить не приходится. Для машинной
арифметики порядок выполнения операций может оказаться
весьма существенным. Это в математике a * b / c = a / c * b.
В программировании — не всегда. Пример см. на рис. 9.11.

Рис. 9.11. Переполнение при промежуточных вычислениях

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Ошибкоопасные-ситуации

45

9.3. Ïåðåäà÷à óïðàâëåíèÿ
1. Развилки.
Опасность: Пропущена ветвь «иначе».
Этот момент особенно важен для оператора выбора. Что будет делать ваша программа, если значение выражения после
case не соответствует ни одному из перечисленных вариантов?
2. Вложенные условные операторы.
Опасность: Сочетание if-then-if-then-else.
Как понимать запись: if C1 then if C2 then S1 else S2?
Возможны два варианта:
1) if C1 then (if C2 then S1 else S2)
2) if C1 then (if C2 then S1) else S2
или в структурной записи:
1) if C1 then
if C2 then S1
else S2
2) if C1 then
if C2 then S1
else S2
В первом случае внешний оператор является сокращенным:
if C1 then, а вложенный — полным: if C2 then S1
else S2.
Во втором случае внешний оператор является полным: if
C1 then … else S2, а вложенный — сокращенным: if C2
then S1.
Как будет трактовать эту запись ваш язык программирования? Паскаль трактует ее первым способом: приписывает else
к ближайшему предшествующему if.
Отметим, что в данном случае плохую услугу может оказать
неправильная структурная запись. Для Паскаля абзацные отступы и пробелы роли не играют, для человека — играют.
Запись
if C1 then
if C2 then S1
else S2
у человека создает впечатление, что ветвь else S2 относится
к оператору if C1 then. Но Паскаль-то отнесет ее к оператору
if C2 then S1!

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

46

Глава-9

Дополнительного внимания требует корректировка программы. Пусть изначально был записан абсолютно правильный текст:
if C1 then
if C2 then S1
else S3
else S2
Потом в процессе корректировки программы ветвь else S3
была исключена. Получилось:
if C1 then
if C2 then S1
else S2
При этом Паскаль автоматически переставит ветвь else S2
из оператора if C1 then в оператор if C2 then S1.
Чтобы избежать подобных неприятностей, не следует после
then писать сокращенных условных операторов. Если это необходимо, то либо такие операторы должны быть преобразованы
в полные, приписыванием пустой части «иначе»:
if C1 then
if C2 then S1
else {not C2 – íè÷åãî}
else S2
либо сокращенный условный оператор должен быть заключен
в операторные скобки begin—end:
if C1 then begin
if C2 then S1
end
else S2
3. Циклы.
Опасность: Зацикливание.
Убедитесь, что, в конце концов, каждый из циклов будет
завершен. Если это цикл с пред- или постусловием, то при выполнении тела цикла должна меняться хотя бы одна из переменных, входящих в условие. Соблюдение этого требования не
гарантирует выхода из цикла, но без него зацикливание неизбежно. Если это цикл со счетчиком, не меняйте счетчик цикла
«вручную» при вычислении тела цикла, даже если язык программирования и позволяет это сделать.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Ошибкоопасные-ситуации

47

Обратите внимание на то, что Турбо Паскаль позволяет
«вручную» менять счетчик цикла в теле цикла. В результате
появляется возможность зацикливания для циклов, в которых
границы заданы константами. Например, так:
for k:= 1 to 5 do k:= 2;
Остановить зациклившуюся программу в Турбо Паскале
можно нажатием комбинации клавиш Ctrl+Break.

9.4. Ïîäïðîãðàììû
1. Вызов подпрограммы.
Опасность 1: Неверное количество параметров.
Убедитесь, что количество фактических параметров соответствует количеству формальных. Неприятности могут возникнуть в случае раздельной трансляции.
Опасность 2: Неверные типы параметров.
Убедитесь, что типы фактических параметров согласуются с
типами соответствующих формальных параметров. Неприятности могут возникнуть в случае раздельной трансляции.
Опасность 3: Неверный порядок следования параметров.
Почти во всех языках программирования соответствие между фактическими и формальными параметрами устанавливается по номеру параметра (первый фактический соответствует
первому формальному, второй фактический — второму формальному и т. д.). Убедитесь, что порядок следования фактических параметров соответствует порядку следования формальных. Повышенного внимания требует ситуация, когда подряд
идут несколько параметров одного типа. В этом случае вам не
сможет помочь типовый контроль.
2. Использование параметров внутри подпрограммы.
Опасность: Неверные единицы измерения параметров.
Убедитесь, что во всех программных модулях для одного и
того же параметра используется одна и та же единица измерения (углы могут измеряться в градусах, а могут в радианах, размеры — в сантиметрах и дюймах и т. д.). Сложность в том, что
почти во всех языках программирования единица измерения не
записывается явно в тексте программы, а подразумевается.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

48

Глава-9

3. Использование формального параметра внутри подпрограммы для изменения значения соответствующего фактического параметра.
Опасность: Неверный способ передачи параметров.
В различных языках программирования используется около
десятка способов передачи параметров в процедуру. Однако
большинство языков ограничивается одним-двумя способами.
Самые ходовые — это передача параметров по значению и по
ссылке (в Паскале это называется «параметры-константы» и
«параметры-переменные» соответственно). Формальный параметр, передаваемый по значению, представляет собой локальную переменную, которая получает начальное значение из
фактического параметра. После этого всякая связь между
формальным и фактическим параметрами разрывается. Формальный параметр может быть изменен внутри процедуры
(с этой точки зрения паскалевский термин «параметр-константа» неудачен), но это изменение никак не повлияет на соответствующий фактический параметр. Если формальный параметр
передается по ссылке, соответствующий фактический параметр
обязательно должен быть переменной. Формальный параметр
в этом случае представляет собой еще одно имя этой переменной. Все действия, задаваемые для формального параметра, на
самом деле выполняются с соответствующим фактическим параметром. В частности, все изменения формального параметра
являются изменениями параметра фактического.
Если вы хотите, чтобы изменения, произведенные в процедуре с формальным параметром, отразились вне процедуры на
фактическом параметре, такой параметр надо передавать по
ссылке. Наоборот, если изменения формального параметра не
должны выйти наружу, такой параметр надо передавать по
значению.
Еще одна опасность связана с тем, что некоторые языки программирования позволяют передавать по ссылке константы.
Вообще говоря, изменение такого формального параметра
должно приводить к изменению соответствующей константы.
Следующая программа должна напечатать число 8!
procedure p(var x: integer);
begin x:= x + 1; end; {p}
begin p(7); write(7) end.
К счастью, в большинстве языков (в том числе, в Паскале)
такое запрещено.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Ошибкоопасные-ситуации

49

4. Использование внутри подпрограммы формальных параметров, передаваемых по ссылке.
Опасность: Одна и та же переменная может быть указана
как фактический параметр одновременно для нескольких формальных параметров, передаваемых по ссылке.
Поскольку формальный параметр, передаваемый по ссылке, — это просто другое имя для переменной, являющейся фактическим параметром, возникает ситуация, когда к одной и той
же переменной процедура будет обращаться сразу по нескольким именам. Все изменения, выполняемые с одним из формальных параметров, будут реально выполняться сразу со всеми.
Само по себе это не ошибка, но требует повышенного внимания.
В качестве примера рассмотрим следующую ситуацию.
Пусть у нас есть процедура mulmatr(a,b,c) с тремя параметрами-матрицами, которая занимается умножением своего первого параметра на свой второй параметр. Результат заносится
в третий параметр. Пусть из соображений экономии места и
времени все три параметра передаются по ссылке. И пусть, наконец, мы решили воспользоваться этой процедурой для возведения в квадрат некоторой матрицы X. Старое ее значение нам
больше не нужно, поэтому новую матрицу записываем прямо
поверх старой. Если бы X было числом, мы написали бы оператор присваивания X: = X* X. В случае матриц естественным кажется вызов процедуры mulmatr, в котором всем трем параметрам соответствует матрица X:
mulmatr(X, X, X).
Представьте себе, как будет выполняться умножение. Сначала
через первую строку параметра a и первый столбец параметра b
будет посчитан элемент c11 . Затем через первую строку параметра a и второй столбец параметра b начнется подсчет элемента c12 .
Но дело в том, что элемент c11 является в то же время и элементом a 11 , и элементом b11 . То есть, изменяя результирующую матрицу C, мы изменим и матрицы-сомножители. Понятно, что
остальные элементы матрицы C будут подсчитаны неверно.
5. Использование внутри подпрограммы глобальной переменной и параметра, передаваемого по ссылке.
Опасность: Одновременный доступ к одной и той же переменной через параметр, передаваемый по ссылке, и через глобал.
Пусть некоторая переменная из объемлющего блока доступна внутри процедуры как глобальная. Пусть она изменяется

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

50

Глава-9

Рис. 9.12. Доступ к одной и той же переменной через параметр,
передаваемый по ссылке, и как к глобальной переменной

внутри этой процедуры. Пусть у этой процедуры есть параметр,
передаваемый по ссылке. Пусть при вызове данной процедуры
в качестве фактического параметра для передачи по ссылке
указана та самая переменная, которая является глобальной.
Тогда возникает ситуация, когда к одной и той же переменной
мы обращаемся одновременно по двум разным именам: по имени глобальной переменной и по имени формального параметра.
Пример см. на рис. 9.12.
После первого вызова процедуры p обе переменные получат значение 4. Внутри процедуры переменная m была доступна как глобал, а переменная n как параметр, передаваемый по ссылке. После второго вызова переменная n сохранит
значение 1 (ее вызов процедуры вообще не затрагивает), переменная m станет равна 16. Внутри процедуры m и x — это два
имени одного и того же объекта. Любое изменение m является
в то же время изменением x, а любое изменение x — изменением m.
Само по себе такое «двухименное» обращение к переменной
не ошибка, но требует повышенного внимания.

9.5. Ôàéëû
1. Запись/чтение двоичных файлов.
Опасность: Путаница между файлами разных типов.
Паскалевские файлы (и файлы во многих других языках)
могут быть текстовые (специфицированы стандартным типом

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Ошибкоопасные-ситуации

51

TEXT) и двоичные (описаны как file of T). Каждой файловой
переменной должен быть поставлен в соответствие набор данных (файл) на диске. Обратите внимание на то, что в файле на
диске не сохраняется информация о том, какого типа данные в
нем записаны. Поэтому вы можете сначала поставить в соответствие этому файлу файловую переменную типа file of real,
записать в него несколько вещественных чисел, закрыть, а потом опять открыть, но уже поставив ему в соответствие файловую переменную типа file of integer. С этим файлом можно
будет выполнять любые операции, но его прежнее содержимое — двоичный код нескольких вещественных чисел — будет
рассматриваться как код целых чисел. Программа, демонстрирующая этот эффект, приведена ниже.
var freal: file of real;
fint: file of integer;
r: real;
k, i: integer;
begin
assign(freal,'f.dat');
rewrite(freal);
r:= 3.1415926;
write(freal,r)
reset(freal);
while not eof(freal) do begin
read(freal,r);
writeln(r:12:8)
end;
close(freal);
assign(fint,'f.dat');
reset(fint);
while not eof(fint) do begin
read(fint,i);
writeln(i)
end;
end.
Результат ее работы будет выглядеть следующим образом:
3.14159260
-26750
-9624
18703

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

52

Глава-9

2. Создание файлов с данными с помощью текстового редактора. Просмотр файлов с данными с помощью текстового
редактора.
Опасность: Путаница текстовых и двоичных файлов.
Файл, создаваемый с помощью текстового редактора (например, в среде Турбо Паскаль), будет файлом текстовым. В Паскале он соответствует стандартному типу TEXT. Даже если вы записали туда последовательность целых чисел, открывать его
как file of integer не следует. В текстовом файле будет
храниться последовательность литер, с помощью которых вы
изобразили целые числа, а в file of integer должно храниться их внутреннее представление, соответствующий двоичный код.
И наоборот. Если файл записан из программы как двоичный,
корректный просмотр его с помощью текстового редактора невозможен. В лучшем случае вы увидите некоторый набор самых
разных символов (отнюдь не цифр). В худшем вы или сам текстовый редактор что-нибудь в нем измените, после чего прежнее содержимое файла окажется испорченным.
3. Запись/чтение нетипизированных файлов.
Опасность: Еще одна возможность обойти контроль типов.
Данные могут быть записаны в файл как значения одного типа,
а прочитаны как значения другого.
4. Запись в файл.
Опасность: Отсутствие явного закрытия файлов.
Если вы не закрыли явно файл перед окончанием работы
программы, есть шанс, что содержимое последнего заполненного буфера не будет вытолкнуто на диск. Это значит, что результаты последних операций записи могут в файл не попасть.
Не возникло ли у читателя желания еще раз вернуться к задаче о проверке программы для анализа треугольников? С учетом понимания тех ошибок, которые могут быть допущены при
составлении этой программы.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Глава 10

Безмашинное тестирование

Для большинства начинающих программистов тестирование
подразумевает непременный прогон программы на компьютере.
И напрасно. На большинство людей экран терминала оказывает гипнотическое воздействие. Человек, сидящий перед монитором, соображает с трудом. А при тестировании и отладке соображать совершенно необходимо!
Особенно важно безмашинное тестирование для новичков.
Только ручная прокрутка программы позволит вам понять, как
же работает ваша программа.
Но разве можно вести тестирование без помощи компьютера? Можно. И весьма плодотворно. Как показывает опыт, безмашинное тестирование обнаруживает от 30 до 80% ошибок в
профессиональных программах и до 100% в учебных. Существуют следующие варианты безмашинного тестирования.
1. «Сухая прокрутка».
Вы пытаетесь вручную смоделировать работу машины при
выполнении программы. Вам при этом достается роль центрального процессора. А роль оперативной памяти будет играть
трассировочная таблица. В этой таблице будет своя графа для
каждой переменной, а порядок присваивания значений будет
отображаться выравниванием значений по вертикали. Например, для следующей программы
program P;
var k, n: integer;
sum: integer;
begin
writeln('ß ñ÷èòàþ ñóììó êâàäðàòîâ ÷èñåë îò 1 äî 5');
n:= 5;
sum:= 0;
for k:= 1 to n do
sum:= sum + k*k;
writeln('Ñóììà êâàäðàòîâ ÷èñåë îò 1 äî ',n,' = ',sum)
end. {p}

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

54

Глава-10

трассировочная таблица будет выглядеть так:
n

sum

k

5
0
1
1
2
5
3
14
4
30
5
55

Замечание. Сдвиг по вертикали в разных графах трассировочной таблицы удобней делать на полстроки.
2. «Символическая прокрутка».
На этот раз вам не надо подставлять конкретные значения и
вести трассировку переменных. Вместо этого необходимо шаг за
шагом анализировать ход программы. Каждый раз, когда переменная получает значение, вы должны убедиться, что это значение соответствует смыслу переменной (для этого, как минимум,
надо точно знать смысл каждой переменной), найти те условия,
при которых вычисления дадут сбой (см. главу 9 «Ошибкоопасные ситуации»), убедиться, что в программе отражены все возможные особые случаи.
3. Объяснение коллеге.
Расчет в данном случае делается не на то, что коллега много
умнее вас, а на то, что объяснение другому человеку отличается от рассуждений про себя. В ваших рассуждениях о программе обязательно присутствуют некоторые допущения и предположения. Когда вы рассуждаете о программе сами с собой,
многие из них остаются не проговоренными и явно не сформулированными. Когда вы то же самое начинаете вслух объяснять постороннему человеку, вы вынуждены явно сформулиро-

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Безмашинное-тестирование

55

вать все допущения. И очень может быть, что в процессе этой
формулировки сами и обнаружите неточность2.
4. Передача программы коллеге для самостоятельного изучения.
В данном случае опять расчет не на то, что коллега много
умнее вас. Мы уже упоминали особенность человеческой психики, которая заключается в том, что человеческий глаз видит
не то, что есть на самом деле, а то, что человек хочет. Соответственно возможно, что при изучении своей программы вы видите не то, что она делает на самом деле, а то, что вам хотелось
бы, чтобы она делала. Есть надежда, что ваш коллега подобным пристрастиям будет не подвержен и увидит в тексте программы то, что там на самом деле написано.
5. Просмотр текста программы с анализом ошибкоопасных
мест.
Об этом речь шла в предыдущей главе.
6. Искусственное внесение ошибок в программу.
Естественно, вносить ошибки должен посторонний человек
(или программа), и место ошибок должно держаться в секрете
от человека, занимающегося тестированием.
Очень мощный прием. Прежде всего, с психологической
точки зрения. Мы уже говорили, что многие программисты
склонны отождествлять себя со своей программой и поэтому не
склонны выискивать в этой программе огрехи. После внесения
в программу искусственных ошибок этот психологический стопор снимается. Ошибки-то не мои! Более того, настроение меняется на прямо противоположное. У меня была такая прекрасная программа, а эти злодеи ее испортили своими ошибками!
Ну, я им покажу, как портить мои программы!

2

Вообще говоря, с этой точки зрения примерно все равно, кому объяснять программу: соседу по комнате или кошке.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Глава 11

Пример тестирования
несложной программы

Проиллюстрируем все вышесказанное достаточно простым
примером. Пусть требуется написать программу, которая считает среднее арифметическое последовательности целых чисел,
заканчивающейся нулем.
Для начала отметим, чего делать ни в коем случае не следует. Не надо, еще не дослушав задание, бросаться к монитору
компьютера и быстро-быстро набирать что-нибудь вроде:
Program p;
var a, b, c: integer:
begin
repeat
read(a);
b:= b + 1;
c:= c + a
until a=0;
writeln(c/b)
end.
Сначала всегда полезно подумать.
Согласно принципу 6, перед началом тестирования надо
сформулировать цели тестирования, выбрать критерии полноты. Зафиксируем в качестве цели: провести тестирование, полное с точки зрения критериев черного ящика и критерия МГТ.
Согласно принципу 5, разработка тестов должна предшествовать написанию программы и первоначально должна быть
направлена на уяснение поставленной задачи. Начнем, естественно, с критериев черного ящика (критерии белого ящика
пока неприменимы за отсутствием текста программы).
Первый критерий — тестирование функций — нам в данном
случае неинтересен, поскольку программа у нас однофункциональная. Эту единственную функциюмы и будем постоянно тестировать.
Далее на очереди тестирование классов входных и выходных
данных, областей допустимых значений, длины последовательности, упорядоченности набора данных. Выделение классов
входных и выходных данных в нашей задаче не очевидно. Об-

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Пример-тестирования-несложной-программы

57

ластей допустимых значений — пока тоже. Упорядоченность
нам не важна. А вот проверка длины последовательности имеет
к нам непосредственное отношение.
Пойдем по порядку.
1. Пустой набор (не содержит ни одного элемента).
Входные данные? Стоп. Вопрос на уточнение: как ввести пустую последовательность? Вы можете сами придумать ответ на
этот вопрос. Но вы не должны этого делать! Ваша цель не додумывать самому непонятные места, а понять, чего хочет от вас заказчик. Поэтому этот вопрос следует задать постановщику задачи.
Ответ: пустая последовательность — это последовательность,
состоящая из одного нуля. Нуль элементом последовательности
не является. Количество элементов в такой последовательности
считается равным нулю.
С входными данными теперь ясно: 0.
Выходные данные? Опять стоп. Что должна выдавать программа в ответ на пустую последовательность? Опять же, вы
можете сами придумать ответ на этот вопрос. Но вы не должны
этого делать! Это опять вопрос постановщику задачи. Ответ:
в случае пустой последовательности результатом должна быть
фраза «Последовательность пуста».
Вот теперь мы можем сформулировать первый тест.
Вход: 0.
Ожидаемый выход: Последовательность пуста.

2. Единичный набор (состоит из одного-единственного элемента).
Вход: 4, 0.
Ожидаемый выход: 4.

3. Слишком короткий набор.
Для нас такого понятия нет.
4. Набор минимально возможной длины.
Для нас такого понятия нет.
5. Нормальный набор (состоит из нескольких элементов).
Для определенности возьмем набор из 3 элементов.
Вход: 1, 3, 4, 0.
Выход: Стоп!

В математике 8/3 = 2,(6). Оказывается, среднее арифметическое последовательности целых чисел может быть числом вещественным. На всякий случай уточним у заказчика, должны

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

58

Глава-11

ли мы выдавать вещественный результат или должны преобразовывать его к целому. Если преобразовывать, то как: округлять или обрубать? Если вещественный, то с какой точностью?
Повторим, что вы можете сами придумать ответы на все эти вопросы, но не должны делать это. Пусть отвечает заказчик. Его
ответ: среднее арифметическое выдается как вещественное
число с той точностью, которую обеспечит ваш компьютер.
Что ж. Ответ для нас весьма удобный. Для определенности,
пусть будет 6 знаков после запятой. Итак:
Вход: 1, 3, 4, 0.
Ожидаемый выход: 2.666667.

6. Набор из нескольких частей (если такое возможно).
В нашем случае это невозможно.
7. Набор максимально возможной длины (если такая предусмотрена).
Не предусмотрена.
8. Слишком длинный набор (с длиной больше максимально
допустимой).
И это для нас невозможно.
Итак, что мы получили? Во-первых, мы построили 3 теста.
Во-вторых, мы уточнили несколько важных деталей (что такое пустая последовательность, как на нее реагировать, какого типа должен быть результат, с какой точностью он должен
выдаваться). Наши тесты покрывают классы выходных данных («Последовательность пуста» и число, являющееся средним арифметическим) и классы входных (3 вида последовательностей, различающихся длиной).
Запишем 3 наших теста в таблицу следующего формата:
Тест

Вход

Т1

0

Т2

4, 0

Т3

1, 3, 4, 0

Ожидаемый
результат

+/—
Результат
Результат +/—
выполнения на
«сухой
компьютере
прокрутки»

Последовательность пуста
4
2.666667

Пока что мы заполним в ней только первые строки и в каждой из них — первые графы. Строки нам, возможно, придется

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Пример-тестирования-несложной-программы

59

добавить, исходя из критериев белого ящика и анализа ошибкоопасных мест. Две средние графы заполним по результатам
безмашинного тестирования, две последние — по результатам
выполнения на компьютере. В графах «+/–» будем отмечать совпадение или несовпадение реального результата с ожидаемым.
Теперь можно переходить к составлению текста программы.
Идея проста. Среднее арифметическое — это частное от деления суммы элементов на их количество. Значит надо ввести
элементы, просуммировать их, посчитать их количество, после
чего напечатать частное. Например, так:
Программа на псевдокоде

begin
âûäàòü íà÷àëüíîå ïðèâåòñòâèå;
ââåñòè 1-é ýëåìåíò (tek);
while ïîñëåäîâàòåëüíîñòü íå êîí÷åíà do begin
óâåëè÷èòü ñóììó (sum);
óâåëè÷èòü êîëè÷åñòâî (kol)
end;
âûâåñòè ñðåäíåå àðèôìåòè÷åñêîå (sum/kol)
end.

Примечание

var tek: integer;
var sum: integer;
var kol: integer;

Упомянутые в проекте фрагменты раскроем следующим образом:
Ââåñòè 1-é ýëåìåíò
writeln('Ââåäèòå, ïîæàëóéñòà, 1-é ýëåìåíò');
read(tek);
Ïîñëåäîâàòåëüíîñòü íå êîí÷åíà
tek0
Óâåëè÷èòü ñóììó
sum:= sum + tek;
Óâåëè÷èòü êîëè÷åñòâî
kol:= kol + 1;
Первый вариант программы будет выглядеть так:
program SrednArifm;
var tek, kol, sum: integer;
begin
writeln('ß ñ÷èòàþ ñðåäíåå àðèôìåòè÷åñêîå' +
'ïîñëåäîâàòåëüíîñòè ÷èñåë, êîí÷àþùåéñÿ íóëåì');

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

60

Глава-11

writeln('Ââåäèòå, ïîæàëóéñòà, 1-é ýëåìåíò');
read(tek);
while tek0 {ïîñëåäîâàòåëüíîñòü íå êîí÷åíà} do begin
sum:= sum + tek; {óâåëè÷èòü ñóììó ýëåìåíòîâ}
kol:= kol + 1;
{óâåëè÷èòü êîë-âî ýëåìåíòîâ}
end;
if kol=0 then writeln('Ïîñëåäîâàòåëüíîñòü ïóñòà')
else writeln('Ñðåäíåå àðèôìåòè÷åñêîå=',sum/kol)
end.
Заметим, что худо-бедно, но мы позаботились не только о
функциональности программы, но и об удобстве пользователя.
Это, конечно, не windows-интерфейс, но, во всяком случае,
пользователю не придется сидеть перед пустым черным экраном и гадать, что же за программу он запустил и что ему делать дальше.
Кроме того, первым пользователем любой программы является ее разработчик. Значит, надо позаботиться о его удобстве.
Здесь существует некоторое противоречие. Разработчику приходится выполнять с текстом программы два противоположных действия: писать и читать. Для удобства записи текст
должен быть кратким, для удобства чтения — подробным. Что
выбрать? Текст записывается только один раз, а читается много. Значит, решение надо принимать в пользу чтения. Для
упрощения чтения могут использоваться осмысленные имена
переменных, абзацные отступы (запись лесенкой), комментарии.
Продолжим составление тестов. На этот раз с использованием критериев белого ящика, конкретней — МГТ. Строим таблицу минимально грубого тестирования. В нашей программе всего две развилки: цикл с предусловием и ветвление. В обоих
случаях используются простые условия. Значит, МГТ-таблица
будет содержать всего 5 строк:
Т1
У1

while tek0

=0
=1
>1

У2

if kol=0

+


Т2

Т3

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Пример-тестирования-несложной-программы

61

Мы уже имеем 3 теста, построенные исходя из критериев
черного ящика. Посмотрим, как они поведут себя на нашей
программе и как отразятся в таблице МГТ. Для этого вручную
выполним первый тест и заполним трассировочную таблицу.
Вход первого теста состоит из единственного числа — нуль. Оно
и будет введено как значение переменной tek. Занесем его в
трассировочную таблицу:
tek

kol

sum

0

Тело цикла while не выполняется ни разу. Сразу переходим на условный оператор. Равна ли переменная kol нулю?
Моделью оперативной памяти для нас является трассировочная таблица. Там и ищем ответ. Но… В трассировочной таблице для переменной kol не указано вообще никакого значения! Мы забыли ее инициализировать! kol — это количество
элементов последовательности. Значит, изначально, пока никакой последовательности еще нет, kol должна быть равна
нулю. Вставим соответствующий оператор перед вводом первого элемента последовательности. Тело программы приобретет вид:
begin
writeln('ß ñ÷èòàþ …');
kol:= 0;
writeln('Ââåäèòå, ïîæàëóéñòà, 1-é ýëåìåíò');
read(tek);
while tek0 do begin
sum:= sum + tek;
kol:= kol + 1;
end;
if kol=0 then writeln('Ïîñëåäîâàòåëüíîñòü ïóñòà')
else writeln('Ñðåäíåå àðèôìåòè÷åñêîå = ', sum/kol)
end.
Повторим первый тест. Теперь его удастся выполнить до
конца. Трассировочная таблица будет иметь вид:
tek

kol
0

0

sum

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

62

Глава-11

В таблице тестов отметим результат выполнения первого теста:
Тест

Вход

T1

0

T2

4, 0

T3

1, 3, 4, 0

Ожидаемый
результат

Результат
«сухой
прокрутки»

Последователь- Последовательность пуста
ность пуста

+/–

+/–
Результат
выполнения на
компьютере

+

4
2.666667

В таблице МГТ отметим проверенные строки:
Т1
У1

=0

while tek0

Т2

Т3

+

=1
>1
У2

+

if kol=0

+



Перейдем ко второму тесту. Выполняем программу и заполняем трассировочную таблицу.
tek

kol

sum

0
4

После входа в цикл спотыкаемся на том же, на чем уже
споткнулись в предыдущий раз. Переменная sum не имеет начального значения. Ее, так же как и переменную kol, необходимо инициализировать нулем перед входом в цикл. Получаем
следующий текст тела программы:
begin
writeln('ß ñ÷èòàþ …');
kol:= 0;
sum:= 0;
writeln('Ââåäèòå, ïîæàëóéñòà, 1-é ýëåìåíò');
read(tek);
while tek0 do begin
sum:= sum + tek;
kol:= kol + 1;
end;
if kol=0 then writeln('Ïîñëåäîâàòåëüíîñòü ïóñòà')
else writeln('Ñðåäíåå àðèôìåòè÷åñêîå=',sum/kol)
end.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Пример-тестирования-несложной-программы

63

Теперь выполнение второго теста пойдет веселее. По крайней мере, поначалу.
tek

kol

sum

0
0
4
4
1
8
2
12
3

Уже можно остановиться. Понятно, что что-то не так. Что
именно? Воспользуемся перечнем ошибкоопасных ситуаций.
У нас проблемы с циклом, произошло именно то, о чем нас и
предупреждали: зацикливание. Чтобы его избежать, в теле
цикла, как минимум, должна меняться хотя бы одна переменная, входящая в условие цикла. У нас такая переменная всего
одна, и она в теле цикла не меняется! Мы забыли ввести
остальные элементы последовательности! Исправим ошибку.
Теперь тело программы приобретет вид:
begin
writeln('ß ñ÷èòàþ …');
kol:= 0;
sum:= 0;
writeln('Ââåäèòå, ïîæàëóéñòà, 1-é ýëåìåíò');
read(tek);
while tek0 do begin
sum:= sum + tek;
kol:= kol + 1;
writeln('Ââåäèòå, ïîæàëóéñòà, ýëåìåíò ¹ ',kol);
read(tek);
end;
if kol=0 then writeln('Ïîñëåäîâàòåëüíîñòü ïóñòà')
else writeln('Ñðåäíåå àðèôìåòè÷åñêîå = ',sum/kol)
end.
Заметим, что мы опять в меру сил позаботились об удобстве
пользователя. Во-первых, он получит приглашение к вводу очередного элемента, во-вторых, это приглашение будет содержать
номер элемента.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

64

Глава-11

Выполняем еще раз второй тест и наконец-то доходим до
конца.
tek

kol

sum

0
0
4
4
1
0

По ходу выполнения выясняем, что мы допустили еще одну
маленькую ошибку. Правда, не в вычислениях, а в интерфейсе.
Программа второй раз попросила нас ввести элемент № 1. Приглашение в теле цикла должно выглядеть так:
writeln('Ââåäèòå, ïîæàëóéñòà, ýëåìåíò ¹ ',kol+1);
Внесем в текст соответствующие изменения, на результатах
выполнения это не скажется.
Сделаем отметку в таблице тестов.
Тест

Вход

T1

0

T2

4, 0

T3

1, 3, 4, 0

Ожидаемый
результат

Результат
«сухой
прокрутки»

Последователь- Последовательность пуста
ность пуста
4

4

+/–

+/–
Результат
выполнения на
компьютере

+
+

2.666667

Заполним соответствующий столбец в таблице МГТ.
Т1
У1

while tek0

=0

Т2

Т3

+

=1

+

>1
У2

if kol=0

+


+
+

С тестом 2 вроде бы все. Но поскольку мы внесли в программу исправления, необходимо повторить все ранее прогнанные
тесты. Нам это просто, поскольку такой тест был всего один.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Пример-тестирования-несложной-программы

65

При его повторе результаты получим прежние, хотя трассировочная таблица несколько изменится:
tek

kol

sum

0
0
0

Добрались до теста 3. Строим трассировку.
tek

kol

sum

0
0
1
1
1
3
4
2
4
8
3
0

Заполняем таблицы тестов и МГТ.
Тест

Вход

Ожидаемый
результат

T1

0

T2

4, 0

T3

1, 3, 4, 0

У1

while tek0

Результат
«сухой
прокрутки»

Последователь- Последовательность пуста
ность пуста

+/–

+

4

4

+

2.666667

2.666667

+

Т1
=0

+/–
Результат
выполнения на
компьютере

Т2

=1

+

>1
У2

if kol=0

+


Т3

+

+
+
+

+

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

66

Глава-11

Третий тест оказался неудачен — никакой ошибки не выявил. Зато оказалось, что все строки таблицы МГТ уже заполнены. Значит, мы построили и прогнали набор тестов, полный
и с точки зрения критериев черного ящика, и с точки зрения
минимально грубого тестирования. Значит, ручное тестирование пора заканчивать.
Теперь можно выходить на компьютер и прогнать еще раз те
же тесты уже через него. Никаких неожиданностей мы при
этом не встретим и сможем заполнить крайние правые графы
в таблице тестов:
Тест

Вход

T1

0

T2

4, 0

T3

1, 3, 4, 0

Ожидаемый
результат

Результат
«сухой
прокрутки»

Последователь- Последовательность пуста
ность пуста

+/–

+

+/–
Результат
выполнения на
компьютере
Последовательность пуста

+

4

4

+

4

+

2.666667

2.666667

+

2.666667

+

Перед окончанием данной главы сделаем 3 замечания:
1. Тестов, построенных исходя из критериев черного ящика,
оказалось достаточно, чтобы удовлетворить критерию МГТ.
2. При этом тесты эти (построенные в тот момент, когда текста программы еще не существовало) оказались настолько
хороши, что первые 2 из них обнаружили в нашей программе 3 ошибки. И только последний третий тест оказался неудачен, ошибок не выявил.
3. Легко заметить, что все допущенные нами ошибки упоминались в перечне ошибкоопасных ситуаций. Значит, если
бы мы вместо того, чтобы сразу же начинать тестовые
прогоны, сначала проанализировали нашу программу
с точки зрения этого перечня, ошибки удалось бы обнаружить гораздо быстрей и с меньшими усилиями. Это
урок на будущее!

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Глава 12

Порядок работы над программой

Из примера, разобранного в предыдущей главе, виден порядок работы над программой.
1. Начинаем с критериев черного ящика. Соответствующие
тесты составляем до написания текста программы. Тесты
заносим в таблицу тестов, в которой кроме входных данных и ожидаемых результатов предусмотрена графа для
реальных результатов и отметки о совпадении их с ожидаемыми.
2. Пишем текст программы.
3. Проверяем его, исходя из списка ошибкоопасных конструкций и ситуаций.
4. Составляем таблицу МГТ.
5. Прогоняем тесты, составленные исходя из критериев черного ящика. В процессе прогона строим трассировочные
таблицы.
6. При необходимости корректируем программу. Проверяем
места корректировок на ошибкоопасность.
7. После корректировки программы проводим повторное тестирование: повторяем заново все ранее прогнанные тесты.
8. Заполняем последние графы таблицы тестов и таблицу МГТ.
9. Если таблица МГТ еще неполна, добавляем тесты для покрытия незаполненных строк таблицы МГТ. Заносим их
в таблицу тестов.
10. Прогоняем их через программу. Строим трассировки.
11. При необходимости корректируем программу. Проверяем
места корректировок на ошибкоопасность.
12. Заполняем последние графы таблицы тестов и таблицу МГТ.
13. После корректировки программы проводим повторное тестирование: повторяем заново все ранее прогнанные тесты.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Глава 13

Нисходящее тестирование

Программирование методом пошаговой детализации (программирование сверху вниз) подразумевает написание программы на
псевдокоде и постепенный перевод ее на язык программирования.
При этом программа может достаточно долго включать в себя
фрагменты на псевдокоде. С точки зрения системы программирования, такую программу исполнять нельзя. Можно ли такую программу тестировать? Да, можно. Прежде всего, можно (и нужно)
использовать безмашинные методы, о которых шла речь выше.
Но кроме того, можно организовать выполнение такой программы на компьютере. Для этого используются так называемые заглушки3. Заглушка представляет собой имитатор еще не разработанной части программы. Она может быть подставлена вместо
подпрограммы или фрагмента, написанного на псевдокоде. В качестве заглушек могут использоваться:
— пустые операторы;
— операторы печати;
— упрощенные варианты имитируемых частей (например,
вместо выполнения сложных вычислений заглушка процедуры может присваивать выходным параметрам некоторые стандартные значения).
Пустой оператор в качестве заглушки требует минимальных
усилий, но и пользу приносит минимальную. Заглушка в виде
оператора печати усилий требует ненамного больших, а для
слежения за работой программы гораздо полезнее. В качестве
печатаемого текста удобно выдавать название имитируемого
фрагмента. Если имитируется работа подпрограммы, полезно
распечатать значения ее параметров. Надо позаботиться, чтобы операторы заглушки по внешнему виду отличались от
«нормальных» операторов программы, а сообщения, выдаваемые заглушкой, — от «нормальных» сообщений программы.
Например, операторы заглушки будем помечать специальным комментарием, включающим в себя два диеза (##). Любое
сообщение, выдаваемое заглушкой, будем начинать со специального маркера: двух диезов (##).
3

В английском языке используется термин «stub» — обрубок.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Нисходящее-тестирование

69

В качестве примера использования заглушек приведем следующий текст:
procedure Obrabotka(m, n: integer; var c: char; var x: real);
begin
{##} c:= '+'; x:= 0.5; {êîíñòàíòû âìåñòî âû÷èñëåíèé}
{##} writeln('##Obrabotka. m=',m,' n=',n,' c=',c,' x=',x);
end; {Obrabotka}
begin
writeln('## Èíèöèàëèçàöèÿ äàííûõ');
writeln('## Ââîä äàííûõ');
Obrabotka(7, 4, s, z);
Obrabotka(14, -3, d, y);
writeln('## Çàâåðøåíèå');
end.
При выполнении такой программы каждый из выполняемых фрагментов сообщит о том, что он проработал. Процедура
Obrabotka, кроме того, присвоит выходным параметрам некоторые стандартные значения и распечатает значения входных
и выходных параметров.
Нисходящее тестирование обладает рядом достоинств и недостатков.
Достоинства нисходящего тестирования:
1) возможность раннего начала тестирования;
2) ранняя проверка сопряжений между модулями;
3) более тщательная проверка модулей верхнего уровня.
Недостатки нисходящего тестирования:
1) трудно тестировать модули нижнего уровня. На выполнение подается программа целиком. Вход/выход для модуля нижнего уровня опосредован работой модулей верхнего
уровня. Приходится таким образом подбирать значения
вводимых в программу переменных, чтобы тестируемая
процедура была вызвана именно с теми значениями параметров, которые нужны для тестирования;
2) необходимо тратить силы на написание заглушек;
3) заглушка — это все-таки имитатор, упрощенный вариант.
Поэтому тестирование на заглушках оказывается неполным. После замены их реальным программным текстом тестирование приходится повторять;
4) заглушки можно подставить только вместо процедур и операторов. Нет заглушек для переменных и типов.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

70

Глава-13

Первый недостаток можно преодолеть, если отказаться от
строго нисходящего порядка работы. Для отладки модулей
нижнего уровня пишут специальные модули верхнего уровня,
так называемые драйверы4. Назначение этих модулей:
1)
2)
3)
4)

установка нужного для тестирования контекста;
загрузка исходных данных теста;
вызов тестируемых подпрограмм;
обработка результатов тестирования.

Для языков с независимой трансляцией подпрограмм драйвер оформляется в виде отдельной программной единицы. Для
языков с блочной структурой дело обстоит сложнее. Для тестирования вложенных подпрограмм приходится менять тело главной программы и объемлющих блоков.

4

Сейчас термин «драйвер» прочно ассоциируется с управлением внешними
устройствами. Но исторически впервые он появился именно в тестировании.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Глава 14

*Оценка количества ошибок
5
в программе

Сколько ошибок в программе? Это вопрос, который волнует
и каждого программиста, и каждого заказчика. Особую актуальность придает ему принцип «кучкования ошибок», согласно
которому нахождение в некотором модуле ошибки увеличивает
вероятность того, что в этом модуле есть и другие ошибки.
Точного ответа на вопрос о количестве ошибок в программе
очень часто дать невозможно, а вот построить некоторую оценку — можно. Для этого существуют нескольких статистических моделей, которые мы и рассмотрим в данной главе.
Заметим, что даже приблизительное знание количества
ошибок в программе может быть очень полезно для формулирования цели тестирования и составления планов тестирования.

14.1. Ìîäåëü Ìèëëñà
В 1972 г. суперпрограммист фирмы IBM Харлан Миллс
предложил следующий способ оценки количества ошибок в
программе.
Пусть у нас есть программа. Предположим, что в ней N ошибок. Назовем их «естественными». Внесем в нее дополнительно
M «искусственных» ошибок. Проведем тестирование программы. Пусть в ходе тестирования было обнаружено n естественных ошибок и m искусственных. Предположим, что вероятность обнаружения для естественных и искусственных ошибок
одинакова. Тогда выполняется соотношение:
n
m
=
.
N M
Мы нашли один и тот же процент естественных и искусственных ошибок. Отсюда количество ошибок в программе
M
N = n ) . Количество необнаруженных ошибок равно N - n.
m
5

Глава отмечена звездочкой. Это означает, что некоторые ее части могут показаться недостаточно подготовленному читателю слишком сложными. Можете пропустить такие части (если это допускается вашей учебной программой). Чтение последующих глав не требует обязательного знания материала
данного раздела.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

72

Глава-14

Например, пусть в программу было внесено 20 искусственных ошибок, в ходе тестирования было обнаружено 12 искусственных и 7 естественных ошибок. Получим следующую оценку
количества ошибок в программе:
N =7×

20 70
=
» 12.
12
6

Количество необнаруженных ошибок равно N - n = 12 - 7 = 5.
Сам Миллс предлагал по ходу тестирования постоянно строить график для оценки количества ошибок в программе.
Легко заметить, что в описанном выше способе Миллса есть
один существенный недостаток. Если мы найдем 100% искусственных ошибок, это будет означать, что и естественных ошибок мы нашли 100%. Но чем меньше мы внесем искусственных
ошибок, тем больше вероятность того, что мы найдем их все.
Внесем единственную искусственную ошибку, найдем ее и на
этом основании объявим, что нашли все естественные ошибки!
Чтобы противостоять этому искусу, Миллс добавил вторую
часть модели, предназначенную для проверки гипотезы о величине N. Выглядит она так.
Предположим, что в программе N естественных ошибок.
Внесем в нее еще M искусственных ошибок. Будем тестировать
программу до тех пор, пока не найдем все искусственные ошибки. Пусть к этому моменту найдено n естественных ошибок. На
основании этих чисел вычислим величину
ì1
ï
C=í
M
ïî M + N + 1

при n > N,
при n £ N.

Величина C выражает меру доверия к модели. Это вероятность того, что модель будет правильно отклонять ложное предположение. Например, пусть мы считаем, что естественных
ошибок в программе нет (N = 0). Внесем в программу 4 искусственные ошибки. Будем тестировать программу, пока не обнаружим все искусственные ошибки. Пусть при этом мы не обнаружим ни одной естественной ошибки. В этом случае мера
доверия нашему предположению (об отсутствии ошибок в программе) будет равна 80% (4 / (4 + 0 + 1)). Для того чтобы довести
ее до 90%, количество искусственных ошибок придется поднять до 9. Следующие 5% уверенности в отсутствии естествен-

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Оценка-количества-ошибок-в-программе

73

ных ошибок обойдутся нам в 10 дополнительных искусственных ошибок. M придется довести до 19.
Если мы предположим, что в программе не более 3 естественных ошибок (N = 3), внесем в нее 6 искусственных
(M = 6), найдем все искусственные и одну, две или три (но
не больше!) естественных, то мера доверия к модели будет
60% (6 / (6 + 3 + 1)).
Далее в таблице 1 приведены значения функции C для различных значений N и M от 0 до 20 с шагом 1, в таблице 2 —
для N и M от 0 до 100 с шагом 5.
Модель допускает динамическую подстройку. Например,
пусть в предыдущем примере была найдена только одна естественная ошибка. Тогда удобно понизить N с 3 до 1. В результате степень доверия к модели возрастет до 75%. С другой
стороны, если в процессе тестирования было найдено 4 естественные ошибки, гипотезу об N = 3 придется заменить на гипотезу об N = 4. А это сразу же понизит меру доверия к модели c 60% до 55%.
Верхнюю строку в определении величины C можно интерпретировать следующим образом. Если мы предположили, что
в программе не более N ошибок, а обнаружили их больше N, то
с вероятностью 100% мы опровергнем гипотезу о том, что ошибок в программе не более N.
Из формулы для вычисления меры доверия легко получить
формулу для вычисления количества искусственных ошибок,
которые необходимо внести в программу для получения нужной уверенности в полученной оценке:
M=

C(N + 1)
.
1-C

Далее в таблице 3 приведены значения функции M для различных значений N от 0 до 20 с шагом 1 и C от 0 до 99 с шагом 5 (C выражено в процентах), в таблице 4 — для N от 0 до
100 с шагом 5 и С от 0 до 99 с шагом 5. Поскольку при C = 100%
функция M невычислима, последнее значение C берется равным 99%.
Обращают на себя внимание резкие скачки при переходе C
от 90% к 95% и, особенно, от 95% к 99%.
Модель Миллса достаточно проста. Ее слабое место — предположение о равновероятности нахождения ошибок. Чтобы это
предположение оправдалось, процедура внесения искусственных ошибок должна обладать определенной степенью «интеллекта».

60

80

82

83

85

90

90

88

89

90

91

92

92

93

93

94

94

94

95

95

95

7

8

9

10

11

12

13

14

15

16

17

18

19

20

71

91

89

89

88

88

87

86

78

75

83

86

6

67

5

75

80

3

4

50

67

2

33

50

1

0

1

0

0

0

M

87

86

86

85

84

83

82

81

80

79

77

75

73

70

67

63

57

50

40

25

0

2

83

83

82

81

80

79

78

76

75

73

71

69

67

64

60

56

50

43

33

20

0

3

80

79

78

77

76

75

74

72

71

69

67

64

62

58

55

50

44

38

29

17

0

4

77

76

75

74

73

71

70

68

67

65

63

60

57

54

50

45

40

33

25

14

0

5

74

73

72

71

70

68

67

65

63

61

59

56

53

50

46

42

36

30

22

13

0

6

71

70

69

68

67

65

64

62

60

58

56

53

50

47

43

38

33

27

20

11

0

7

69

68

67

65

64

63

61

59

57

55

53

50

47

44

40

36

31

25

18

10

0

8

67

66

64

63

62

60

58

57

55

52

50

47

44

41

38

33

29

23

17

9

0

9

65

63

62

61

59

58

56

54

52

50

48

45

42

39

35

31

27

21

15

8

0

10

N

63

61

60

59

57

56

54

52

50

48

45

43

40

37

33

29

25

20

14

8

0

11

61

59

58

57

55

54

52

50

48

46

43

41

38

35

32

28

24

19

13

7

0

12

59

58

56

55

53

52

50

48

46

44

42

39

36

33

30

26

22

18

13

7

0

13

57

56

55

53

52

50

48

46

44

42

40

38

35

32

29

25

21

17

12

6

0

14

56

54

53

52

50

48

47

45

43

41

38

36

33

30

27

24

20

16

11

6

0

15

54

53

51

50

48

47

45

43

41

39

37

35

32

29

26

23

19

15

11

6

0

16

Таблица 1. Мера доверия к миллсовой модели в зависимости
от гипотезы о количестве естественных ошибок (N) и количества внесенных
искусственных ошибок (M) для N и M от 0 до 20 с шагом 1, %

53

51

50

49

47

45

44

42

40

38

36

33

31

28

26

22

18

14

10

5

0

17

51

50

49

47

46

44

42

41

39

37

34

32

30

27

24

21

17

14

10

5

0

18

50

49

47

46

44

43

41

39

38

35

33

31

29

26

23

20

17

13

9

5

0

19

49

48

46

45

43

42

40

38

36

34

32

30

28

25

22

19

16

13

9

5

0

20

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

89

90

91

92

92

93

97

97

98

98

98

98

98

98

99

99

99

99

99

99

99

30

35

40

45

50

55

60

65

70

75

80

85

90

95

100

94

94

94

93

93

88

87

85

83

81

77

71

95

94

15

63

96

91

10

45

25

83

5

0

5

20

0

0

0

M

90

90

89

89

88

87

86

86

85

83

82

80

78

76

73

69

65

58

48

31

0

10

86

86

85

84

83

82

81

80

79

77

76

74

71

69

65

61

56

48

38

24

0

15

83

82

81

80

79

78

77

76

74

72

70

68

66

63

59

54

49

42

32

19

0

20

79

79

78

77

75

74

73

71

70

68

66

63

61

57

54

49

43

37

28

16

0

25

76

75

74

73

72

71

69

68

66

64

62

59

56

53

49

45

39

33

24

14

0

30

74

73

71

70

69

68

66

64

63

60

58

56

53

49

45

41

36

29

22

12

0

35

71

70

69

67

66

65

63

61

59

57

55

52

49

46

42

38

33

27

20

11

0

40

68

67

66

65

63

62

60

59

57

54

52

49

47

43

39

35

30

25

18

10

0

45

66

65

64

63

61

60

58

56

54

52

50

47

44

41

37

33

28

23

16

9

0

50

N

64

63

62

60

59

57

56

54

52

50

47

45

42

38

35

31

26

21

15

8

0

55

62

61

60

58

57

55

53

52

50

47

45

42

40

36

33

29

25

20

14

8

0

60

60

59

58

56

55

53

51

50

48

45

43

41

38

35

31

27

23

19

13

7

0

65

58

57

56

54

53

51

50

48

46

44

41

39

36

33

30

26

22

17

12

7

0

70

57

56

54

53

51

50

48

46

44

42

40

37

34

32

28

25

21

16

12

6

0

75

55

54

53

51

50

48

46

45

43

40

38

36

33

30

27

24

20

16

11

6

0

80

Таблица 2. Мера доверия к миллсовой модели в зависимости
от гипотезы о количестве естественных ошибок (N) и количества внесенных
искусственных ошибок (M) для N и M от 0 до 100 с шагом 5, %

54

52

51

50

48

47

45

43

41

39

37

34

32

29

26

23

19

15

10

5

0

85

52

51

50

48

47

45

43

42

40

38

35

33

31

28

25

22

18

14

10

5

0

90

51

50

48

47

45

44

42

40

38

36

34

32

29

27

24

21

17

14

9

5

0

95

50

48

47

46

44

43

41

39

37

35

33

31

28

26

23

20

17

13

9

5

0

100

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

0

0

0

0

0

0

0

1

10

15

20

25

30

35

6

9

19

99

85

90

95

99

6

2

70

3

2

65

4

2

60

75

1

55

80

5

1

50

1

45

198

38

18

11

8

4

3

2

2

2

1

1

40

1

1

1

1

0

0

5

0

1

0

0

0

C, %

297

57

27

17

12

9

7

6

5

4

3

2

2

2

1

1

1

1

0

0

0

2

396

76

36

23

16

12

9

7

6

5

4

3

3

2

2

1

1

1

0

0

0

3

495

95

45

28

20

15

12

9

8

6

5

4

3

3

2

2

1

1

1

0

0

4

594

114

54

34

24

18

14

11

9

7

6

5

4

3

3

2

2

1

1

0

0

5

693

133

63

40

28

21

16

13

11

9

7

6

5

4

3

2

2

1

1

0

0

6

792

152

72

45

32

24

19

15

12

10

8

7

5

4

3

3

2

1

1

0

0

7

891

171

81

51

36

27

21

17

14

11

9

7

6

5

4

3

2

2

1

0

0

8

990

190

90

57

40

30

23

19

15

12

10

8

7

5

4

3

3

2

1

1

0

9

1089

209

99

62

44

33

26

20

17

13

11

9

7

6

5

4

3

2

1

1

0

10

N

1188

228

108

68

48

36

28

22

18

15

12

10

8

6

5

4

3

2

1

1

0

11

1287

247

117

74

52

39

30

24

20

16

13

11

9

7

6

4

3

2

1

1

0

12

1386

266

126

79

56

42

33

26

21

17

14

11

9

8

6

5

4

2

2

1

0

13

1485

285

135

85

60

45

35

28

23

18

15

12

10

8

6

5

4

3

2

1

0

14

1584

304

144

91

64

48

37

30

24

20

16

13

11

9

7

5

4

3

2

1

0

15

1683

323

153

96

68

51

40

32

26

21

17

14

11

9

7

6

4

3

2

1

0

16

1782

342

162

102

72

54

42

33

27

22

18

15

12

10

8

6

5

3

2

1

0

17

Таблица 3. Количество искусственных ошибок, которые необходимо внести
в программу для достижения нужной меры доверия к миллсовой модели в зависимости
от гипотезы о количестве естественных ошибок (N) для N от 0 до 20 с шагом 1

1881

361

171

108

76

57

44

35

29

23

19

16

13

10

8

6

5

3

2

1

0

18

1980

380

180

113

80

60

47

37

30

24

20

16

13

11

9

7

5

4

2

1

0

19

2079

399

189

119

84

63

49

39

32

26

21

17

14

11

9

7

5

4

2

1

0

20

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

1

1

3

3

0

0

0

0

0

0

5

10

15

20

25

30

6

9

19

99

90

95

99

2

70

85

18

2

65

3

2

60

4

1

55

75

1

50

80

14

1

45

594

114

54

34

24

11

9

7

6

5

4

1

1

35

40

2

2

0

0

0

0

5

0

C,
%

1089

209

99

62

44

33

26

20

17

13

11

9

7

6

5

4

3

2

1

1

0

10

1584

304

144

91

64

48

37

30

24

20

16

13

11

9

7

5

4

3

2

1

0

15

2079

399

189

119

84

63

49

39

32

26

21

17

14

11

9

7

5

4

2

1

0

20

2574

494

234

147

104

78

61

48

39

32

26

21

17

14

11

9

7

5

3

1

0

25

3069

589

279

176

124

93

72

58

47

38

31

25

21

17

13

10

8

5

3

2

0

30

3564

684

324

204

144

108

84

67

54

44

36

29

24

19

15

12

9

6

4

2

0

35

4059

779

369

232

164

123

96

76

62

50

41

34

27

22

18

14

10

7

5

2

0

40

4554

874

414

261

184

138

107

85

69

56

46

38

31

25

20

15

12

8

5

2

0

45

5049

969

459

289

204

153

119

95

77

62

51

42

34

27

22

17

13

9

6

3

0

50

N

5544

1064

504

317

224

168

131

104

84

68

56

46

37

30

24

19

14

10

6

3

0

55

6039

1159

549

346

244

183

142

113

92

75

61

50

41

33

26

20

15

11

7

3

0

60

6534

1254

594

374

264

198

154

123

99

81

66

54

44

36

28

22

17

12

7

3

0

65

7029

1349

639

402

284

213

166

132

107

87

71

58

47

38

30

24

18

13

8

4

0

70

7524

1444

684

431

304

228

177

141

114

93

76

62

51

41

33

25

19

13

8

4

0

75

8019

1539

729

459

324

243

189

150

122

99

81

66

54

44

35

27

20

14

9

4

0

80

8514

1634

774

487

344

258

201

160

129

105

86

70

57

46

37

29

22

15

10

5

0

85

Таблица 4. Количество искусственных ошибок, которые необходимо внести
в программу для достижения нужной меры доверия к миллсовой модели в зависимости
от гипотезы о количестве естественных ошибок (N) для N от 0 до 100 с шагом 5

9009

1729

819

516

364

273

212

169

137

111

91

74

61

49

39

30

23

16

10

5

0

90

9504

1824

864

544

384

288

224

178

144

117

96

79

64

52

41

32

24

17

11

5

0

95

9999

1919

909

572

404

303

236

188

152

123

101

83

67

54

43

34

25

18

11

5

0

100

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

78

Глава-14

Еще одно слабое место — это требование второй части миллсовой модели отыскать непременно все искусственные ошибки.
А этого может не произойти долго, может быть и никогда. Существует модификация модели, позволяющая учесть нахождение только части искусственных ошибок. Вычисление C там
проводится по более громоздкой формуле, и рассматривать ее
здесь мы не будем.

14.2. «Ïàðíàÿ» îöåíêà
Следующая модель требует тестирования программы двумя независимыми специалистами (или группами специалистов). Зато не требует искусственного внесения ошибок в программу.
Пусть программу тестируют независимо друг от друга две
группы специалистов. Предположим, что в программе содержится N ошибок. Пусть первая группа нашла N1 ошибок, а
вторая — N 2 . Часть ошибок обнаружена обеими группами.
Пусть таких ошибок N12 . Изобразим соответствующие множества на схеме.

Эффективность работы групп оценим через процент обнаруженных ими ошибок:
E1 =

N1
,
N

E2 =

N2
.
N

Обнаружение всех ошибок считаем равновероятным. Тогда
верно следующее рассуждение (оно довольно громоздко, и, возможно, его придется, не торопясь, перечитать пару раз).
В силу равновероятности нахождения любой из ошибок любое случайным образом выбранное подмножество из N можно
рассматривать как аппроксимацию всего множества N. Это зна-

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Оценка-количества-ошибок-в-программе

79

чит, что если первая группа обнаружила 10% всех ошибок, она
должна обнаружить примерно 10% ошибок из любого случайным образом выбранного подмножества. В качестве такового
случайным образом выбранного подмножества возьмем множество ошибок, найденных второй группой. Доля всех ошибок,
N
найденных первой группой, равна 1 . Доля ошибок, найденN
ных первой группой среди тех ошибок, которые были найдены
N
второй группой, равна 12 . Согласно нашимрассуждениям,
N2
эти две величины должны быть равны:
N1 N12
=
.
N
N2

N1 × N 2
.
N12
Количество ненайденных ошибок равно (N - N1 - N 2 + N12 ).

Отсюда количество ошибок в программе: N =

Например, пусть первая группа нашла 8 ошибок, вторая — 9.
Обеими группами были найдены 3 ошибки. Тогда количест8×9
во ошибок в программе N =
= 24. Из них уже найдено
3
(8 + 9 - 3) = 14. Осталось найти еще 10.

14.3. Èñòîðè÷åñêèé îïûò
Еще один способ оценить количество ошибок в программе —
использовать опыт предыдущих программ. Оценка эта не очень
надежная, однако, больше, чем ничего. В литературе встречаются следующие оценки. После написания текста программы
в ней содержится (в среднем) 4–8 ошибок на 100 операторов.
После окончания автономной отладки модулей в программе
остается 1 ошибка на 100 операторов. Последняя оценка дается
со ссылкой на фирму IBM. Заметим, что в фирме IBM работают
профессионалы. Для новичков такая оценка может оказаться
слишком оптимистичной.
Для крупных длительных проектов некоторые фирмыразработчики строят специальные модели, отражающие специфику именно этого проекта. Так, в процессе работы над
уже упоминавшейся ОС/360 фирма IBM использовала для
оценки числа ошибок в системе формулу:
N = 2 . ИМ + 23 . МИМ.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

80

Глава-14

Здесь
N — полное число исправлений из-за ошибок,
ИМ — число исправляемых модулей,
МИМ — число многократно исправляемых модулей.
Многократно исправляемыми считались модули, которые
потребовали 10 или более исправлений. ИМ оценивалось как
90% новых модулей и 15% старых, МИМ — как 15% новых
модулей и 6% старых. При подстановке этих оценок формула
получает вид
Nиспр. = 2 . (0,9 . Nнов.мод. + 0,15 . Nстар.мод.) +
+ 23 . (0,15 . N
+ 0,06 . N
).
нов.мод.

стар.мод.

Таким образом, если в вашей системе уже содержится 140
модулей и в процессе обновления предстоит добавить еще 20, то
количество ошибок, которое при этом будет обнаружено, оценивается следующим образом:
2 . (0,9 . 20 + 0,15 . 140) + 23 . (0,15 . 20 + 0,06 . 140) =
= 2 . (18 + 21) + 23 . (3 + 8,4) =
.
= 2 39 + 23 . 11,4 = 78 + 262,2 = 340,2.
Можно ожидать 340 ошибок. В 18 из 20 новых модулей придется внести хотя бы одно исправление, в 3 модуля — не менее
десятка. Из старых модулей хотя бы один раз придется исправить 21 модуль, а 8 или 9 модулей — не менее 10 раз.
Стоит сделать два замечания. Во-первых, очевидно, что описанная модель годится только для достаточно крупных проектов. Во-вторых, разрабатывалась она для конкретной системы.
Не факт, что ее можно напрямую применять в других случаях.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Глава 15

*Оценка количества необходимых
6
тестов

В предыдущей главе мы рассмотрели модели, которые позволяют оценить количество ошибок в программе. А сколько
тестов понадобится, чтобы обнаружить эти ошибки?
В [3] для ответа на этот вопрос предлагается следующая модель. Утверждается, что вероятность успешности очередного
теста зависит от процента оставшихся ошибок. Последние
ошибки обнаружить сложнее. (Заметим, что данное утверждение вполне соответствует опыту.)
Пусть Tn — среднее количество тестов, необходимых для обнаружения n ошибок, N — число ошибок в программе. Для
оценки Tn предлагается следующая формула:
n -1
1
Tn = a å
.
k=0 N - k
Здесь a — некий коэффициент, значение которого надо определять экспериментально. Но мы заниматься этим не будем, поскольку формула для вычисления Tn будет интересовать нас не
сама по себе, а лишь как база для последующих рассуждений.
Мы хотим оценить количество тестов, которое потребуется для
нахождения всех N ошибок. Посчитать напрямую TN мы не можем, поскольку не знаем коэффициента a. Чтобы исключить его
из вычислений, перейдем от абсолютных величин к относительным. Попробуем оценить, какую часть от всех необходимых тестов придется выполнить для того, чтобы найти первые n ошибок.
n -1
n -1
1
1

å
T
N - k k= 0 N - k
P = n = Nk=-01
= N -1
.
1
1
TN

å
k= 0 N - k
k= 0 N - k
Теперь осталось подставить в формулу для P конкретные значения N и n. Результаты — очень интересные — представлены
в таблицах 5 и 6. В таблице 5 N изменяется от 10 до 100, в таб6

Глава отмечена звездочкой. Это означает, что некоторые ее части могут показаться недостаточно подготовленному читателю слишком сложными. Можете пропустить такие части (если это допускается вашей учебной программой). Чтение последующих глав не требует обязательного знания материала
данного раздела.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

82

Глава-15

лице 6 — от 1 до 10. Последняя таблица построена специально
для новичков, программы которых настолько малы по размерам, что в них просто не найдется места для сотни ошибок.
Таблица 5. Средний процент тестов,
необходимых для обнаружения заданного процента ошибок
(в зависимости от общего числа ошибок в программе)
Общее число ошибок в программе (N)

Процент
найденных
ошибок
(n/N)

10

20

30

40

50

60

70

80

90

100

10

3

3

3

2

2

2

2

2

2

2
4

20

7

6

5

5

5

5

5

4

4

30

11

10

9

8

8

8

7

7

7

7

40

16

14

13

13

11

11

10

10

10

10

50

22

19

17

16

15

15

14

14

14

13

60

29

24

22

21

20

19

19

18

18

18

70

37

32

29

27

26

25

25

24

23

23

80

49

42

39

36

35

34

33

32

31

31

90

66

58

54

51

49

48

46

45

44

44

100

100

100

100

100

100

100

100

100

100

100

Оказывается, что при N = 10 первые 22% тестов обнаружат
половину всех ошибок (5 штук). Для того чтобы обнаружить
две следующие ошибки, количество тестов придется увеличить
в 1,7 раза — до 37%. Поиск следующих двух ошибок потребует
увеличения количества тестов еще в 1,8 раза — до 66%. И наконец, поиск последней ошибки потребует оставшихся 34% тестов.
Чем больше ошибок в программе, тем дороже обойдется поиск
последних ошибок. При N = 50 для обнаружения 50% ошибок достаточно 15% тестов, 70% ошибок будут найдены с помощью
26%, 90% ошибок — с помощью 49% тестов. Поиск последних
10% ошибок будет стоить дороже, чем поиск первых 90%!
При увеличении количества ошибок с 10 до 100 стоимость поиска последних 10% ошибок возрастает с 34% тестов до 66%.
Заметим, что речь идет не об абсолютном числе тестов, а о
их доле. Поскольку программа, содержащая 100 естественных
ошибок, скорее всего, сложнее программы, в которой ошибок
только 10, есть основания полагать, что общее число тестов
также будет возрастать. То есть придется брать больший процент от большего числа тестов.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Оценка-количества-необходимых-тестов

83

В таблице 6 N изменяется от 1 до 10. В этом случае говорить
о процентах найденных ошибок смысла нет. Поэтому в боковике записаны не относительные, а абсолютные значения. Поскольку указанное количество ошибок вполне реально для
учебных программ, интересно было сравнить модельные данные, приведенные в таблице, с реальными данными из практики. Надо только помнить, во-первых, что речь идет о средних
значениях. А во-вторых, что данные в таблице получены из
статистической модели. А статистика любит большие числа.
Таблица 6. Средний процент тестов,
необходимых для обнаружения заданного количества ошибок
(в зависимости от общего числа ошибок в программе)
Общее число ошибок в программе (N)

Количество
найденных
ошибок (n)

1

2

3

4

5

6

7

8

9

10
3

1

100

33

18

12

9

7

6

5

4

2



100

45

28

20

15

12

10

8

7

3





100

52

34

35

20

16

13

11

4







100

56

39

29

23

19

16

5









100

59

42

33

26

22

6











100

61

45

35

29

7













100

63

47

37

8















100

65

49

9

















100

66

10



















100

Имея такие оценки относительного количества тестов, в
конкретном проекте можно перейти к абсолютным величинам.
Поскольку нам известно, сколько тестов нам понадобилось для
нахождения n ошибок, легко оценить, сколько понадобится для
нахождения оставшихся. Если количество ошибок в программе
оценено в 10 и для обнаружения первых пяти потребовалось,
например, 7 тестов, то для поиска двух следующих ошибок количество тестов придется довести до 12 (5 дополнительных тестов на 2 ошибки), для поиска следующих двух — до 21 (9 дополнительных тестов на 2 ошибки). Все 10 ошибок есть надежда
найти за 32 теста.
Подобные оценки можно использовать для планирования
времени и ресурсов, выделяемых для процесса тестирования.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Глава 16

Отладка

Выше мы определили отладку7 как процесс локализации места ошибки и внесения изменений в программу. В этой главе
мы обсудим некоторые вопросы, связанные с определением места ошибки.

16.1. Ìåñòî ïðîÿâëåíèÿ îøèáêè
è ìåñòî íàõîæäåíèÿ îøèáêè
Прежде всего, необходимо различать место проявления
ошибки и место нахождения ошибки. Вернемся к примеру 1
из главы 7 «Критерии белого ящика»:
a:= 0;
if x>3 then a:= 10;
b:= 1/a;
Место проявления ошибки в данном случае — третий оператор фрагмента: b:= 1/a. Но место нахождения ошибки указать так определенно мы не можем. Возможно, что ошибка в
третьем операторе, и в знаменателе должна стоять не переменная a, а какое-то иное выражение. Возможно, что ошибка в
первом операторе, и переменной a должно было быть присвоено иное ненулевое значение. Возможно, что ошибка во втором
операторе, и там потеряна ветвь «иначе», при выполнении которой переменная a должна была поменять свое значение на
ненулевое.
7

В английском языке используется термин «debug», который является буквальным аналогом принятого в русском языке (но не имеющего отношения к computer science) термина «инсектикация» (борьба с насекомыми). Причины появления англоязычного термина исторические. Согласно апокрифу, во время
одного из сеансов работы «Эниака» (машины, которую принято считать первой американской ЭВМ, хотя она и не имела памяти команд) произошел аварийный останов, вызванный тем, что под одну из клемм одного из многочисленных устройств попал мотылек (честно говоря, не совсем понятно, как). На
поиск места неисправности и ее устранение потребовалось время. Виновник
был найден и приклеен в журнал для регистрации работы ЭВМ. Но в это время последовал телефонный звонок от начальства с вопросом, почему машина
не работает и чем там вообще занимается обслуживающий персонал. На что
Грейс Мюррей Хоппер — руководитель группы кодировщиков (слова «программист» тогда еще не существовало) ответила: «Ловим насекомых».

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Отладка

85

По каким-то внешним признакам мы можем найти только
место проявления ошибки. Место нахождения ошибки можно
определить только путем содержательного анализа текста программы.

16.2. Îòëàäî÷íûå îïåðàòîðû
После обнаружения факта наличия в программе ошибки
уточнение ее места нахождения может потребовать сбора дополнительной информации о ходе выполнения программы.
Большинство систем программирования включает в себя те или
иные отладочные средства. (В следующей главе будут рассмотрены отладочные средства популярной системы Турбо Паскаль.)
Кроме встроенных отладочных средств для сбора данных о работе программы можно использовать специальные отладочные
операторы, добавляемые в ее текст.
Так же, как и в случае с заглушками, отладочные операторы
должны отличаться от «нормальных» операторов программы, а отладочные сообщения — от «нормальных» сообщений.
В главе 13 «Нисходящее тестирование» для этого предлагалось использовать в качестве маркера строку из двух диезов:
«##». Все отладочные операторы будут помечаться комментарием, содержащим эту пометку, а все отладочные сообщения —
начинаться с этой комбинации.
Как правило, отладочные операторы — это операторы печати, которые выполняют полную или частичную трассировку
значений нужных переменных. Трасса может выдаваться на экран или записываться в файл. При этом надо учитывать следующие факторы. Информация, выводимая на экран, может очень
быстро «убежать» с него, вытесненная новыми данными. Поэтому если трассируемые значения выводятся на экран, может
быть полезно после выдачи очередной порции информации сделать паузу. В некоторых языках есть специальные операторы
pause. Там, где их нет, для приостановки выполнения программы может использоваться оператор ввода, который ждет ввода
конца строки (т. е. нажатия клавиши Enter). В Турбо Паскале
для этого достаточно вызова процедуры readln без параметров.
Главный недостаток выдачи информации на экран — невозможность ее дальнейшего анализа. Для сохранения трассировочных данных их можно записать в файл. При этом программиста ждет другая опасность. Велик искус начать записывать в
файл «все подряд» по принципу «авось пригодится». В результате трассировочные файлы достигают совершенно неверо-

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

86

Глава-16

ятных размеров, и разобраться в их содержимом становится
практически невозможно. Не случайно подобный метод сбора
информации называют методом «грубой силы».
Полезно, чтобы по отладочному сообщению можно было однозначно определить, какой именно отладочный оператор его
выдал. Для этого в выдаваемый текст вдобавок к отладочной
информации надо включать указание на точку выдачи.
Удобно, когда в отладочном сообщении выдаются не только
значения трассируемых переменных, но и их имена. Это существенно упрощает анализ трассировки.
С учетом всего сказанного отладочная печать может выглядеть, например, так:
{##} writeln('##Òî÷êà QR7: a=', a, 'b=', b);
{##} readln;
Как только в программе появляются отладочные операторы,
возникает желание управлять ими (подключать или отключать
при очередном выполнении программы). Физическое удаление
отладочных операторов из текста программы — решение слабое.
А вдруг они еще понадобятся? Удобным приемом для отключения отладочных операторов является превращение их в комментарий, заключение в «комментаторные скобки». Заметим, что
Турбо Паскаль поощряет использование этого приема тем, что
имеет две пары скобок для записи комментариев. Если в стандартном Паскале комментарий обязательно заключается в фигурные
скобки: {комментарий}, то в Турбо Паскале комментарий может
быть заключен либо в фигурные скобки, либо в «звездчатые скобки», составленные из пары «круглая скобка — звездочка»: (*комментарий*). Причем скобки должны быть обязательно парными!
Комментарий, открытый фигурной скобкой, закрывается только
фигурной скобкой. Звездчатые скобки внутри него игнорируются. Аналогично комментарий, открытый звездчатой скобкой, закрывается только звездчатой скобкой. Фигурные скобки внутри
него игнорируются. Это дает возможность отделить обычные комментарии от отключенных отладочных операторов. Для этого достаточно для выделения обычных комментариев использовать
один вид скобок (например, фигурные), а для отключения отладочных операторов — другой (звездчатые). Тогда отключение отладочных операторов будет иметь, например, такой вид:
a:= b;
{ýòî îïåðàòîð ïðîãðàììû ñ îáû÷íûì êîììåíòàðèåì}
(*{##} writeln('##Òî÷êà SD3: a= ',a);
{ýòî îòëàäî÷íàÿ ïå÷àòü}*)

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Отладка

87

Еще один полезный инструмент отладки — встраивание в текст
программы утверждений о том, каким ограничениям должны удовлетворять те или иные переменные. Если известно, что переменная a всегда должна быть больше нуля, а переменная b в какой-то
точке программы должна быть меньше переменной c, в соответствующих местах программы ставятся утверждения assert(a>0),
assert(b0,'Âåñ ÷åëîâåêà > 0','P1.q');
потребует от программиста усилий не намного больших, чем
стандартная конструкция, а сработает ничуть не хуже.
Резко усилить мощность отладочных средств можно, сделав
их условными. Делается это следующим образом. В программе
описываются одна или несколько специальных переменных
для управления отладкой. Будем называть их отладочными
флагами. Переменные эти, как правило, логические, хотя могут быть и других типов. Отладочные операторы помещаются
внутрь условных операторов, выполнение которых зависит от
отладочных флагов. Например, так:
{##} var DebugFlag: Boolean;

{##} DebugFlag:= true; {DebugFlag:= false;}

{##} if DebugFlag then writeln('##Òî÷êà Ð1:x=',x,'y=',y);


Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

88

Глава-16

Теперь можно включать и выключать отладочные действия
без изменения текста программы. Для этого достаточно изменить значение отладочных флагов. Причем управление может
быть достаточно «хитрым». Отладочные операторы могут быть
включены в одной части программы и отключены в другой. Могут существовать наборы отладочных операторов, расположенных в разных частях программы, но управляемых одним и тем
же отладочным флагом и, соответственно, включающихся/выключающихся одновременно. Это имеет смысл, если все операторы такого набора отслеживают, например, одну и ту же переменную.
Менять значения отладочных флагов (и, соответственно, включать/выключать отладочные действия) можно динамически по
ходу выполнения программы.
Можно ввести понятие «уровень отладки». Меняя значение
соответствующей управляющей переменной (она должна быть
числом), можно регулировать подробность трассировочной информации.
Начальное значение отладочных флагов может быть задано в тексте программы, а может загружаться из файла. В последнем случае включать/выключать трассировки можно
будет, даже не перетранслируя текст программы. Например,
так:
{##}
{##}

{##}
{##}
{##}

{##}

var debug1, debug2: integer;
var DebugFile: text;
assign(DebugFile,'dbg.txt');
reset(DebugFile);
read(DebugFile, debug1, debug2);
if debug1>0 then writeln('##Òî÷êà FDR z= ',z);

В принципе условные отладочные операторы вообще необязательно удалять из текста программы. Они могут оставаться
там (естественно, в отключенном виде) и по окончании отладки.
Особенно если в перспективе возможна дальнейшая модификация программы. Достаточно вспомнить приведенную в главе 14
«Оценка количества ошибок в программе» IBM-овскую формулу для оценки количества исправлений в старых модулях системы при добавлении в систему новых модулей. Для больших
систем модификация (а значит, и отладка) могут стать процессом непрерывным.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Отладка

89

Платой за наличие отладочных операторов в тексте программы станет некоторое повышение требований к машинным ресурсам. Отладочные операторы увеличивают размер
исходной программы, время трансляции, размер кода, время
выполнения. Избежать всего этого можно, если использовать
механизм условной трансляции, который поддерживают многие системы программирования. Отладочные операторы можно сделать условно транслируемыми. Если версия программы
предназначена для отладки, трансляция отладочных операторов включается, и они работают. Если отладка закончена и
пора готовить систему к эксплуатации, надо перетранслировать программу, отключив трансляцию отладочных средств.
В результате в окончательной версии программы их уже не
окажется.
В Турбо Паскале установка/сброс символов для управления условной трансляцией производится в опциях компилятора Options/Compiler.../Conditional Defines (см. рис. 9.1 на
стр. 33) либо директивами DEFINE и UNDEF. Управление
условной трансляцией осуществляется с помощью директив:
IFDEF, IFNDEF, IFOPT, ELSE, ENDIF.
Последнее замечание по поводу отладочных операторов. Помните, что добавление/удаление отладочных операторов — это
изменение текста программы. Независимо от того, каким образом оно выполнялось: вручную или автоматически с помощью
условной трансляции. И — как любое изменение — оно может
привести к внесению в программу ошибок. Поэтому после удаления из программы отладочных операторов тестирование программы необходимо повторить.

16.3. Èíäóêòèâíûé è äåäóêòèâíûé ìåòîäû
ïîèñêà îøèáêè. Ðåòðîàíàëèç
К поиску ошибки существует два подхода: индуктивный и
дедуктивный.
Индуктивный подход означает движение от частного к общему. От того, какие данные говорят об ошибке, к тому, как их
можно объяснить.
Для сбора информации о работе программы могут быть полезны дополнительные тесты, спроектированные специально
для нужд отладки. Такие тесты отличаются от тестов, предназначенных для выявления факта наличия ошибки. Тесты
для выявления факта наличия ошибки должны быть как можно более «охватными». Чем большую часть программы прове-

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

90

Глава-16

рит такой тест, тем лучше. К отладочным тестам требования
противоположные. От них требуется не широта охвата, а прицельность, сбор как можно более точной и подробной информации о том конкретном месте программы, в котором есть
основания подозревать ошибку.
Схема индуктивных рассуждений:

Дедуктивный подход означает движение от общего к частному. Что в принципе могло произойти? Исходя из некоторых общих соображений формируется множество гипотез. Затем оно
уточняется: какие-то гипотезы будут исключены как несоответствующие имеющимся признакам ошибки, какие-то уточнены за счет дополнительной информации.
Например: программа нормально заканчивает вычисления,
но выдает странные результаты. На выходе ожидались два числа, близких к единице с одним знаком после запятой. Получили
одно число очень большое, а второе — со странной непериодической дробной частью. Известно, что «странные» выходные данные могут получиться, если в вычислениях участвовала неини-

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Отладка

91

циализированная переменная, либо при вводе из файла «не тех»
значений (например, файл готовился как текстовый, а читался
как типизированный), либо при неверной передаче значения параметра из подпрограммы, либо при неправильных вычислениях (например, требующих слишком больших или слишком маленьких промежуточных результатов). Значит, надо проверить,
все ли переменные, использованные при вычислении «странного» результата, были явно инициализированы. Не использовались ли для вычисления этих результатов значения, введенные
из файла? Если да, то что за данные лежат в этом файле. Не
было ли передачи параметра из подпрограммы? Если да, то передается ли этот параметр по ссылке? Результатом каких именно вычислений являются «странные» значения? Не могло ли по
ходу этих вычислений возникнуть слишком больших или слишком маленьких промежуточных результатов?
Надо отметить, что именно дедуктивный подход (основанный на накопленном опыте) часто позволяет преподавателям
быстро найти ошибку в программе студента. Ошибки-то повторяются!
Схема дедуктивных рассуждений:

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

92

Глава-16

Ðåòðîàíàëèç
Четверть века назад в шахматных журналах были очень популярны задачи на так называемый ретроанализ. Выглядели они
так. Дается ситуация, при которой белые ставят мат в один ход и
черные ставят мат в один ход. Вопрос: кто выиграет? То есть надо
разобраться, чей сейчас ход. А для этого «открутить» игру назад и
понять, каким образом сложилась описанная в задаче ситуация.
Как ретроанализ выглядит применительно к отладке? Находим место ошибки. Ошибка связана с тем, что некоторые переменные имеют определенные значения. Проследим назад по
программе, откуда эти значения взялись. Они были вычислены
через некоторые другие переменные. А откуда взялись значения этих других переменных? И так далее. Например:
read(a);
b:= 7;
c:= a + b;
d:= 1/c;
При вычислении d возникает деление на нуль. Значит, переменная c в этом операторе равна нулю. Возможны два варианта. Либо ошибочно выражение, записанное в знаменателе (знаменатель должен быть отличен от c), либо переменная c имеет
неверное значение. Если выражение в знаменателе правильно,
значит, переменная c имеет неверное значение. Откуда взялось
значение переменной c? Поднимаемся вверх по программе. Переменная c получила значение из выражения a + b. Значит,
либо в этой позиции должно стоять другое выражение, либо
что-то не так со значениями переменных a и b. Откуда взялись
значения этих переменных? Опять поднимаемся вверх по программе. И так далее. Не всегда картина бывает такой ясной.
Разбираться приходится и с развилками, и с циклами, и с подпрограммами. Но принципиальная схема именно такова.

16.4. Ïðèíöèïû îòëàäêè
1. Думай! Средства отладки играют только вспомогательную
роль. В этом отношении процесс отладки напоминает работу детектива. Вспомните «В августе 44-го…» В. Богомолова. Массовая отладочная печать напоминает войсковую операцию. Внешне она, может быть, и эффектна. Но добиться
с ее помощью «момента истины» сложно.
2. Избегай экспериментирования. Работа по принципу «я не
знаю, в чем ошибка, но сейчас исправлю вот это место и посмотрю, к чему это приведет» — совершенно недопустима!

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Отладка

93

3. Исправляй поочередно. Одновременное внесение в программу нескольких исправлений существенно затрудняет анализ последствий каждого из них.
4. Необходимо найти ошибку, которая бы объясняла все 100%
симптомов.
5. Там, где есть ошибка, может быть еще.
6. Исправление может внести новую ошибку.

16.5. Àíàëèç îáíàðóæåííîé îøèáêè
Раз уж мы допустили ошибку в программе, хочется, по возможности, обратить вред в пользу и извлечь из допущенной
ошибки максимум пользы на будущее. Для этого надо проанализировать обнаруженную ошибку по следующему плану:
1. Когда была сделана ошибка?
На каком этапе работы над программой допущена ошибка:
при постановке задачи, при проектировании программы, при
написании текста на языке программирования и т. д.
2. Почему была сделана ошибка?
Что именно было непонятно? Была ли причиной ошибки неточность в формулировке задачи, или недопонимание какой-то
языковой конструкции, или неудачный выбор имени переменной (который сделал описку ошибкой), или что-либо еще.
3. Как можно было предотвратить ошибку?
Есть хороший методический принцип: «Никогда не спрашивай человека, чему он научился. Спрашивай, что он в следующий раз будет делать по-другому». Именно этот вопрос мы сейчас и задаем. Что в следующий раз надо делать по-другому,
чтобы подобные ошибки не повторялись?
4. Почему ошибку не обнаружили раньше?
Если ошибку удалось обнаружить сейчас, то почему ее не
удалось обнаружить раньше, на более ранних этапах?
5. Как можно было обнаружить раньше?
Как нашли ошибку? Почему именно этот тест оказался удачным? Нельзя ли таким же образом найти аналогичные ошибки
в этой или других программах?
Результаты анализа полезно фиксировать в «дневнике отладки». Ошибки и их исправление — процесс творческий. Свой
собственный опыт в этой области заменить трудно. А вот собирать и анализировать его очень полезно.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Глава 17

Отладочные средства
системы Турбо Паскаль

17.1. Ïåðå÷åíü îòëàäî÷íûõ ñðåäñòâ
Òóðáî Ïàñêàëÿ
В данной главе описаны отладочные средства, предоставляемые системой программирования Турбо Паскаль версии 7.0.
В других версиях они могут несколько отличаться, но отличия
эти непринципиальны.
Для поиска ошибок в программе, а также для лучшего понимания того, как именно она работает, можно использовать следующие возможности системы Турбо Паскаль:
1) пошаговое исполнение программы с заходом в процедуры
и без захода;
2) исполнение «до курсора»;
3) установка контрольных точек, в том числе условных и со
счетчиком;
4) наблюдение за значениями переменных;
5) вычисление выражений и изменение значений переменных во время приостанова программы;
6) наблюдение за состоянием стека вызванных подпрограмм;
7) динамическое управление отладочными средствами.

Рис. 17.1. Вертикальное меню Run

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Отладочные-средства-системы-Турбо-Паскаль

95

Рис. 17.2. Вертикальное меню Debug

В верхнем меню они сосредоточены в пунктах Run и Debug
(см. рис. 17.1, 17.2).
Команды из вертикального меню пункта Run обеспечивают
управление способом выполнения программы, из пункта Debug — управление контрольными точками, наблюдение за значением переменных и вызовами подпрограмм, вычисление выражений и переменных. Кроме того, часть наиболее ходовых
команд продублирована в локальном меню окна редактирования программы. Это меню вызывается комбинацией клавиш
Alt+F10 (см. рис. 17.3).

Рис. 17.3. Локальное меню в окне редактирования текста программы

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

96

Глава-17

Рис. 17.4. Сообщение об ошибке при отключенных опциях
сбора отладочной информации

Надо подчеркнуть, что для нормальной работы отладочных
средств необходимо, чтобы были включены две опции компилятора: Options/Compiler…/Debug information и Options/
Compiler…/Local symbols (см. рис. 9.1 на стр. 33). Включение
этих опций обеспечивает сбор отладочной информации и позволит далее вести отладку в терминах языка высокого уровня (использовать имена переменных и процедур, ссылаться на конкретные строки текста программы). При выключенных опциях
вы можете рассчитывать только на сообщения типа «Runtime
error» (см. рис. 17.4).

17.2. Ïîøàãîâîå âûïîëíåíèå ïðîãðàììû
Программа в Турбо Паскале может выполняться безостановочно или пошагово.
Безостановочное выполнение запускается командой Run в
вертикальном меню Run (горячая клавиша Ctrl+F9). В простейшем (с точки зрения отладки) случае выполнение начинается с начала программы и продолжается до конца. В более
сложных случаях внутри программы могут быть установлены
точки приостанова. В этом случае по команде Run выполнение
идет от одной точки до другой. Подробнее об этом будет рассказано в следующем пункте.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Отладочные-средства-системы-Турбо-Паскаль

97

Рис. 17.5. Пошаговое выполнение программы. Состояние
до выполнения присваивания значений переменным i и j

Второй возможный режим выполнения программы — выполнение пошаговое (построчное). Перед выполнением очередного
шага соответствующая строка в программе выделяется бирюзовой полосой. Если в строке записаны несколько операторов, все
они будут выполнены за один шаг (см. рис. 17.5, 17.6).

Рис. 17.6. Пошаговое выполнение программы. Состояние
после выполнения присваивания значений переменным i и j.
Присваивание значений обеим переменным выполнено за один шаг

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

98

Глава-17

Для выполнения шага могут использоваться функциональные клавиши F8 или F7 либо соответствующие команды Step
over или Trace into из вертикального меню Run. Если выполняемая программа не имеет подпрограмм (процедур и функций),
обе команды действуют совершенно одинаково: по нажатию горячей клавиши F8 или F7 выполнится одна строка. Указатель
следующего шага (бирюзовая полоса) переместится на соответствующую строку текста программы.
Разница между командами проявляется при попытке выполнить вызов процедуры.
Команда Step over (горячая клавиша F8, буквальный перевод
«Шагать через») рассматривает вызов подпрограммы как один
оператор. Порядок выполнения тела подпрограммы ее не интересует. Вся подпрограмма будет выполнена за один шаг. По нажатию клавиши F8 указатель следующего выполняемого шага
(бирюзовая полоса) переместится на соответствующую строку
текста программы.
Команда Trace into (горячая клавиша F7, буквальный перевод «Следить внутрь») выполняет заход внутрь тела вызываемой подпрограммы и ее пошаговое выполнение. По нажатию
клавиши F7 на экран будет выдано тело вызванной подпрограммы и указатель следующего выполняемого шага (бирюзовая полоса) переместится на ее первый оператор.

17.3. Êîíòðîëüíûå òî÷êè
Пошаговое выполнение дает максимально подробную информацию о ходе вычислений. Но для больших программ оно может
быть слишком медленно и утомительно. Часто нас интересует
подробная информация о выполнении не всей программы, а
только каких-то ее участков. Нам бы хотелось безостановочно
дойти до интересующего нас участка программы, там остановить
выполнение и заняться исследованием (просмотром значений
переменных и стека подпрограмм, пошаговым выполнением интересного участка и т. п.). Затем опять быстро (безостановочно)
пройти неинтересную часть программы до следующего интересного участка, где опять заняться исследованиями.
Приостанов выполнения программы с возможностью его возобновления реализуется в Турбо Паскале с помощью контрольных точек (точек приостанова, англ. breakpoint). Аппарат
контрольных точек в Турбо Паскале весьма развит. Они могут
быть условными (т. е. выполнение программы будет или не
будет останавливаться в зависимости от значения некоторого

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Отладочные-средства-системы-Турбо-Паскаль

99

Рис. 17.7. Контрольные точки

условия). Они могут быть связаны со счетчиком (выполнение
будет останавливаться не каждый раз, а только после заданного числа проходов). Они могут устанавливаться и сниматься динамически прямо во время выполнения программы. После контрольной точки выполнение может быть продолжено далее или
начато опять с начала программы.
На экране контрольные точки изображаются красной полосой. Пример контрольных точек см. на рис. 17.7. Программа
будет приостановлена в первый раз перед инициализацией переменной nom, а затем будет приостанавливаться каждый раз
после выполнения тела цикла.
Добавление новой контрольной точки проводится с помощью
команды Add breakpoint… в вертикальном меню Debug (существует горячая клавиша Ctrl+F8, но в вертикальном меню она не
указана). Воспользоваться ей можно как до начала выполнения
программы, так и во время выполнения (после выполнения очередного шага при пошаговом выполнении или в момент приостанова в контрольной точке). На рис. 17.8 показано назначение
новой контрольной точки во время выполнения программы (бирюзовая полоса на заголовке цикла while tek0 then
îáðàáîòàòü ïîëîæèòåëüíûé ýëåìåíò
else îáðàáîòàòü îòðèöàòåëüíûé ýëåìåíò;
end; {do}
âûäàòü ðåçóëüòàò
end.

Примечание

ïîêà íå ïîíÿòíî, êàêèå
var n: integer;
var k: integer;
var tek: integer;

Уточняем недоопределенные фрагменты:
Îáðàáîòàòü ïîëîæèòåëüíûé ýëåìåíò
if tekMaxOtr then begin
MaxOtr:= tek;
NomMaxOtr:= k
end

var MinPol: integer;
var NomMinPol:
integer;

var MaxOtr: integer;
var NomMaxOtr:
integer;

При написании программы не забудем шишки, которые мы
уже успели набить на первом примере. По большому счету, их
было две. Во-первых, мы хорошо запомнили, что все данные
должны быть инициализированы. Во-вторых, чтобы не зациклиться, надо, как минимум, менять хотя бы одну переменную
в условии цикла.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

120

Глава-18

Разберемся с инициализацией. У нас 7 переменных: n, k,
tek, MinPol, NomMinPol, MaxOtr, NomMaxOtr. Какие из них
надо инициализировать, какие нет? Первая группа: n, k, tek.
Значение переменной n будет введено сразу после начала выполнения программы. Собственно, это первое содержательное
действие нашей программы. Переменная k — счетчик цикла,
она получит значение в операторе for. Значение переменной
tek будет введено. Значит, эти три переменные в инициализации не нуждаются.
Далее появляются две пары переменных: MinPol, NomMinPol
и MaxOtr, NomMaxOtr. Каждая пара должна рассматриваться не изолированно, а именно как пара. Смысл этих переменных: минимальное положительное значение и его номер в последовательности, максимальное отрицательное значение и его
номер в последовательности. Возникали они у нас на уровне
уточнения текста программы, при раскрытии недоопределенных фрагментов верхнего уровня. Две из них — MinPol и MaxOtr — возникли сразу в вычислениях как переменные, уже
имеющие осмысленные значения. Значит, их инициализировать нужно обязательно. А поскольку NomMinPol и NomMaxOtr
с ними неразрывно связаны, лучше инициализировать сразу
все четыре. Чем? Будем исходить из смысла этих переменных.
Что такое MinPol и MaxOtr? Минимальное положительное и
максимальное отрицательное значения. В момент инициализации у нас еще нет ни того, ни другого. Значит, при инициализации переменные должны получить значения, по которым
было бы видно, что эти значения «не настоящие», получены не
в ходе содержательных вычислений. Очевидно, что в качестве
таких значений удобно использовать нуль. Он не является ни
положительным, ни отрицательным. Поэтому не может быть
«настоящим» значением ни для MinPol, ни для MaxOtr. Аналогично можно рассуждать про NomMinPol и NomMaxOtr. В момент инициализации отсутствуют минимальное положительное
и максимальное отрицательное, стало быть, отсутствуют и их
номера. Значит, NomMinPol и NomMaxOtr надо инициализировать значениями, которые не могут быть «настоящими» номерами. И опять в качестве такого значения удобно взять нуль.
Значит, эти четыре переменные инициализировать надо, и в
качестве начального значения всем им присвоим нули.
Что касается цикла, то легко заметить, что условия у нас изменились. Раньше мы не знали заранее, сколько раз должен
повторяться цикл. Это выяснялось только в ходе его выполнения. Поэтому необходимо было использовать либо цикл while,

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Еще-один-пример-тестирования-программы

121

либо цикл repeat, в зависимости от минимального количества повторов цикла. Сейчас мы знаем еще до входа в цикл, что
он должен отработать n раз. Значит, вместо цикла с предусловием нам удобней воспользоваться циклом со счетчиком:
for k:= 1 to n do. Правда, у нас возможна пустая последовательность (n=0), но это не страшно. В Паскале минимальное
количество повторений цикла со счетчиком равно нулю.
Первый вариант программы будет иметь вид:
program PolOtrMinMax;
var n, k, tek: integer;
MinPol, NomMinPol: integer;
MaxOtr, NomMaxOtr: integer;
begin
{âûäàòü íà÷àëüíîå ïðèâåòñòâèå}
writeln('Ôóíêöèÿ ïðîãðàììû:');
writeln(' ââåñòè êîë-âî ÷èñåë â ïîñëåäîâàòåëüíîñòè,');
writeln(' ââåñòè óêàçàííîå ÷èñëî ÷èñåë,');
writeln(' ñîîáùèòü, ÷òî èäåò ðàíüøå: ìèíèìàëüíîå');
writeln(' ïîëîæèòåëüíîå èëè ìàêñèìàëüíîå îòðèöàòåëüíîå.');
{èíèöèàëèçèðîâàòü äàííûå}
MinPol:= 0; NomMinPol:= 0;
MaxOtr:= 0; NomMaxOtr:= 0;
{ââåñòè äëèíó ïîñëåäîâàòåëüíîñòè}
writeln('Ââåäèòå êîë-âî ýëåìåíòîâ ïîñëåäîâàòåëüíîñòè');
read(n);
for k:= 1 to n do begin
{ââåñòè î÷åðåäíîé ýëåìåíò}
writeln('Ââåäèòå ýëåìåíò ¹',k);
read(tek);
if tek>0 then
{îáðàáîòàòü ïîëîæèòåëüíûé ýëåìåíò}
if tekMaxOtr then begin
MaxOtr:= tek;
NomMaxOtr:= k
end;
end; {do}
{âûäàòü ðåçóëüòàò}
if n=0 then writeln('Ïîñëåäîâàòåëüíîñòü ïóñòà')

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

122

Глава-18

else if NomMinPol=0 then
writeln('Ïîëîæèòåëüíûõ ÷èñåë íåò')
else if NomMaxOtr=0 then
writeln('Îòðèöàòåëüíûõ ÷èñåë íåò')
else if NomMinPol=0 and NomMaxOtr=0 then
writeln('Ïîñëåäîâàòåëüíîñòü ñîñòîèò èç îäíèõ íóëåé')
else if NomMaxOtr1

У2

if tek>0

+


У3

if tekMaxOtr

+


У5

if n=0

+


У6

if NomMinPol=0

+


У7

if NomMaxOtr=0

+


У8
У9

if NomMinPol=0
and NomMaxOtr=0
NomMinPol=0

+

+


У10

NomMaxOtr=0

+


У11

if NomMaxOtr<
NomMinPol

+


Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

Еще-один-пример-тестирования-программы

123

18.4. «Ñóõàÿ ïðîêðóòêà»
Приступим к прокрутке. Для теста Т1 трассировочная таблица будет предельно проста:
n

k

0

1

tek

MinPol

NomMinPol

MaxOtr

NomMaxOtr

0

0

0

0

Результат: Последовательность пуста.

Отметим результаты в таблице тестов:
Тест

Вход

Ожидаемый
результат

Результат
«сухой
прокрутки»

+/–

T1

0

Последовательность
пуста

Последовательность
пуста

+

...

...

...

Результат
выполнения на
компьютере

+/–

Тест Т1 оказался неудачен. Перейдем к тесту Т2. Трассировка будет иметь вид:
n

k

tek

1

1

5

MinPol

NomMinPol

MaxOtr

NomMaxOtr

0

0

0

0

Результат: Положительных чисел нет.

То есть как? Мы только что ввели положительное число 5, а
программа заявляет, что положительных чисел нет. В чем дело?
Для поиска ошибки применим ретроанализ.
Программа выдала сообщение «Положительных чисел нет».
Такое сообщение может быть выдано только из одной точки
программы: из оператора
if NomMinPol=0 then writeln('Ïîëîæèòåëüíûõ ÷èñåë íåò')

Значит, при выполнении этого оператора условие было истинно, т. е. переменная NomMinPol = 0. Почему переменная
NomMinPol имеет значение 0? Нулевое значение переменная
получила при инициализации. И это было правильно. В момент
инициализации не введено ни одного элемента последователь-

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

124

Глава-18

ности. Значит, нет и минимального положительного. А значит,
нет и его номера. Признаком отсутствия номера удобно сделать
нулевой номер. Но это все говорится о моменте инициализации.
Сравнение if NomMinPol=0 выполняется в конце программы.
К этому времени мы уже ввели положительный элемент последовательности. Значит, нулевое значение NomMinPol уже должно
быть заменено. Сделать это должен был фрагмент:
MinPol:= tek;
NomMinPol:= k
Не сделал. Значит, этот фрагмент не выполнялся. Почему?
Он «скрыт» двумя условиями:
if tek>0 then
if tek0 — истинно. Через первое условие проходим. Проверяем второе: tek=5,
MinPol=0, tek0

+

+

+



У3
У3.1

if MinPol=0
or tekMaxOtr

+

+

+



У4.2

+

+


У4

+

+

+



+
+

+

Тесты, разработанные исходя из критериев черного ящика, неплохо ведут себя и с точки зрения критериев белого
ящика. Незакрытымиостались условие У3.2+ и группа условий У8+, У9+, У10+, которые могут быть выполнены только
все вместе. Тесты Т8 и Т9 с точки зрения МГТ вообще избыточны. Ни одной новой строки в таблице они не закрыли.
Идущий далее тест Т10 должен закрыть 3 из четырех пока
еще не закрытых строк.

Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»

138

Глава-18

Его трассировка:
n
3

k

tek

1

0

2

0

3

0

Результат:

MinPol

NomMinPol

MaxOtr

NomMaxOtr

0

0

0

0

0

1

0

2

0

3

Положительных чисел нет.

Стоп! Это совсем не то, на что мы рассчитывали. Тест — удачен! В программе обнаружено наличие еще одной ошибки! Начинаем разбираться.
Прежде всего, в трассировочной таблице бросается в глаза то,
что мы каждый раз (точнее, для каждого элемента последовательности) перевычисляли MaxOtr и NomMaxOtr, хотя не должны были делать этого ни разу. При этом MinPol и NomMinPol,
как и положено, ни разу не перевычислялись. Почему предшествующие условия предохранили от перевычисления MinPol и
NomMinPol, но не предохранили MaxOtr и NomMaxOtr?
Условие, предохраняющее MinPol и NomMinPol: if tek>0
then. Оно отфильтровывает только положительные числа.
А вот условие, отфильтровывающее только отрицательные числа, в программе отсутствует. Мы допустили стандартную ошибку: потеряли третий результат операции сравнения. Все числа,
которые «НЕ больше нуля», мы объявили отрицательными. Но
ведь в эту категорию попадает и сам нуль. Значит, на ветке
«иначе» вышеуказанного оператора if tek>0 then должна
появиться еще одна проверка: if tek0 then
{îáðàáîòàòü ïîëîæèòåëüíûé ýëåìåíò}
if MinPol=0 or tek0

+

+

+



У3
У3.1

+

+

if MinPol=0
or tek1
У2

if tek>0

+

+

+


У3

+

+

if MinPol=0
or tek