Тестирование-книга. Ю. Н. Артеменко Научный редактор
Скачать 6.27 Mb.
|
Неправильное преобразование данных из одного формата в другой Программа просит ввести число между 0 и 9. Вы вводите 1. Это сим вол, и программа получает его код — 49. Чтобы превратить полученную информацию во введенное вами число, программа должна вычесть из зна чения кода число 48. Вместо этого она вычитает 49 и получает 0. Каждый фрагмент программного кода, в котором выполняется преобразование формата данных, является потенциальным источником ошибок. Имейте в виду, что обычно в программе выполняется огромное количество преобра зований — между различными форматами числовых значений, числами и строками, символами и цифрами и т.п. Неверная формула Во многих программах используются сложные формулы. Программист может ошибиться, переписывая формулу из книги, взять вообще не ту формулу или же неправильно ее запрограммировать. Неправильное приближение Многие формулы для приблизительной оценки значений разработаны задолго до появления компьютеров. Они являются исключительно полез ным достижением, позволяющим сократить количество вычислений, но результаты получаются очень неточными. Благодаря вычислительной мощи современной компьютерной техники, в значительной части этих формул больше нет нужды. Однако они присутствуют во многих учебниках и по- прежнему используются многими программами, отчего страдает точность результатов. Поэтому планируя тестирование программ, в которых много математических и статистических расчетов, позаботьтесь, чтобы в группе был хоть один специалист, основательно разбирающийся в этих вопросах. Начальное и последующие состояния Как правило, выполнение программой определенной функции начина ется с инициализации ее переменных. Процесс инициализации заключается в объявлении переменных, определении их типов, выделении для них памяти и присвоении им начальных значений. Начальные значения часто читаются программой с диска. Что, если там не окажется файла? Иници ализация одних данных выполняется при запуске программы, других — при первом вызове функции, а третьих — при каждом ее вызове. Приложение: Распространенные программные ошибки 497 Возможные стратегии инициализации данных определяются языком программирования, а их выбор в каждом конкретном случае — нуждами программы. Вот несколько примеров. • Объявленные в функции переменные могут сохранять свои значения от одного ее вызова до другого. Такие переменные часто называют статическими. Их используют для тех данных, которые функция должна сохранить для следующего вызова. Другие переменные эта же функция может инициализировать при каждом своем запуске. Для правильной работы со статическими переменными функция должна определять, была ли она уже вызвана хотя бы раз, и, если нет, инициализировать все эти переменные. • Локальные переменные функции могут стираться из памяти сразу же после завершения ее работы. Такие переменные называют динами ческими. Каждый раз, когда функция вызывается, она должна по вторно инициализировать все свои динамические переменные. • За присвоение переменной начального значения может отвечать как компилятор, так и программист. Если программист не присвоил переменной начальное значение, одни компиляторы присваивают ей 0, а другие не заботятся об очищении выделенного переменной участка памяти, и в ней может оказаться все что угодно. Ошибки инициализации обычно проявляются при первом вызове фун кции, когда она не инициализировала свои переменные правильно, и при втором, когда она неправильно выполнила повторную инициализацию. Иногда ошибки инициализации зависят от пути выполнения программы. За выполнение инициализации переменных могут отвечать фрагменты кода функции, выполняющиеся в одних случаях, и не выполняющиеся в других. Не присвоены начальные значения Многие компиляторы очищают выделяемую для переменных память, таким образом инициализируя их нулями или пустыми значениями. Одна ко, когда начальное значение переменной не должно быть нулевым, мно гие программисты забывают его присвоить. Такая ошибка обычно обнаруживается сразу, как только программа обращается к неправильно инициализированным данным. Не инициализирована переменная, управляющая циклом Переменная, управляющая циклом, определяет, сколько раз будут выпол нены составляющие его операторы. Например, программа печатает первые 10 строк текстового файла. Как только достигается 11-я строка, програм- 4 9 8 Часть III: Управление проектами и группами ма останавливается. При следующем запуске функция, выполняющая всю эту работу, должна снова присвоить счетчику строк значение 1. Не инициализирован указатель В переменных-указателях хранятся адреса памяти. Такая переменная может, например, указывать начало определенной строки. Значение указа теля может меняться: например, вначале он содержит адрес первого сим вола строки, затем второго, третьего и т.д. Если указатель неправильно инициализировать или забыть инициализировать вообще, он может содер жать неверный адрес. Если программа отображает фрагмент строки, "му сор" или не те элементы массива, скорее всего, ошибка связана с использованием указателей. Не очищена строка В строковых переменных хранятся последовательности символов. Если значением числовой переменной может быть 5, то значением строковой — Привет, меня зовут Сергей. Строковые переменные могут отличаться по длине. Переменной, в которой хранилась строка Привет, меня зовут Сер гей можно присвоить более короткую строку Пока. Присваивание может работать по-разному (это зависит от компилятора и выбранного способа). Если программист не допишет в конец новой строки пробелы, в результа те может получиться Покает, меня зовут Сергей. Не инициализированы регистры Регистрами называются специальные области памяти объемом в не сколько байтов, обычно находящиеся внутри центрального процессора. Хранящимися в них данными компьютер манипулирует гораздо быстрее, чем теми, которые записаны в обычной оперативной памяти. Поэтому программисты часто пользуются регистрами для временного хранения дан ных. Они копируют несколько переменных в регистры, работают с ними, а затем копируют их значения обратно в память. После этого бывает не обходимо восстановить исходные значения регистров, о чем часто забыва ют. Забывают также загрузить данные в один или несколько регистров. Не сброшен флаг Флаги — это переменные, значения которых являются сигналами об определенных условиях. Флаг может быть установлен (истинен, включен, равен 1) или сброшен (очищен, ложен, выключен, равен 0 или -1). В нор мальном состоянии флаг сброшен. Если одна часть программы хочет сооб щить другой о наступлении определенного события — о сбое, инициализации переменной, ее переполнении, о том, что пользователь нажал на клавишу, и т.п., тогда она устанавливает флаг этого события. Приложение: Распространенные программные ошибки 4 9 9 Состояние флага всегда должно соответствовать условию, с которым он связан. Например, подпрограмма может сбрасывать свой флаг при вызове и устанавливать при выходе. Таким образом, установленный флаг будет означать, что выполнение процедуры завершено нормально. Разумеется, никакая другая процедура этим флагом пользоваться не должна. Некоторые программы устанавливают и сбрасывают одни и те же флаги во многих местах кода, так что трудно сказать, достоверны их значения или нет. Данные должны были инициализироваться в другом месте Функция может инициализировать не все свои данные. Например, переменные, которые она использует совместно с какой-нибудь другой функцией, могут инициализироваться обеими. Предположим, что несколь ко функций из одного и того же меню используют несколько переменных, инициализируемых программой при отображении этого меню. Если другого способа вызвать эти функции нет, тогда все в порядке. Но что, если одна из этих функций присутствует и в другом меню либо вызывается из блока обработки ошибки в другой функции или еще откуда-нибудь? Не выполнена повторная инициализация Программист может забыть удостовериться, что при повторном вызове функции в ее переменных содержатся правильные значения. Если, напри- \ мер, переменная сохраняет свое значение от вызова к вызову и при ее создании компилятор автоматически присваивает ей 0, программисту это го делать не нужно. Но только при первом вызове. Когда функция вызва на второй раз, в переменной, скорее всего, уже не нулевое значение. Ожидает ли программист, что в ней всегда О? Имейте в виду, что инициализация не всегда выполняется в самом начале подпрограммы. При одном способе входа в нее или одном пути ее выполнения данные могут быть инициализированы правильно, а при дру гом — нет. Предположение, что данные не были инициализированы Иногда программа может инициализировать данные несколько раз под- ряд. Такое повторение в общем не приносит вреда, за исключением потерь I времени. Путаница со статическими и динамическими переменными Динамические переменные создаются при входе в функцию и разруша ются при выходе из нее, в то время как статические создаются при самом 5 0 0 Часть III: Управление проектами и группами первом входе в функцию и сохраняют свои значения от вызова к вызову. В одних языках программирования локальные переменные функций всегда являются динамическими, в других же программист может при их описа нии определять и время их жизни. В этом случае программист легко мо жет забыть, как он объявил переменную, и обращаться со статическими данными как с динамическими или наоборот. В результате программа будет работать, исходя из неверных предположений о содержимом переменных. Не предполагавшаяся модификация данных, выполняемая другими подпрограммами После инициализации подпрограмма может использовать переменную, не меняя ее значение. При повторном входе в подпрограмму или после вызова из нее других подпрограмм эта переменная может не инициализи роваться, поскольку программист полагает, что ее значение осталось неиз менным. Однако для этого переменная должна быть объявлена как локальная для данной функции. Если же программист забыл это сделать или язык программирования не поддерживает концепции локальных пере менных, любая другая процедура может изменить значение данной пере менной. Ошибочная инициализация Программист может присвоить переменной неверное значение, вместо переменной с плавающей запятой объявить целую, объявить динамическую переменную вместо статической или глобальную вместо локальной. Мно гие из этих ошибок выявляются компилятором, но некоторые проявляют ся и в процессе выполнения программы. Зависимость от инструментальных средств, которыми пользователь может не уметь пользоваться или которых он может не иметь Такое случается, хотя и не часто. Главное же, что на ошибки такого рода можно просто не обратить внимание. Например, программист пред полагает, что для изменения некоторых аспектов операционной среды пользователь продукта воспользуется определенной утилитой. Тестировщик выполнит необходимую настройку перед первым запуском программы, но потом о ней просто забудет. Ошибки управления потоком Управление потоком — это определение того, в какой последовательно сти и при каких обстоятельствах выполняются операторы программы. Если очередным действием программы оказывается не то, которое предполагал Приложение: Распространенные программные ошибки 5 0 1 программист, значит, имеет место ошибка управления потоком. Результа том может быть очевидно неверное поведение программы или ее остановка, хотя последствия могут оказаться и не настолько очевидными. Очевидно неверное поведение программы Программа отображает на экране "мусор" или выводит искаженное изображение, сохраняет "мусор" на диске, невпопад начинает печатать или выполняет совершенно неожиданные действия. Все это означает, что про грамма вышла из-под контроля. Такие ошибки наиболее очевидны, и их легко найти и исправить. Хотя они все выглядят похожими, их причины могут быть различны. Ниже приводится несколько примеров подобных ошибок. Искать их специально не имеет смысла, если только вы не обла даете некоторыми познаниями в программировании и не знакомы с внут ренней структурой программы. Переход по GOTO Оператор GOTO передает управление другой части программы. Если управление передано не туда, возможны самые разнообразные, но, как правило, очевидные последствия. Программа может "зависнуть", отобра зить на экране нечто явно неподходящее и т.п. У программистов команда GOTO крайне непопулярна, и ее использо вание считается дурным тоном. Сторонники структурного программирова ния утверждают, что нет такой программы, при написании которой без этих операторов нельзя было бы обойтись. И более того, без них програм мы более последовательны и читабельны и содержат меньше ошибок (Йор дан (Yourdon, 1976)). Ошибки наиболее вероятны в следующих ситуациях. • Выполняется переход назад, особенно к оператору, перед которым выполнялась необходимая инициализация данных и устройств; • Выполняется непрямой переход — по адресу, хранящемуся в пере менной. Если значение переменной изменится, программа перейдет к другому месту кода. Читая исходный текст, трудно сказать, пра вильное ли значение будет находиться в переменной при каждом обращении. Логика, основанная на определении вызывающей подпрограммы Подпрограмма может определять, что ей делать, в зависимости от того, откуда она была вызвана. Если она не правильно определит то или другое, произойдет ошибка. Как правило, при использовании данного приема вызывающая процедура устанавливает флаг или присваивает определенной 5 0 2 Часть III: Управление проектами и группами переменной значение, позволяющее определить, откуда выполнен вызов. Если этими же флагами и переменными пользуются и другие процедуры, программа становится запутанной и в ней очень вероятны ошибки. Поэто му такая логика программирования крайне непопулярна. Использование таблиц переходов Программы могут пользоваться таблицами адресов, по которым осуще ствляется переход в той или иной ситуации. Таблица адресов может хра ниться в дисковом файле, чтобы ее можно было редактировать, не меняя при этом программу. Это очень удобно для дальнейшей поддержки продук та, но связано с определенным риском. • В таблице могут быть неверные адреса, особенно если она редакти руется вручную. • Если таблица длинная, при ее составлении легко допустить ошибки и к тому же пропустить их в ходе тестирования. • Предположим, что в таблице пять элементов и программа выбира ет один из них в зависимости от значения определенной перемен ной. Что будет, если значением этой переменной окажется 6? • После модификации кода можно забыть обновить таблицу перехо дов. Выполнение данных По содержимому группы байтов невозможно определить, хранится ли в них команда программы либо один или несколько символов, число или адрес памяти. Программа просто держит различные типы информации в разных местах памяти. Если же она попытается выполнить данные как команду, то, скорее всего, "зависнет". Некоторые компьютеры выявляют попытки выполнения "невозможных" команд и останавливают программу, выдав сообщение об ошибке. Программа может попытаться выполнить данные вместо команды в следующих случаях. (А) Данные скопированы в область памяти, зарезервированную для кода. Вот примеры того, как это может произойти. • Указатели — это переменные, в которых хранятся адреса памяти. Программист может записать определенные данные по адресу, хранящемуся в указателе, но из-за ошибки в логике программы этот указатель может содержать адрес не внутри области данных, а внутри области программного кода. • Некоторые языки программирования не проверяют границ мас сивов. Предположим, объявлен массив MYARRAY из трех эле- Приложение: Распространенные программные ошибки 5 0 3 ментов - MYARRAY[1], MYARRAY[2] и MYARRAY[3]. Что произойдет, если программа попытается сохранить данные в эле менте MYARRAY[2044]. Если язык программирования не выяв ляет таких ошибок, данные будут записаны в область памяти, адрес которой равен адресу массива плюс 2044, и все это умно женное на длину элемента массива в байтах. По этому адресу могут располагаться программный код или данные, память вне шних устройств, операционная система, другие программы — все что угодно, но только не массив MYARRAY. (Б) Программа переходит по адресу, принадлежащему области данных, а не кода. • Ошибочная запись в таблице адресов перехода, которой управля ется программа. • Некоторые компьютеры делят память на сегменты. Компьютер интерпретирует все содержимое сегмента кода как код и все со держимое сегмента данных как данные. Если программа каким- то образом изменит адреса сегментов, компьютер будет выбирать последующие команды не из того места памяти. Переход к подпрограмме, которая отсутствует в памяти Для экономии памяти компьютеры часто выгружают из памяти фраг менты слишком объемных программ и данных. Такие фрагменты назы ваются оверлеями {overlay), а используемая технология — подкачкой или свопингом {swapping). Когда программе необходима определенная подпрог рамма или данные, компьютер выгружает на диск какой-нибудь ненужный фрагмент, а вместо него загружает в память требующийся. Если программа сама отвечает за подкачку, перед переходом по опре деленному адресу она должна удостовериться, что данный фрагмент кода находится в памяти, иначе она выполнит переход не к тому месту кода. С подкачкой связаны и проблемы производительности. На проверку наличия нужных оверлеев и их считывание с диска тратится компьютерное время. Поэтому важно организовать оверлейную программу таким образом, чтобы не было необходимости выполнять подкачку слишком часто. |