П л а н и р о в а н и е. ┌───────────────────────────────────────────────────────────────────┐ │ При отсутствии планового подхода тестирование обычно сводится │ │ к тому, что программист берет какие-то, можно сказать, первые │ │ попавшиеся исходные данные и пропускает программу многократно, │ │ исправляя ее при обнаружении ошибок и добиваясь того,чтобы по- │ │ лучаемые результаты походили на желаемые. │ └───────────────────────────────────────────────────────────────────┘ Ясно, что при этом контролируется только некоторая часть блоков и опе- раторов, а остальные выполнятся в первый раз уже во время счета, и будут ли при этом найдены ошибки, имеющиеся в них, зависит только от случая. При п л а н о в о м подходе программа проверяется последовательно блок за блоком, причем если программа состоит из центрального блока, кото- рый проводит обращения к периферийным блокам, мало связанным друг с дру- гом, то возможны следующие два основных подхода к контролю такой програм- мы, два основных направления тестирования: от периферии к центру (в о с- х о д я щ е е тестирование) или, наоборот, от центра к периферии (н и с- х о д я щ е е тестирование). При первом, в о с х о д я щ е м способе,применяемом обычно для неболь- ших программ, сначала тестируют отдельные периферийные блоки, а затем пе- реходят к тестированию центральной части, которая, разумеется, взаимодей- ствует только с отлаженными уже блоками. При н и с х о д я щ е м тестировании, используемом для достаточно больших программ, параллельно с контролем периферийных блоков (или даже до начала их контроля) производится и контроль центрального блока, выпол- няемого на компьютере совместно с имитаторами периферийных блоков, называ- емых "з а г л у ш к а м и". В задачу имитаторов входит моделирование рабо- ты соответствующих блоков с целью поддержать функционирование централь- ного блока. Обычно "заглушки" выдают простейший результат, например конс- танту и сообщение о факте своего участия в работе. Вместо постоянной вели- чины на наболее поздней стадии тестирования может выдаваться и случайная величина в требуемом диапазоне. Например, для начального контроля программы, включающей в качестве од- ного из своих блоков вычисление определенного интеграла,"заглушка" такого блока может возвращать константу. В свою очередь, блок интегрирования сам имеет периферийный блок вычисления значений подинтегральной функции, в ка- честве "заглушки" которого поначалу также может быть взята константа или простейшая функциональная зависимость. К сожалению, часто неверно понимают функции, выполняемые "заглушками". Так, порой можно услышать, что "заглушка" должна выполнять лишь запись со- общения, устанавливающего: "Модуль подключился".В большинстве случаев эти утверждения ошибочны. Когда модуль A вызывает модуль B, A предполагает, что B выполняет некую работу, т.е.модуль A получает результаты работы модуля B. Когда же модуль B просто возвращает управление или выдает неко- торое сообщение без передачи в A определенных осмысленных результатов, мо- дуль A работает неверно не вследствие ошибок в самом модуле, а из-за несо- ответствия ему модуля-"заглушки". Более того, результат может оказаться неудовлетворительным, если "ответ" модуля-"заглушки" не меняется в зависи- мости от условий теста. Если "заглушка" всегда возвращает один и тот же фиксированный результат вместо конкретного значения, предполагаемого вызы- вающим модулем именно в этом вызове, то вызывающий модуль сработает как ошибочный (например, зациклится) или выдаст неверное выходное значение. Следовательно, ┌───────────────────────────────────────────────────────────┐ │ создание модулей-"заглушек" - задача нетривиальная │. └───────────────────────────────────────────────────────────┘ Практически, оба этих способа редко используются в чистом виде, отдель- но один от другого. Обычно ко времени, когда приступают к контролю цен- трального блока, какие-то простейшие периферийные блоки уже отлажены авто- номно, и нет необходимости моделировать их работу и разрабатывать "за- глушки". Преимуществом ранней отладки центрального блока при нисходящем тести- ровании является то,что программист быстро получает возможность проверить периферийные блоки в условиях, которые в необходимой степени приближены к реальным. Действительно, центральный блок, снабженный хотя бы и простей- шими функциональными возможностями, можно рассматривать как реальную сре- ду, в которую "погружаются" отлаживаемые блоки, добавляемые к центральной части. Добавление отлаживаемых блоков удобно производить по одному для бы- стрейшего поиска ошибок, возникающих при стыковке с центральным блоком. Подключение каждого нового блока к центральной части позволяет постепенно усложнять испытания, которым подвергается тестируемая программа. Строгой, корректной процедуры подключения очередного последовательно тестируемого модуля не существует. Единственное правило, которым следует руководствоваться при выборе очередного модуля, состоит в том, что им дол- жен быть один из модулей, вызываемых модулем, предварительно прошедшим те- стирование. Запомните, что даже если изменения вносятся только в одну подпрограмму, то повторному тестированию подлежит вся система. Этот процесс называется т е с т и р о в а н и е м с в о з в р а т о м . Проверять работу только измененной подпрограммы недостаточно! Недостаточно полное тестирование та- кого рода повышает вероятность неудач. Проведем сравнение нисходящего и восходящего тестирования [50]. ┌──────────────────────────────────┬──────────────────────────────────┐ │ П р е и м у щ е с т в а │ Н е д о с т а т к и │ ├──────────────────────────────────┴──────────────────────────────────┤ │ Н и с х о д я щ е е т е с т и р о в а н и е │ ├──────────────────────────────────┬──────────────────────────────────┤ │ 1. Имеет преимущества, если │ 1. Необходимо разрабатывать │ │ ошибки,главным образом,в верхней │ модули-"заглушки", которые часто │ │ части программы │ оказываются сложнее, чем кажется │ │ 2. Раннее формирование струк-│ вначале │ │ туры программы позволяет провес-│ 2. Может оказаться трудным │ │ ти ее демонстрацию пользователю │ или невозможным создать тестовые │ │ и служит моральным стимулом │ условия │ │ │ 3. Сложнее оценка результатов │ │ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ │ тестирования │ │ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ │ 4. Стимулируется незавершение │ │ │ тестирования некоторых модулей │ ├──────────────────────────────────┴──────────────────────────────────┤ │ В о с х о д я щ е е т е с т и р о в а н и е │ ├──────────────────────────────────┬──────────────────────────────────┤ │ 1. Имеет преимущества, если │ 1. Программа как единое целое │ │ ошибки,главным образом,в модуле │ не существует до тех пор,пока не │ │ нижнего уровня │ добавлен последний модуль │ │ 2. Легче создавать тестовые │ │ │ примеры │ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ │ │ 3. Проще оценка результатов │ ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ │ └──────────────────────────────────┴──────────────────────────────────┘ Значительное повышение корректности и надежности программ достигается применением д в о й н о г о или N - к р а т н о г о программирования ("duplication check"-"двойной просчет", "двойная проверка"). При этом методе при разных алгоритмах и на разных языках программирова- ния создается несколько вариантов программы. Эти варианты реализуют одни и те же функции и при определенных тестовых данных должны выдавать тож- дественные результаты. Различие результатов при тестировании указывает на наличие ошибок по крайней мере в одном из вариантов. Обычно при разработ- ке вариантов программы используется один и тот же алгоритм, но программы создаются на разных языках, разных компьютерах и разными программистами. На практике применяется программирование с N≤2. Практически очень редки случаи, когда реальная программа создавалась в трех и более вариантах. В заключение заметим, что если исполнение теста приносит результаты, не соответствующие предполагаемым, то это означает, что α) либо модуль имеет о ш и б к у; β) либо н е в е р н ы предполагаемые результаты (ошибки в тесте). Для устранения такого рода недоразумений нужно тщательно проверять на- бор тестов ("тестировать" тесты). VIII.2.3.3. Т и п ы т е с т о в О несчастном случае мой подзащитный впервые узнал лишь тогда,когда этот случай произошел. Из речи адвоката О с н о в н ы м т е с т о м мы будем называть тест, проверяющий ос- новные функциональные возможности программы. Однако существует опасность, что после успешного окончания основного тестирования "на радостях" обыч- но забывают о необходимости дальнейшего, более тщательного контроля про- граммы и отдельных ее участков, да и настроиться на такой контроль стано- вится уже психологически трудно.Поэтому помимо основного теста необходимо применить следуюшие типы тестов. В ы р о ж д е н н ы й тест. Этот тест затрагивает работу отлаживае- мой программы в самой минимальной степени. Обычно тест служит для провер- ки правильности выполнения самых внешних функций программы, например, об- ращения к ней и выхода из нее. Т е с т г р а н и ч н ы х з н а ч е н и й, или "с т р е с с о в ы й тест"("high-low bias checking","twin check"). Тест проверяет работу про- граммы для граничных значений параметров, определяющих вычислительный про- цесс. Часто для граничных значений параметра работа программы носит осо- бый характер, который тем самым требует и особого контроля. Если в качестве примера рассмотреть тестирование подпрограммы сортиров- ки, то нужно исследовать следующие ситуации: α) сортируемый массив пуст; β) сортируемый массив содержит только один элемент; γ) все элементы в сортируемом массиве одинаковы; δ) массив уже отсортирован. Лоуренс Питер приводит следующий поучительный пример. Компьютер одной компании по страхованию автомобилей выслал проживающему в Сент-Луисе кли- енту счет на сумму 0.00 долларов. Когда же компьютер направил ему "послед- нее уведомление" с угрозой расторгнуть договор, этот челевек обратился за помощью к своему финансовому агенту. Тот пришел к выводу, что лучший спо- соб уладить дело - отправить компьютеру чек на 0.00 долларов. Это было сделано, и в ответ пришло подтверждение с благодарностью и заверением,что договор остается в силе! А в а р и й н ы й т е с т. Тест проверяет реакцию программы на возник- новение разного рода аварийных ситуаций в программе, в частности вызван- ных неправильными исходными данными, то есть проверяется диагностика, вы- даваемая программой, а также окончание ее работы или, может быть, попытка исправления неверных исходных данных (разработчики реальных программ зна- ют, что пользователи подобны шаловливому ребенку, играющему в отсутствие старших с телевизором или магнитофоном). Поэтому в реальных программах, спроектированных с достаточной надежнос- тью,совокупности приказов, которые должны работать только в особых аварий- ных ситуациях, занимают порой более 90% общего объема программы. Эти сово- купности приказов называют иногда блоками "з а щ и т ы от д у р а к а" ("fool proof"). Такие системы, обладая достаточной надежностью,устойчиво функционируют даже при самых неподходящих действиях работающих с ними лю- дей. В журнале "Компьютерный мир" рассказывалось об одной довольно дорого обошедшейся ошибке. При вводе данных палец оператора случайно задел не ту клавишу, и автомобиль "Форд", принадлежащий одному из граждан,стал стоить не 950, а 7000950 долларов! А если обладаешь таким дорогим имуществом, нужно платить большой налог. Налог составил 290000 долларов. Когда ошибка обнаружилась, эта сумма была уже включена в бюджет города. Владелец авто- мобиля получил счет на 290 тыс.долларов,но платить не стал. Причиной ошиб- ки следует считать, конечно, не неверно нажатую клавишу, а плохую програм- му, автор которой не позаботился о достаточно мощных процедурах контроля входных данных. Процветающие фирмы, занятые разработкой программного обеспечения, спе- циально нанимают профессионально неподготовленных людей, чтобы они порабо- тали с вновь созданными программами. В их задачу входит за короткое время сделать столько неправильных обращений к программе, сколько пользователь не сделает и за долгий период. Например, когда программа запрашивает цену товара,оператор набирает на клавиатуре слово "Почему?" вместо числа и т.д. Одним из свойств хорошей программы является, как говорят специалисты, ее д р у ж е с т в е н н о с т ь. Это означает, что в случае ошибки поль- зователя программа выдаст на экран сообщение, направленное на оказание по- мощи в выполнении поставленной задачи. Это может быть подсказка, наводя- щий вопрос, разъяснение противоречивости или иной ошибки в требованиях пользователя. В лучших образцах таких программ вместо сообщения "треугольника с таки- ми сторонами не бывает" на экране выдаются тексты вида: "Вероятно, Вы оши- блись. На плоскости невозможно построить треугольник со сторонами, имеющи- ми длины 1, 1, 100. Попытайтесь изменить значения длин сторон." Существуют программы, которые не только обнаруживают, но и исправляют ошибки. Например, при проектировании какого-то прибора инженер за диспле- ем подбирает параметры его деталей и вводит приказ запомнить величину со- противления 150 кОм. Тогда компьютер может ответить: "Вероятно, Вы ошиб- лись. К сожалению, известны только данные о выпускаемых сопротивлениях с номиналами 160 и 180 кОм. Попытайтесь изменить значение номинала сопротив- ления. Если Вам подходит значение 180 кОм, нажмите клавишу "ВВОД"". Про- грамма лишь предложила один из возможных вариантов взамен явно неосущест- вимого. Окончательное решение осталось за пользователем. Однако дружественность программ должна иметь четкие границы, иначе ав- томатическое исправление ошибок превратится в медвежью услугу пользовате- лю. Одно из свойств хороших программ состоит в том, что пользователь не должен при работе с ними удивляться, они не должны делать ничего неожи- данного, т.к. эти неожиданности редко бывают приятными и полезными. В связи с этим интересны рекомендации по проектированию программ веде- ния диалога [60], где автор вообще выступает против какого-либо очелове- чивания вычислительных систем: "Создание ЭВМ, ведущих себя как люди, напоминает попытки строить само- леты, машущие крыльями. ...Создавать вычислительные системы,которые будут вести себя как инструменты...это означает избегать диалоговых систем,начи- нающихся с реплик "Привет, я Бетси 307, назови свое имя". Не давайте человеческих имен или признаков программам и системам. Не приписывайте вычислительным системам свободы воли или поведения, на- поминающего живое существо. Если что-то происходит неправильно,не обвиняйте в этом ЭВМ или програм- му. Помните, что инструменты не делают ошибок, они или отказывают или ло- маются". VIII.3. М е т о д ы л о к а л и з а ц и и о ш и б о к Если Вы думаете, что разработка и кодирова- ние программы - вещь трудная, то Вы еще ни- чего не видели. Популярный афоризм После того как установлено, что в программе или в конкретном ее блоке имеется ошибка, возникает задача ее л о к а л и з а ц и и, то есть уста- новления точного места в программе, где она находится. Можно считать, что п р о ц е с с л о к а л и з а ц и и о ш и б о к состоит из трех основ- ных компонентов [49]: 1) получение вручную или с помощью компьютера тестовых результатов; 2) анализ тестовых результатов и сверка их с эталонными; 3) выявление ошибки или формулировка предположения о характере и месте ошибки в программе. Если ошибка найдена, то производится ее исправление;в противном случае осуществляется переход к пункту 1, т.е. к получению дополнительных тесто- вых результатов. В ходе поиска ошибок программист, анализируя полученные на компьютере результаты, проверяет различные предположения о характере и месте ошибки в программе, которые при этом приходят ему в голову. В случае несоответст- вия этих гипотез выданным результатам, программист выдвигает новые гипоте- зы и проверяет их или в уме, или проводя вычисления за столом, или обраща- ясь за новыми результатами к компьютеру. В таком характере работы программиста можно найти нечто общее с рас- четом вариантов, который осуществляет шахматист (или шашист) во время иг- ры, когда он путем расчетов в уме ищет выигрывающий ход в позиции на шах- матной доске, подвергая проверке один из заслуживающих внимания ходов за другим. Не найдя выигрывающего хода, шахматист делает какой-то,по его мне- нию, хороший ход, приближающий его к цели.Так и программист,не найдя ошиб- ки путем исследования полученных тестовых результатов, делает новое пред- положение о месте или о характере ошибки, вставляет новую отладочную пе- чать или изменяет программу ("ход программиста"), а ЭВМ выдает новые тес- товые результаты ("ход ЭВМ"). Компьютер выступает как своеобразный парт- нер, задача которого заключается в том, чтобы вскрыть ошибки в рассуждени- ях программиста, как бы сформулированных им в тексте отлаживаемой прог- раммы. Продолжая аналогию, можно сказать, что подобно тому, как нельзя ре- ально надеяться выиграть партию в два-три хода, так же нельзя найти все ошибки в реальной программе за одно-два обращения к компьютеру. Программистов, успешно проводящих поиск ошибок в программе,можно услов- но разделить на "аналитиков" и "экспериментаторов" [49]. А н а л и т и к и отлаживают программу, редко используя компьютер и применяя простейшие способы получения тестовых результатов на компьютере путем тщательного изучения этих результатов и на основании глубокого и четкого представления о структуре и особенностях алгоритма отлаживаемой программы. Э к с п е р и м е н т а т о р ы ищут ошибки, изощренно используя все- возможные о т л а д о ч н ы е с р е д с т в а, быстро получая необходи- мые для все большей и большей локализации ошибок промежуточные результа- ты и легко ориентируясь в них. Конечно, идеальным является случай, когда программист сочетает в себе способность к глубокому расчету в уме различных вариантов работы програм- мы и навыки работы с разнообразными отладочными средствами. Если успех аналитического подхода к поиску ошибок зависит, видимо, от способностей и опыта программиста, то изучение и использование средств,по- могающих локализации ошибок - главным образом средств получения необходи- мых промежуточных результатов, - доступно каждому программисту. Имеются такие средства и в языке программирования MSX-BASIC! Под п р о м е ж у т о ч н ы м и результатами выполняемой программы договоримся понимать как арифметические результаты, характеризующие значе- ния используемых величин, так и л о г и ч е с к и е "результаты",т.е. ин- формацию, содержащую сведения о факте или последовательности выполнения операторов программы. Учтите следующие принципы Г.Майерса [51]. 1. Д у м а й т е ! Наиболее эффективный метод отладки заключается в глубоком анализе ин- формации об ошибках. Для ее эффективного проведения ┌──────────────────────────────────────────────────────────────┐ │ специалист должен обладать способностью точно определять │ │ большинство ошибок без использования компьютера. │ └──────────────────────────────────────────────────────────────┘ 2. Используйте средства отладки только как вспомогательные.Не применяй- те эти средства вместо того, чтобы обдумывать задачу. ┌────────────────────────────────────────────────────────────────────┐ │ Ясно, что такие средства как трассировка (п.VIII.3.1) и │ │ аварийная печать (п.VIII.3.2) отражают случайный подход к отладке.│ └────────────────────────────────────────────────────────────────────┘ Эксперименты показали, что программисты, избегающие применения средств отладки, даже при отлаживании незнакомых им программ выполняют ее лучше, чем те, кто пользуется этими средствами. ┌─────────────────────────────────────────┐ 3. │ Избегайте экспериментирования! │ └─────────────────────────────────────────┘ Пользуйтесь им только как последним средством. Наиболее общей ошибкой, которую допускают начинающие программисты,занимающиеся отладкой, является попытка решить задачу посредством внесения в программу экспериментальных изменений ("Я не знаю, что неправильно, но я изменю этот оператор IF и по- смотрю, что получится"). Этот абсолютно неверный подход не может даже рас- сматриваться как отладка; он основан на случайности. Экспериментирование не только уменьшает вероятность успеха, но часто и усложняет задачу, пос- кольку при этом в программу вносятся новые ошибки. 4. Если Вы зашли в тупик, отложите рассмотрение программы. Наше подсо- знание является мощным механизмом решения проблем. То, что мы часто припи- сываем вдохновению, оказывается всего лишь выполненной подсознанием рабо- той по решению задачи, тогда как наша сознательная деятельность в это вре- мя связана с чем-нибудь другим, например с едой, прогулкой или просмотром кинофильма. Если Вы не можете локализовать ошибку в приемлемые сроки(пред- положительно за 30 минут для небольших программ и за несколько часов для больших), прекратите поиски и займитесь каким-нибудь другим делом,так как эффективность Вашей работы, во всяком случае, значительно снизится. Проб- лему следует "забыть" до тех пор, пока Вы либо подсознательно не найдете ее решения,либо отдохнете и будете готовы вновь рассмотреть симптомы ошиб- ки. И наконец, ┌────────────────────────────────────────────────────────────────────────┐ │ если Вы окончательно зашли в тупик, то изложите задачу кому-нибудь еще.│ └────────────────────────────────────────────────────────────────────────┘ Сделав это, Вы, вероятно, обнаружите что-то новое. Часто случается так, что, просто пересказав задачу хорошему слушателю,Вы вдруг найдете решение без какой-либо помощи с его стороны. Далее мы рассмотрим некоторые, ставшие уже классическими, способы по- лучения программистом промежуточных результатов, вырабатываемых отлаживае- мой программой.