Вопросы отладки и тестирования программного изделия

СОДЕРЖАНИЕ
Введение
1. Цели, объекты и проблемы отладки и тестирования программного изделия
2. Виды тестирования
2.1. Основные понятия
2.2. Автономная отладка модуля
2.3. Комплексная отладка программного средства
2.4. Модульное тестирование
2.5 Регрессионное тестирование
2.6. Нагрузочное тестирование
2.7. Структурное тестирование
2.8. Функциональное тестирование
3. Уровни тестирования
3.1 Комбинирование уровней тестирования
3.2 Методы тестирования на соответствие стандартам,
обеспечивающим переносимость прикладных программ
3.2.1 Цель проведения
3.2.2. Уровни тестирования
3.2.3 Уровни сложности элементов тестирования
3.3 Тестовое покрытие
3.4 Тестовая документация
3.4.1. Стратегия тестирования
3.4.2. Планы тестирования
3.4.3. Разработка наборов тестовых данных (тест-кейсов)
3.4.4. Тестовые процедуры
3.4.5. Протоколы
3.4.6. Документирование результатов тестирования
4. Интеграционное тестирование
5. Системное тестирование
6. Методы тестирования
6.1. Инспекция кода
6.2. Разбиение на эквивалентные части
6.3 Анализ граничных величин
6.4 Многократная разработка
7. Классификация ошибок
Заключение
Библиографический список
ВВЕДЕНИЕ
В настоящее время в самых различных сферах народного хозяйства, военного дела и других отраслях человеческой деятельности получили широкое применение персональные ЭВМ (ПЭВМ). Сложность их программного обеспечения (ПО) достигла значительных величин. В дальнейшем будет наблюдаться её всё более прогрессивный рост.
Оборона, авиация и космос, медицина, технологические процессы на современных ядерных, химических и других производствах – вот неполный перечень тех предметных областей, где низкое качество ПО и даже единичные дефекты в нём находят воплощение в терминах человеческих жизней и разрушенных материальных ценностей. Над подобными “ответственными” системами работает целая отрасль с огромными денежными затратами, располагающая значительным количеством высококвалифицированных программистов и проектировщиков, хорошо поставлен менеджмент, отлажены процессы разработки, испытаний и контроля. И, тем не менее, ПО даёт порой такой сбой, резонанс от которого бывает весьма громким.
Основными причинами проявления ошибок ПО являются недостаточно высокий уровень технологии производства программных средств и их чрезмерная сложность. И несмотря на то, что в области качества и надёжности программных средств за последнее время достигнуты определённые положительные результаты и ошибки в процессе функционирования ПО сравнительно редки, проблема обеспечения высокой надёжности сложного ПО остаётся достаточно злободневной. Для решения данной проблемы нужен комплексный, системный подход.
Причина обращения к данному принципу в теории надёжности техники и ПО одна и та же. Современная теория оперирует с численными оценками. Хорошо известно парадоксальное противоречие: чем более надёжный продукт мы создаём, тем труднее оценить и подтвердить его надёжность. Однако на практике эту задачу как-то необходимо решать, чтобы при реализации системой цели можно было принять правильное решение. Сжатие информации об ошибках системы во времени в данном случае есть единственный выход из ситуации, когда в процессе её испытаний в реальном времени может не хватить ни времени испытаний, ни ресурсных затрат, или, в крайнем случае, сама система может морально устареть.
Многие организации, занимающиеся созданием программного обеспечения, до 30% средств, выделенных на разработку программ, тратят на испытания, что составляет миллиарды долларов по всему миру в целом. И все же, несмотря на громадные капиталовложения, знаний о сути испытаний явно не хватает и большинство программных продуктов ненадежно.
Под испытанием программной продукции следует понимать экспериментальное определение количественных и/или качественных характеристик свойств продукции при ее функционировании в реальной среде и/или моделировании среды функционирования.
Невозможно гарантировать отсутствие ошибок в нетривиальной программе; в лучшем случае можно попытаться показать наличие ошибок. Если программа правильно ведет себя для солидного набора тестов, нет оснований утверждать, что в ней нет ошибок; со всей определенностью можно лишь утверждать, что не известно, когда эта программа не работает. Конечно, если есть причины считать данный набор тестов способным с большой вероятностью обнаружить все возможные ошибки, то можно говорить о некотором уровне уверенности в правильности программы, устанавливаемом этими тестами.
Надежность невозможно внести в программу в результате тестирования, она определяется правильностью этапов проектирования. Наилучшее решение проблемы надежности — с самого начала не допускать ошибок в программе. Однако вероятность того, что удастся безупречно спроектировать большую программу, бесконечно мала.
Испытания таких программ, как системы реального времени, операционные системы и программы управления данными, которые сохраняют «память» о предыдущих входных данных, особенно трудны. Нам потребовалось бы тестировать программу не только для каждого входного значения, но и для каждой последовательности, каждой комбинации входных данных. Поэтому исчерпывающее тестирование для всех входных данных любой разумной программы неосуществимо.
Испытание является завершающим этапом разработки. Ему предшествует этап статической и динамической отладки программ. Основным методом динамической отладки является тестирование. В узком смысле цель тестирования состоит в обнаружении ошибок, цель же отладки—не только в обнаружении, но и в устранении ошибок. Однако ограничиться только отладкой программы, если есть уверенность в том, что все ошибки в ней устранены, нельзя. Цели у отладки и испытания разные. Полностью отлаженная программа может не обладать определенными потребительскими свойствами и тем самым быть непригодной к использованию по своему назначению. Не может служить альтернативой испытанию и проверка работоспособности программы на контрольном примере, так как программа, работоспособная в условиях контрольного примера, может оказаться неработоспособной в других условиях применения. Попытки охватить контрольным примером все предполагаемые условия функционирования сводятся, в конечном счете, к тем же испытаниям.
1. ЦЕЛИ, ОБЪЕКТЫ И ПРОБЛЕМЫ ОТЛАДКИ И ТЕСТИРОВАНИЯ
ПРОГРАММНОГО ИЗДЕЛИЯ
Для программ, создаваемых на уровне продукции производственно-технического назначения и отчуждаемых от разработчика, испытания являются одним из важнейших этапов жизненного цикла, на котором проверяются и фиксируются достигнутые показатели качества программного средства (ПС).
В соответствии с ГОСТ 16504—81 под испытанием промышленной продукции понимают экспериментальное определение количественных и/или качественных характеристик объекта испытания как результата воздействия на него; при его функционировании; при моделировании объекта и/или воздействия. Под испытанием программной продукции следует понимать экспериментальное определение количественных и/или качественных характеристик свойств продукции при ее функционировании в реальной среде и/или моделировании среды функционирования.
Целью испытания является экспериментальное определение фактических (достигнутых) характеристик свойств испытываемого ПС и определение степени соответствия созданного комплекса программ техническому заданию, полученному от заказчика. Эти характеристики могут быть как количественными, так и качественными. Важно, чтобы на их основе можно было сделать вывод о пригодности данного ПС к использованию по своему назначению. Если вывод отрицательный, то образец ПС возвращается на доработку. Таким образом, перекрывается доступ недоброкачественной продукции к пользователю. Непосредственно в ходе испытаний качество ПС может и не измениться, так как локализация ошибок не является целью испытания. Вместе с тем некоторые дефекты в программах и документации могут устраняться по ходу испытания.
В зарубежной литературе, в том числе в стандартах на программное обеспечение, понятие “испытание” часто отождествляют с понятием “тестирование”. Например, в Std IEEE 829—1983 “Документация тестов программного обеспечения” (США) дано следующее определение тестирования: “ .процесс активного анализа ПО на предмет обнаружения расхождения между реальными и требуемыми нормами ПО (т. е. наличия ошибок в программах) и с целью оценки характеристик элементов ПО”. Данное определение объединяет два приведенных определения термина “испытание” с той лишь разницей, что при принятой концепции поиск и локализация ошибок не являются явно выраженными целями испытания. С учетом высказанных соображений термин “тестирование”, используемый в зарубежной литературе, будем интерпретировать как испытание методом тестирования.
Важная особенность испытаний программы состоит в наличии достаточно полных эталонов, которым должен соответствовать комплекс программ, — требований технического задания.
За относительно короткий период приемосдаточных испытаний трудно провести достаточно полное тестирование, демонстрирующее достигнутое качество сложного комплекса программ. Поэтому для обеспечения их высокого качества в техническом задании целесообразно задавать технологию его проектирования и условия поэтапной проверки основных компонент в процессе разработки. Для этого до начала разработки в процессе формирования технического задания формируются основы методики тестирования и проверяемые характеристики программ при испытаниях. В этом случае испытатель получает возможность поэтапно и глубоко знакомиться с создаваемым изделием и подготовиться к испытаниям программных средств. Одновременно уточняются и конкретизируются техническое задание и методика тестирования программ на завершающих приемосдаточных испытаниях.
Длительность испытания зависит от типа, конфигурации (сложности) ПС, а также от целей и степени автоматизации рассматриваемого технологического процесса. При испытании операционных систем она колеблется от одного до шести месяцев. Сложные программные комплексы после интеграции могут испытываться и более длительное время.
2. Виды тестирования
2.1. Основные понятия
Отладка ПС – это деятельность, направленная на обнаружение и исправление ошибок в ПС с использованием процессов выполнения его программ. Тестирование ПС – это процесс выполнения его программ на некотором наборе данных, для которого заранее известен результат применения или известны правила поведения этих программ. Указанный набор данных называется тестовым или просто тестом. Таким образом, отладку можно представить в виде многократного повторения трех процессов: тестирования, в результате которого может быть констатировано наличие в ПС ошибки, поиска места ошибки в программах и документации ПС и редактирования программ и документации с целью устранения обнаруженной ошибки. Другими словами:
Отладка = Тестирование + Поиск ошибок + Редактирование.
В зарубежной литературе отладку часто понимают только как процесс поиска и исправления ошибок (без тестирования), факт наличия которых устанавливается при тестировании. Иногда тестирование и отладку считают синонимами. В нашей стране в понятие отладки обычно включают и тестирование, поэтому мы будем следовать сложившейся традиции. Следует отметить, что тестирование используется и как часть процесса аттестации ПС.
Рекомендации по организации отладки:
1. Считайте тестирование ключевой задачей разработки ПС, поручайте его самым квалифицированным и одаренным программистам; нежелательно тестировать свою собственную программу.
2. Хорош тот тест, для которого высока вероятность обнаружить ошибку, а не тот, который демонстрирует правильную работу программы.
3. Готовьте тесты как для правильных, так и для неправильных данных.
4. Избегайте невоспроизводимых тестов, документируйте их пропуск через компьютер; детально изучайте результаты каждого теста.
5. Каждый модуль подключайте к программе только один раз; никогда не изменяйте программу, чтобы облегчить ее тестирование.
6. Пропускайте заново все тесты, связанные с проверкой работы какой-либо программы ПС или ее взаимодействия с другими программами, если в нее были внесены изменения (например, в результате устранения ошибки).
2.2. Автономная отладка модуля
При автономной отладке каждый модуль на самом деле тестируется в некотором программном окружении, кроме случая, когда отлаживаемая программа состоит только из одного модуля. Это окружение состоит из других модулей, часть которых является модулями отлаживаемой программы, которые уже отлажены, а часть – модулями, управляющими отладкой (отладочными модулями). Таким образом, при автономной отладке тестируется всегда некоторая программа, построенная специально для тестирования отлаживаемого модуля. Эта программа лишь частично совпадает с отлаживаемой программой, кроме случая, когда отлаживается последний модуль отлаживаемой программы. По мере продвижения отладки программы все большую часть окружения очередного отлаживаемого модуля будут составлять уже отлаженные модули этой программы, а при отладке последнего модуля этой программы окружение отлаживаемого модуля будет целиком состоять из всех остальных (уже отлаженных) модулей отлаживаемой программы (без каких-либо отладочных модулей), т.е. в этом случае тестироваться будет сама отлаживаемая программа. Такой процесс наращивания отлаживаемой программы отлаженными и отлаживаемыми модулями называется интеграцией программы.
Отладочные модули, входящие в окружение отлаживаемого модуля, зависят от порядка, в каком отлаживаются модули этой программы, от того, какой модуль отлаживается и, возможно, от того, какой тест будет пропускаться.
При восходящем тестировании это окружение всегда будет содержать только один отладочный модуль (кроме случая, когда отлаживается последний модуль отлаживаемой программы), который будет головным в тестируемой программе и который называют ведущим (или драйвером). Ведущий отладочный модуль подготавливает информационную среду для тестирования отлаживаемого модуля (т. е. формирует ее состояние, требуемое для тестирования этого модуля, в частности, может осуществлять ввод некоторых тестовых данных), осуществляет обращение к отлаживаемому модулю и после окончания его работы выдает необходимые сообщения. При отладке одного модуля для разных тестов могут составляться разные ведущие отладочные модули.
При нисходящем тестировании окружение отлаживаемого модуля в качестве отладочных модулей содержит имитаторы всех модулей, к которым может обращаться отлаживаемый модуль, а также имитаторы тех модулей, к которым могут обращаться отлаженные модули отлаживаемой программы (включенные в это окружение), но которые еще не отлажены. Некоторые из этих имитаторов при отладке одного модуля могут изменяться для разных тестов.
На самом деле в окружении отлаживаемого модуля во многих случаях могут содержаться отладочные модули обоих типов по ниже следующим соображениям. Как восходящее, так и нисходящее тестирование имеет свои достоинства и свои недостатки.
К достоинствам восходящего тестирования относятся простота подготовки тестов и возможность полной реализации плана тестирования модуля.
Это связано с тем, что тестовое состояние информационной среды готовится непосредственно перед обращением к отлаживаемому модулю (ведущим отладочным модулем). Недостатками восходящего тестирования являются следующие его особенности:
· тестовые данные готовятся, как правило, не в той форме, которая рассчитана на пользователя (кроме случая, когда отлаживается последний, головной, модуль отлаживаемой программы);
· большой объем отладочного программирования (при отладке одного модуля часто приходится составлять для разных тестов много ведущих отладочных модулей);
· необходимость специального тестирования сопряжения модулей.
К достоинствам нисходящего тестирования относятся следующие его особенности:
· большинство тестов готовится в форме, рассчитанной на пользователя;
· во многих случаях относительно небольшой объем отладочного программирования (имитаторы модулей, как правило, весьма просты и каждый пригоден для большого числа, нередко – для всех, тестов);
· отпадает необходимость тестирования сопряжения модулей.
Недостатком нисходящего тестирования является то, что тестовое состояние информационной среды перед обращением к отлаживаемому модулю готовится косвенно – оно является результатом применения уже отлаженных модулей к тестовым данным или данным, выдаваемым имитаторами. Это, во-первых, затрудняет подготовку тестов, требует высокой квалификации исполнителя – тестовика, а во-вторых, делает затруднительным или даже невозможным реализацию полного плана тестирования отлаживаемого модуля. Указанный недостаток иногда вынуждает разработчиков применять восходящее тестирование даже в случае нисходящей разработки. Однако чаще применяют некоторые модификации нисходящего тестирования, либо некоторую комбинацию нисходящего и восходящего тестирования.
Исходя из того, что нисходящее тестирование, в принципе, является предпочтительным, остановимся на приемах, позволяющих в какой-то мере преодолеть указанные трудности. Прежде всего необходимо организовать отладку программы таким образом, чтобы как можно раньше были отлажены модули, осуществляющие ввод данных, – тогда тестовые данные можно готовить в форме, рассчитанной на пользователя, что существенно упростит подготовку последующих тестов. Далеко не всегда этот ввод осуществляется в головном модуле, поэтому приходится в первую очередь отлаживать цепочки модулей, ведущие к модулям, осуществляющим указанный ввод. Пока модули, осуществляющие ввод данных, не отлажены, тестовые данные поставляются некоторыми имитаторами: они либо включаются в имитатор как его часть, либо вводятся этим имитатором.
При нисходящем тестировании некоторые состояния информационной среды, при которых требуется тестировать отлаживаемый модуль, могут не возникать при выполнении отлаживаемой программы ни при каких входных данных. В этих случаях можно было бы вообще не тестировать отлаживаемый модуль, так как обнаруживаемые при этом ошибки не будут проявляться при выполнении отлаживаемой программы ни при каких входных данных. Однако так поступать не рекомендуется, так как при изменениях отлаживаемой программы (например, при сопровождении ПС) не использованные для тестирования отлаживаемого модуля состояния информационной среды могут уже возникать, что требует дополнительного тестирования этого модуля (а этого при рациональной организации отладки можно было бы не делать, если сам данный модуль не изменялся). Для осуществления тестирования отлаживаемого модуля в указанных ситуациях иногда используют подходящие имитаторы, чтобы создать требуемое состояние информационной среды. Чаще же пользуются модифицированным вариантом нисходящего тестирования, при котором отлаживаемые модули перед их интеграцией предварительно тестируются отдельно (в этом случае в окружении отлаживаемого модуля появляется ведущий отладочный модуль, наряду с имитаторами модулей, к которым может обращаться отлаживаемый модуль). Однако, представляется более целесообразной другая модификация нисходящего тестирования: после завершения нисходящего тестирования отлаживаемого модуля для достижимых тестовых состояний информационной среды следует его отдельно протестировать для остальных требуемых состояний информационной среды.
Часто применяют также комбинацию восходящего и нисходящего тестирования, которую называют методом сэндвича. Сущность этого метода заключается в одновременном осуществлении как восходящего, так и нисходящего тестирования, пока эти два процесса тестирования не встретятся на каком-либо модуле где-то в середине структуры отлаживаемой программы. Этот метод позволяет при разумном подходе воспользоваться достоинствами как восходящего, так и нисходящего тестирования и в значительной степени нейтрализовать их недостатки. Этот эффект является проявлением более общего принципа: наибольшего технологического эффекта можно добиться, сочетая нисходящие и восходящие методы разработки программ ПС. Именно для поддержки этого метода и предназначен архитектурный подход к разработке программ: слой квалифицированно разработанных и тщательно оттестированных модулей существенно облегчает реализацию семейства программ в соответствующей предметной области и их последующую модернизацию.
Весьма важным при автономной отладке является тестирование сопряжения модулей. Дело в том, что спецификация каждого модуля программы, кроме головного, используется в этой программе в двух ситуациях: во-первых, при разработке текста (иногда говорят: тела) этого модуля и, во-вторых, при написании обращения к этому модулю в других модулях программы. И в том, и в другом случае в результате ошибки может быть нарушено требуемое соответствие заданной спецификации модуля. Такие ошибки требуется обнаруживать и устранять. Для этого и предназначено тестирование сопряжения модулей. При нисходящем тестировании тестирование сопряжения осуществляется попутно каждым пропускаемым тестом, что считают самым сильным достоинством нисходящего тестирования. При восходящем тестировании обращение к отлаживаемому модулю производится не из модулей отлаживаемой программы, а из ведущего отладочного модуля. В связи с этим существует опасность, что последний модуль может приспособиться к некоторым “заблуждениям” отлаживаемого модуля. Поэтому приступая (в процессе интеграции программы) к отладке нового модуля приходится тестировать каждое обращение к ранее отлаженному модулю с целью обнаружения несогласованности этого обращения с телом соответствующего модуля (и не исключено, что виноват в этом ранее отлаженный модуль). Таким образом, приходится частично повторять в новых условиях тестирование ранее отлаженного модуля, при этом возникают те же трудности, что и при нисходящем тестировании.
Автономное тестирование модуля целесообразно осуществлять в четыре последовательно выполняемых шага:
Шаг 1. На основании спецификации отлаживаемого модуля подготовьте тест для каждой возможности и каждой ситуации, для каждой границы областей допустимых значений всех входных данных, для каждой области изменения данных, для каждой области недопустимых значений всех входных данных и каждого недопустимого условия.
Шаг 2. Проверьте текст модуля, чтобы убедиться, что каждое направление любого разветвления будет пройдено хотя бы на одном тесте. Добавьте недостающие тесты.
Шаг 3. Убедитесь по тексту модуля, что для каждого цикла существует тест, для которого тело цикла не выполняется, тест, для которого тело цикла выполняется один раз, и тест, для которого тело цикла выполняется максимальное число раз. Добавьте недостающие тесты.
Шаг 4. Проверьте по тексту модуля его чувствительность к отдельным особым значениям входных данных – все такие значения должны входить в тесты. Добавьте недостающие тесты.
2.3. Комплексная отладка программного средства
При комплексной отладке тестируется ПС в целом, причем тесты готовятся по каждому из документов. Тестирование этих документов производится, как правило, в порядке, обратном их разработке (исключение составляет лишь тестирование документации по применению, которая разрабатывается по внешнему описанию параллельно с разработкой текстов программ; это тестирование лучше производить после завершения тестирования внешнего описания). Тестирование при комплексной отладке представляет собой применение ПС к конкретным данным, которые в принципе могут возникнуть у пользователя (в частности, все тесты готовятся в форме, рассчитанной на пользователя), но, возможно, в моделируемой (а не в реальной) среде. Например, некоторые недоступные при комплексной отладке устройства ввода и вывода могут быть заменены их программными имитаторами.
Тестирование архитектуры ПС. Целью тестирования является поиск несоответствия между описанием архитектуры и совокупностью программ ПС. К моменту начала тестирования архитектуры ПС должна быть уже закончена автономная отладка каждой подсистемы. Ошибки реализации архитектуры могут быть связаны прежде всего с взаимодействием этих подсистем, в частности, с реализацией архитектурных функций (если они есть). Поэтому хотелось бы проверить все пути взаимодействия между подсистемами ПС. Но так как их может быть слишком много, то желательно бы протестировать хотя бы все цепочки выполнения подсистем без повторного вхождения последних. Если заданная архитектура представляет ПС в качестве малой системы из выделенных подсистем, то число таких цепочек будет вполне обозримо.
Тестирование внешних функций. Целью тестирования является поиск расхождений между функциональной спецификацией и совокупностью программ ПС. Несмотря на то, что все эти программы автономно уже отлажены, указанные расхождения могут быть, например, из-за несоответствия внутренних спецификаций программ и их модулей (на основании которых производилось автономное тестирование) внешней функциональной спецификации ПС. Как правило, тестирование внешних функций производится так же, как и тестирование модулей на первом шаге, т.е. как черного ящика.
Тестирование качества ПС. Целью тестирования является поиск нарушений требований качества, сформулированных в спецификации качества ПС. Это наиболее трудный и наименее изученный вид тестирования. Ясно лишь, что далеко не каждый примитив качества ПС может быть испытан тестированием. Завершенность ПС проверяется уже при тестировании внешних функций. На данном этапе тестирование этого примитива качества может быть продолжено если требуется получить какую-либо вероятностную оценку степени надежности ПС. Однако, методика такого тестирования еще требует своей разработки. Точность, устойчивость, защищенность, временная эффективность, в какой-то мере – эффективность по памяти, эффективность по устройствам, расширяемость и, частично, независимость от устройств могут тестироваться. Каждый из этих видов тестирования имеет свою специфику и заслуживает отдельного рассмотрения. Легкость применения ПС (критерий качества, включающий несколько примитивов качества) оценивается при тестировании документации по применению ПС.
Тестирование документации по применению ПС. Целью тестирования является поиск несогласованности документации по применению и совокупностью программ ПС, а также неудобств применения ПС. Этот этап непосредственно предшествует подключению пользователя к завершению разработки ПС (тестированию требований к ПС и аттестации ПС), поэтому весьма важно разработчикам сначала самим воспользоваться ПС так, как это будет делать пользователь. Все тесты на этом этапе готовятся исключительно на основании только документации по применению ПС. Прежде всего, должны тестироваться возможности ПС как это делалось при тестировании внешних функций, но только на основании документации по применению. Должны быть протестированы все неясные места в документации, а также все примеры, использованные в документации. Далее тестируются наиболее трудные случаи применения ПС с целью обнаружить нарушение требований относительности легкости применения ПС.
Тестирование определения требований к ПС. Целью тестирования является выяснение, в какой мере ПС не соответствует предъявленному определению требований к нему. Особенность этого вида тестирования заключается в том, что его осуществляет организация-покупатель или организация-пользователь ПС как один из путей преодоления барьера между разработчиком и пользователем. Обычно это тестирование производится с помощью контрольных задач – типовых задач, для которых известен результат решения. В тех случаях, когда разрабатываемое ПС должно придти на смену другому варианту ПС, который решает хотя бы часть задач разрабатываемого ПС, тестирование производится путем решения общих задач с помощью как старого, так и нового ПС с последующим сопоставлением полученных результатов. Иногда в качестве формы такого тестирования используют опытную эксплуатацию ПС – ограниченное применение нового ПС с анализом использования результатов в практической деятельности. По-существу, этот вид тестирования во многом перекликается с испытанием ПС при его аттестации, но выполняется до аттестации, а иногда и вместо аттестации.
2.4. Модульное тестирование
Модульное тестирование – это тестирование программы на уровне отдельно взятых модулей, функций или классов. Цель модульного тестирования состоит в выявлении локализованных в модуле ошибок в реализации алгоритмов, а также в определении степени готовности системы к переходу на следующий уровень разработки и тестирования. Модульное тестирование проводится по принципу “белого ящика”, то есть основывается на знании внутренней структуры программы, и часто включает те или иные методы анализа покрытия кода.
Модульное тестирование обычно подразумевает создание вокруг каждого модуля определенной среды, включающей заглушки для всех интерфейсов тестируемого модуля. Некоторые из них могут использоваться для подачи входных значений, другие для анализа результатов, присутствие третьих может быть продиктовано требованиями, накладываемыми компилятором и сборщиком.
На уровне модульного тестирования проще всего обнаружить дефекты, связанные с алгоритмическими ошибками и ошибками кодирования алгоритмов, типа работы с условиями и счетчиками циклов, а также с использованием локальных переменных и ресурсов. Ошибки, связанные с неверной трактовкой данных, некорректной реализацией интерфейсов, совместимостью, производительностью и т.п. обычно пропускаются на уровне модульного тестирования и выявляются на более поздних стадиях тестирования.
Именно эффективность обнаружения тех или иных типов дефектов должна определять стратегию модульного тестирования, то есть расстановку акцентов при определении набора входных значений. У организации, занимающейся разработкой программного обеспечения, как правило, имеется историческая база данных (Repository) разработок, хранящая конкретные сведения о разработке предыдущих проектов: о версиях и сборках кода (build) зафиксированных в процессе разработки продукта, о принятых решениях, допущенных просчетах, ошибках, успехах и т.п. Проведя анализ характеристик прежних проектов, подобных заказанному организации, можно предохранить новую разработку от старых ошибок, например, определив типы дефектов, поиск которых наиболее эффективен на различных этапах тестирования.
В данном случае анализируется этап модульного тестирования. Если анализ не дал нужной информации, например, в случае проектов, в которых соответствующие данные не собирались, то основным правилом становится поиск локальных дефектов, у которых код, ресурсы и информация, вовлеченные в дефект, характерны именно для данного модуля. В этом случае на модульном уровне ошибки, связанные, например, с неверным порядком или форматом параметров модуля, могут быть пропущены, поскольку они вовлекают информацию, затрагивающую другие модули (а именно, спецификацию интерфейса), в то время как ошибки в алгоритме обработки параметров довольно легко обнаруживаются.
Являясь по способу исполнения структурным тестированием или тестированием “белого ящика”, модульное тестирование характеризуется степенью, в которой тесты выполняют или покрывают логику программы (исходный текст). Тесты, связанные со структурным тестированием, строятся по следующим принципам:
· На основе анализа потока управления. В этом случае элементы, которые должны быть покрыты при прохождении тестов, определяются на основе структурных критериев тестирования С0, С1, С2. К ним относятся вершины, дуги, пути управляющего графа программы (УГП), условия, комбинации условий и т. п.
· На основе анализа потока данных, когда элементы, которые должны быть покрыты, определяются на основе потока данных, т. е. информационного графа программы.
2.5 Регрессионное тестирование
Регрессионное тестирование- цикл тестирования, который производится при внесении изменений на фазе системного тестирования или сопровождения продукта. Главная проблема регрессионного тестирования- выбор между полным и частичным перетестированием и пополнением тестовых наборов. При частичном перетестировании контролируются только те части проекта, которые связаны с измененными компонентами. На ГМП это пути, содержащие измененные узлы, и, как правило, это методы и классы, лежащие выше модифицированных по уровню, но содержащие их в своем контексте.
Пропуск огромного объема тестов, характерного для этапа системного тестирования, удается осуществить без потери качественных показателей продукта только с помощью регрессионного подхода.
Получив отчет об ошибке, программист анализирует исходный код, находит ошибку, исправляет ее и модульно или интеграционно тестирует результат.
В свою очередь тестировщик, проверяя внесенные программистом изменения, должен:
· Проверить и утвердить исправление ошибки. Для этого необходимо выполнить указанный в отчете тест, с помощью которого была найдена ошибка.
· Попробовать воспроизвести ошибку каким-нибудь другим способом.
· Протестировать последствия исправлений. Возможно, что внесенные исправления привнесли ошибку (наведенную ошибку) в код, который до этого исправно работал.
В случае наведенной ошибки исправление в одном месте привело к ошибке в другом, что демонстрирует необходимость проведения полного перетестирования. Однако повторное перетестирование требует значительных усилий и времени. Возникает задача – отобрать сокращенный набор тестов из исходного набора (может быть, пополнив его рядом дополнительных – вновь разработанных – тестов), которого, тем не менее, будет достаточно для исчерпывающей проверки функциональности в соответствии с выбранным критерием. Организация повторного тестирования в условиях сокращения ресурсов, необходимых для обеспечения заданного уровня качества продукта, обеспечивается регрессионным тестированием.
2.6. Нагрузочное тестирование
Проведение нагрузочного тестирования программных средств необходимо при принятии решений по оптимизации ПС и эффективному использованию ресурсов. Нагрузочное тестирование включает в себя анализ нагрузки на систему, разработку средств моделирования нагрузки и проведение серии испытаний.
В рамках нагрузочного тестирования реализуются проекты трех типов:
· исследование масштабируемости и оптимизация программного обеспечения;
· консалтинг в области нагрузочного тестирования;
· инфраструктурная оптимизация.
Исследование масштабируемости.
Масштабируемость — это способность системы адаптироваться к расширению предъявляемых требований и росту объемов решаемых задач, наращивать производительность при увеличении нагрузки и добавлении аппаратных ресурсов.
Исследование масштабируемости позволяет заранее оценить риски, связанные с ростом нагрузки, определить вопрос соответствия оборудования и программного обеспечения, обеспечивающего требуемую масштабируемость. Нагрузочные испытания позволяют обнаружить и устранить узкие места системы, ошибки проектирования, мешающие масштабированию.
Исследование масштабируемости дает ответы на следующие вопросы:
· Какую нагрузку выдержит используемая аппаратно-программная конфигурация?
· Как повлияет на нагрузочные характеристики изменение аппаратно-программной конфигурации?
· Какие настройки системы оптимальны с точки зрения производительности?
· Как увеличить производительность, не закупая новое оборудование?
· Когда при текущем темпе роста нагрузки будет необходима смена платформы, портирование приложения или выбор нового решения для достижения результата?
· Как быстро устранить причину сбоев системы?
Консалтинг в области нагрузочного тестирования.
Исследования масштабируемости, функциональное тестирование и оптимизацию необходимо проводить перед каждым обновлением приложения, находящегося в промышленной эксплуатации. Чтобы научиться делать это своими силами, организация может воспользоваться услугой консалтинга в специальных компаниях. Сотрудники заказчика и специалисты таких компаний совместно разработают средства нагрузочного тестирования. После обучения специалисты заказчика смогут самостоятельно регулярно тестировать систему, а также разрабатывать и обновлять средства нагрузочного тестирования силами специалистов заказчика.
Инфраструктурная оптимизация.
Инфраструктурная оптимизация проводится для выбора платформы на начальном этапе разработки системы, в случае необходимости смены платформы или при использовании системы на различных платформах.
Инфраструктурная оптимизация включает в себя проведение серий нагрузочных испытаний на различных платформах и с различным уровнем нагрузки. По результатам испытаний выбирается аппаратно-программная конфигурация с оптимальным соотношением цена/производительность для различных уровней и типов нагрузки.
Инфраструктурная оптимизация даёт ответы на следующие вопросы:
· Как изменится масштабируемость приложения при портировании на новую платформу?
· Какой сервер СУБД или сервер приложения обеспечит необходимую производительность?
· Какое решение оптимально при изменении профиля нагрузки на систему?
· Какое оборудование нужно для каждого подразделения организации, если система эксплуатируется в нескольких отделах с уровнем нагрузки от 100 до 5000 пользователей?
2.7. Структурное тестирование
При данном подходе считается, что текст программы виден (белый ящик). Тестируются блоки ветвлений, циклы и т.д.
Существует несколько типов структурного тестирования:
· покрытие операторов,
· покрытие решений,
· покрытие решений / условий,
· комбинаторное покрытие условий,
· тестирование циклов.
Тестирование “белый ящик” выполняется с целью обнаружения проблем во внутренней структуре программы. Это требует от проверяющего глубокого знания внутренней структуры и, следовательно, не может быть выполнено обычным пользователем. Общая задача такого тестирования – обеспечить проверку каждого шага по алгоритму программы. Основное преимущество всех типов стратегий тестирования “белый ящик”: при тестировании принимается во внимание структура всей программы, что облегчает обнаружение ошибок даже в том случае, когда спецификации программного обеспечения недостаточно определенные или неполные.
Тестирование по блокам заключается в проверке блока отдельно от остальной системы. Обычно блок представляет собой функцию или небольшой набор функций (библиотеки, классы), которые выполняются одним программистом. Основная отличительная характеристика блока состоит в том, что он достаточно небольшой по объему для проведения тщательной проверки, которую можно назвать исчерпывающей. Обычно тестирование “белый ящик” проводится разработчиками. Небольшой размер блоков позволяет обеспечить высокий уровень проверки кодов. Таким образом легче обнаружить и устранить ошибки на данном уровне тестирования.
Методы проектирования тестовых путей для достижения заданной степени тестированности в структурном тестировании.
Процесс построения набора тестов при структурном тестировании принято делить на три фазы:
· Конструирование УГП.
· Выбор тестовых путей.
· Генерация тестов, соответствующих тестовым путям.
Первая фаза соответствует статическому анализу программы, задача которого состоит в получении графа программы и зависящего от него и от критерия тестирования множества элементов, которые необходимо покрыть тестами.
На третьей фазе по известным путям тестирования осуществляется поиск подходящих тестов, реализующих прохождение этих путей.
Вторая фаза обеспечивает выбор тестовых путей. Выделяют три подхода к построению тестовых путей:
· Статические методы.
· Динамические методы.
· Методы реализуемых путей.
Статические методы. Самое простое и легко реализуемое решение – построение каждого пути посредством постепенного его удлинения за счет добавления дуг, пока не будет достигнута выходная вершина управляющего графа программы. Эта идея может быть усилена в так называемых адаптивных методах, которые каждый раз добавляют только один тестовый путь (входной тест), используя предыдущие пути (тесты) как руководство для выбора последующих путей в соответствии с некоторой стратегией. Чаще всего адаптивные стратегии применяются по отношению к критерию С1. Основной недостаток статических методов заключается в том, что не учитывается возможная нереализуемость построенных путей тестирования.
Динамические методы. Такие методы предполагают построение полной системы тестов, удовлетворяющих заданному критерию, путем одновременного решения задачи построения покрывающего множества путей и тестовых данных. При этом можно автоматически учитывать реализуемость или нереализуемость ранее рассмотренных путей или их частей. Основной идеей динамических методов является подсоединение к начальным реализуемым отрезкам путей дальнейших их частей так, чтобы: 1) не терять при этом реализуемости вновь полученных путей; 2) покрыть требуемые элементы структуры программы.
Методы реализуемых путей. Данная методика заключается в выделении из множества путей подмножества всех реализуемых путей. После чего покрывающее множество путей строится из полученного подмножества реализуемых путей.
Достоинство статических методов состоит в сравнительно небольшом количестве необходимых ресурсов, как при использовании, так и при разработке. Однако их реализация может содержать непредсказуемый процент брака (нереализуемых путей). Кроме того, в этих системах переход от покрывающего множества путей к полной системе тестов пользователь должен осуществить вручную, а эта работа достаточно трудоемкая. Динамические методы требуют значительно больших ресурсов как при разработке, так и при эксплуатации, однако увеличение затрат происходит, в основном, за счет разработки и эксплуатации аппарата определения реализуемости пути (символический интерпретатор, решатель неравенств). Достоинство этих методов заключается в том, что их продукция имеет некоторый качественный уровень – реализуемость путей. Методы реализуемых путей дают самый лучший результат.
Поскольку исчерпывающее структурное тестирование невозможно, необходимо выбрать такие критерии его полноты, которые допускали бы их простую проверку и облегчали бы целенаправленный подбор тестов. Наиболее слабым из критериев полноты структурного тестирования является требование хотя бы однократного выполнения каждого оператора программы. Более сильным критерием является критерий: каждая ветвь алгоритма (каждый переход) должна быть пройдена (выполнена) хотя бы один раз.
Внимательное изучение этих методов тестирования показывает, что они дополняют друг друга, то есть различные методы находят разные ошибки. Поэтому наиболее эффективные процессы разработки программного обеспечения используют некоторую комбинацию методик “черного ящика” и “белого ящика”.
Белый ящик.
Методы тестирования, которые изучают не только внешнее поведение программы, но и ее внутреннее устройство (исходные тексты). Такие методики обобщенно называют тестированием “белого ящика”. Назовем некоторых представителей этого класса методик: чтение программ, формальные просмотры программ, инспекции и т.п.). Основной трудностью подобных методов является сложность отслеживания вычислений времени выполнения.
При тестировании программы как белый ящик происходит проверка логики программы. Полным тестированием в этом случае будет такое, которое приведет к перебору всех возможных путей. Даже для средних по сложности программ числом таких путей может достигать десятков тысяч.
На уровне отдельных блоков проверяется, не произойдёт ли крах всей программы при передаче в функцию неожидаемых ею параметров. Этот вид тестирования должен проводиться специалистом, имеющим полное представление о способе реализации проверяемой функции. После такой проверки можно быть уверенным в том, что приводящих к краху системы ошибок нет и что функция будет устойчиво работать в любых условиях (т.е. выдавать предсказуемые результаты даже при вводе непредвиденных входных параметров).

Рис. 1. Тестирование “белого ящика”
Цель тестовых наборов для “белого ящика” обнаружить все скрытые дефекты путем всестороннего тестирования функции разнообразными входными параметрами. Эти наборы должны обладать следующими возможностями:
· обеспечивать максимально возможное (100%) покрытие функции: как уже говорилось, такая степень покрытия на уровне блоков возможна, поскольку создавать наборы для тестирования каждой характеристики функции вне приложения гораздо проще (стопроцентное покрытие во всех случаях невозможно, но это цель, к которой надо стремиться);
· выявлять условия краха функции.
Следует заметить, что самостоятельно создание подобных наборов, не владея технологиями их построения, невероятно тяжелое занятие. Чтобы создать эффективные тестовые наборы для “белого ящика”, необходимо сначала получить полное представление о внутренней структуре функции, написать наборы, обеспечивающие максимальное покрытие функции, и найти совокупность входов, приводящих к отказу функции. Получить спектр покрытия, необходимый для высокоэффективного тестирования “белого ящика”, возможно лишь при исследовании значительного числа путей прохода по функции. Например, в обычной программе, состоящей из 10000 операторов, имеется приблизительно сто миллионов возможных путей прохода; вручную создать тестовые наборы для проверки всех этих путей невозможно.
После создания тестовых наборов необходимо провести тестирование функции в полном объеме и проанализировать результаты с целью выявления причин ошибок, крахов и слабых мест. Необходимо иметь способ прогона всех тестовых наборов и быстрого определения, какие из них приводят к возникновению проблем. Необходимо также иметь инструмент измерения степени покрытия для оценки полноты тестирования функции и определения необходимости в дополнительных тестовых наборах.
При любых изменениях функции следует проводить регрессивное тестирование, чтобы убедиться в отсутствии новых и/или устранении предыдущих ошибок. Включение блочного регрессивного тестирования в процесс разработки позволит защититься от многих ошибок они будут обнаружены сразу же после возникновения и не смогут стать причинами распространения ошибок в приложении.
Регрессивное тестирование можно проводить двумя способами. Первый заключается в том, что разработчик или испытатель анализирует каждый тестовый набор и определяет, на работе которого из них может сказаться измененный код. Этот подход характеризуется экономией машинного времени за счет работы, проводимой человеком. Второй, более эффективный, заключается в автоматическом прогоне на компьютере всех тестовых наборов после каждого изменения текста. Данный подход гарантирует большую эффективность труда разработчика, поскольку он не должен тратить время на анализ всей совокупности тестовых наборов, для того чтобы определить, какие наборы следует прогонять, а какие нет.
2.8. Функциональное тестирование
При данном подходе считается, что текст программы не виден, и программа рассматривается как черный ящик, т.е. известны входные и выходные условия, а также общая схема работы. Программа проверятся по ее спецификациям.
Существуют несколько видов функционального тестирования:
· эквивалентные классы;
· анализ граничных значений;
· тестирование на предельных нагрузках;
· тестирование на предельных объемах;
· тестирование защиты;
· эксплуатация системы самим разработчиком (если возможно);
· опытная эксплуатация.
Тестирование “черный ящик” состоит в поиске отсутствующих или неправильно выполняемых функций с целью оценки, насколько хорошо программа отвечает требованиям. Функциональные тесты обычно подтверждают правильность данных на вводе и выходе. В этом случае пользователь – идеальный проверяющий. Иногда такое тестирование называют пользовательским. Тест функциональности состоит в том, чтобы проверить правильное выполнение отдельных функций системой. Проверяющие проводят тесты, которые, по их мнению отражают использование системы в будущем, ее функциональных возможностей.
Черный ящик.
Долгое время основным способом тестирования было тестирование методом “черного ящика” – программе подавались некоторые данные на вход и проверялись результаты, в надежде найти несоответствия. При этом как именно работает программа считается несущественным. Отметим, что даже при таком подходе необходимо иметь спецификацию программы для того, чтобы было с чем сравнивать результаты.
Этот подход до сих пор является самым распространенным в повседневной практике, но у него есть целый ряд недостатков. Во-первых, таким способом невозможно найти взаимоуничтожающихся ошибок, во-вторых, некоторые ошибки возникают достаточно редко (ошибки работы с памятью) и потому их трудно найти и воспроизвести.
Блочное тестирование можно разделить минимум на два отдельных процесса. Первый это тестирование “черного ящика”, или процесс определения функциональных проблем. На уровне отдельных блоков тестирование “черного ящика” заключается в проверке функциональных характеристик посредством определения степени соответствия параметров открытого интерфейса функции ее спецификации; подобная проверка выполняется без учета способов реализации. Результатом тестирования “черного ящика” на блочном уровне является уверенность в том, что данная функция ведет себя в полном соответствии с определением и что незначительная функциональная ошибка не приведет к лавине трудноразрешимых проблем.
Рис. 2. Тестирование “черного ящика”
Основой разработки тестовых наборов для “черного ящика” должна стать спецификация функции. В частности, для каждой записи в спецификации должен быть создан хотя бы один тестовый набор, при этом желательно, чтобы эти наборы учитывали указанные в спецификации граничные условия. Проверять того, что некоторые входные параметры приводят к ожидаемым результатам, недостаточно; необходимо определить диапазон взаимосвязей входов и выходов, который позволит сделать вывод о корректной реализации указанных функциональных характеристик, и затем создавать тестовые наборы, полностью покрывающие данный диапазон. Можно также тестировать не указанные в спецификации и ошибочные условия.
3. УРОВНИ ТЕСТИРОВАНИЯ.
ДОКУМЕНТАЦИЯ ТЕСТИРОВАНИЯ
3.1 Комбинирование уровней тестирования
В каждом конкретном проекте должны быть определены задачи, ресурсы и технологии для каждого уровня тестирования таким образом, чтобы каждый из типов дефектов, ожидаемых в системе, был «адресован», то есть в общем наборе тестов должны иметься тесты, направленные на выявление дефектов подобного типа. Табл. 1 суммирует характеристики свойств модульного, интеграционного и системного уровней тестирования. Задача, которая стоит перед тестировщиками и менеджерами, заключается в оптимальном распределении ресурсов между всеми тремя типами тестирования. Например, перенесение усилий на поиск фиксированного типа дефектов из области системного в область модульного тестирования может существенно снизить сложность и стоимость всего процесса тестирования.
Таблица 1. Характеристики модульного, интеграционного и системного тестирования

Модульное
Интеграционное
Системное
Типы дефектов
Локальные дефекты, такие как опечатки в реализации алгоритма, неверные операции, логические и математические выражения, циклы, ошибки в использовании локальных ресурсов, рекурсия и т.п.
Интерфейсные дефекты, такие как неверная трактовка параметров и их формат, неверное использование системных ресурсов и средств коммуникации, и т.п.
Отсутствующая или некорректная функциональность, неудобство использования, непредусмотренные данные и их комбинации, непредусмотренные или не поддержанные сценарии работы, ошибки совместимости, ошибки пользовательской документации, ошибки переносимости продукта на различные платформы, проблемы производительности, инсталляции и т.п.
Необходимость в системе тестирования
Да
Да
Нет (*)
Цена разработки системы тестирования
Низкая
Низкая до умеренной
Умеренная до высокой или неприемлемой
Цена процесса тестирования, то есть разработки, прогона и анализа тестов
Низкая
Низкая
Высокая
(*) прямой необходимости в системе тестирования нет, но цена процесса системного тестирования часто настолько высока, что требует использования систем автоматизации, несмотря на возможно высокую их стоимость.
3.2 Методы тестирования на соответствие стандартам, обеспечивающим переносимость прикладных программ
3.2.1 Цель проведения
Цель проведения тестирования на соответствие состоит в том, чтобы установить удовлетворяет ли проверяемая ПП техническим требованиям, описанным в соответствующем стандарте POSIX.
Ограничения, связанные с ресурсами компьютеров и затраченным временем на тестирование, делают невозможным проведение исчерпывающих проверок. Из-за этих ограничений, в процессе разработки методов тестирования уровень сложности проверяемого элемента определяет уровень тестирования, который удовлетворял бы требованиям, содержащимся в настоящем стандарте.
3.2.2. Уровни тестирования
Уровни тестирования включают:
· Исчерпывающий тест: – стремится проверить поведение всех возможных состояний тестируемого элемента, включая любые перестановки. Перебор различных опций команды и перебор всех возможных комбинаций параметров в некоторых случаях требует проведения огромного числа испытаний, что делает невозможным проведение исчерпывающего теста в течение допустимого отрезка времени.
· Полный тест: – является одним из упрощенных вариантов исчерпывающего теста, который обычно используется на практике. При выполнении полного теста стремятся проверять поведение каждого состояния элемента, но не осуществлять проверку всех перестановок. В процессе проведения полного теста набора элементов, тестирование проходит и большинство подэлементов. В этом смысле полезно рассматривать только то число тестовых утверждений, которое может быть выделено из спецификации каждого элемента прикладной программы. Если число тестируемых элементов является настолько большим, что становится невозможным проведение их полного теста, используют третий уровень тестирования.
· Идентифицирующий тест: – обычно проверяет только отличительную особенность рассматриваемого элемента. Тестирование элемента проводится при использовании минимально возможного синтаксиса команд и проверки минимального количества функций.
3.2.3 Уровни сложности элементов тестирования
Уровни сложности элементов тестирования включают:
· Простые элементы: – элементы, полностью определенные в своих описаниях, и должны проверяться с использованием полного теста. Простые элементы обычно содержат небольшое число тестовых утверждений и не зависят от других элементов, определенных в том же стандарте POSIX. Пример – Утилита cat, описанная в стандарте POSIX.1 или функция close ( ), определенная в POSIX.1.
· Элементы промежуточной сложности: – содержат большее число тестовых утверждений, чем простые элементы, и могут зависеть от других элементов, определенных в соответствующем стандарте POSIX. Проведение для таких элементов полного теста в некоторых случаях является невозможным. Пример – Утилиты grep и sed, определенные в стандарте POSIX.2.
· Сложные элементы: – к сложным элементам относятся элементы, которые могут либо поддерживать собственный язык команд (например, утилита bash), либо являются функциями элементов промежуточной сложности, либо работают с аппаратной частью. Для сложных элементов проведение полного тестирования возможно только для их составных частей. Пример – Утилиты sh и awk, определенные в POSIX.2.
3.3 Тестовое покрытие
Тестовое покрытие – набор тестов, покрывающих все линейные участки программы.
Критерии полноты тестового покрытия.
Для тестирования программного обеспечения требуется создать репрезентативный набор тестов, то есть набор, охватывающий все возможные сценарии работы системы. Для оценки репрезентативности тестовых наборов используются различные критерии полноты тестового покрытия.
Пусть – множество программных систем, – множество тестов, а – множество тестовых наборов, то есть множество всех конечных подмножеств множества . Тогда задача генерации тестов может быть сформулирована следующим образом: для заданной тестируемой системы построить тестовый набор , удовлетворяющий заданному критерию полноты тестового покрытия , то есть такой набор , для которого .
Многие критерии полноты тестового покрытия, имеющие практическое применение, строятся по следующей схеме: для тестируемой системы критерий определяет множество элементов тестового покрытия . Элементом тестового покрытия можно считать некоторый класс событий, которые могут произойти в ходе работы тестируемой программной системы. По появлению в процессе исполнения программы элементов тестового покрытия и различных их комбинаций можно судить о полноте или качестве проверки, которую выполняет данный тестовый набор. Например, элементами тестового покрытия могут быть исполняемые строки исходного кода (соответствующие событиям их исполнения); рёбра графа потока управления; пути в графе потока управления; логические выражения, встречающиеся в исходном коде и т.п. Кроме того, критерий определяет логическую функцию , которая принимает значение , если элемент тестового покрытия покрывается тестом . Тестовый набор для системы удовлетворяет критерию полноты тестового покрытия , если каждый элемент тестового покрытия из множества покрывается хотя бы одним тестом из тестового набора . Иными словами: .
Примеры часто упоминаемых критериев полноты тестового покрытия:
· каждый оператор в исходном коде выполняется хотя бы один раз;
· каждая ветвь графа потока управления выполняется хотя бы один раз;
· каждый путь графа потока управления исполнение выполняется хотя бы один раз;
· каждое логическое выражение хотя бы один раз вычисляется со значением «истина» и хотя бы один раз – со значением «ложь»;
· тестовый набор убивает всех мутантов из заданного набора.
Все критерии, приведённые в качестве примеров, соответствуют ранее изложенной схеме.
3.4 Тестовая документация
Разработка тестов основана на тех же инженерных принципах, что и разработка ПО.
Хороший проект состоит из нескольких этапов, которые постепенно превращают тесты из первичной высокоуровневой стратегии в детализированные процедуры тестирования. К этим этапам относятся: стратегия тестирования, планирование тестирования, разработка наборов тестовых данных (тест-кейсов) и разработки процедуры тестирования.
При разработке тестов следует руководствоваться спецификацией ПО. На самом верхнем уровне это означает, что тесты должны быть разработаны для верификации того, что ПО действительно реализует требования спецификации требований. На нижних уровнях тесты будут разрабатываться для верификации того, что программные компоненты реализуют все проектные решения, принятые в спецификации архитектурного дизайна и детальных проектных спецификациях. Процесс проектирования тестов, как и любой процесс проектирования, должен быть подвергнут неформальной и формальной оценке.
Простота разработки тестов существенно зависит от проекта ПО. Важно считать тестируемость ключевым (но обычно недокументированным) требованием к любой программной разработке.
3.4.1. Стратегия тестирования
Первым этапом является формулирование стратегии тестирования. Стратегия тестирования – это изложение общего подхода к тестированию, идентифицирующее, какие уровни тестирования должны быть достигнуты и какие методы, технологии и инструменты должны быть использованы. В идеале стратегия тестирования должна быть разработана для всей организации и быть применимой ко всем программным разработкам, выполняемым в организации.
Разработка стратегии тестирования, эффективно отвечающей нуждам организации, является критичной для успеха программных разработок организации. Применение стратегии тестирования к проекту разработки ПО детализировано в программе обеспечения качества проекта.
3.4.2. Планы тестирования
Следующим этапом разработки тестов, который является первым этапом в проекте разработки ПО, является разработка плана тестирования. План тестирования указывает, какие элементы должны быть протестированы, на каком уровне они должны быть протестированы, в какой очередности они должны быть протестированы, как должна быть применена стратегия тестирования при тестировании каждого элемента и описывает среду тестирования.
План тестирования может быть одним для всего проекта или на самом деле может быть иерархией планов, связанных с различными уровнями спецификации и тестирования:
· План приемочного тестирования, описывающий план приемочного тестирования ПО (в русскоязычной среде такой вид тестирования называют «приемо-сдаточными испытаниями»). Обычно он издается в виде отдельного документа, но может быть издан как единый документ вместе с планом системного тестирования.
· План системного тестирования, описывающий план интеграции и тестирования системы. Он так же обычно издается в виде отдельного документа, но может быть издан как единый документ вместе с планом приемочного тестирования.
· План тестирования программной интеграции, описывающий план интеграции протестированных программных компонент. Он может быть оформлен в виде раздела спецификации архитектурного дизайна.
· План(ы) тестирования компонент, описывающий планы тестирования отдельных программных компонент.
Назначением каждого плана тестирования является обеспечение путем тестирования верификации того, что созданное ПО удовлетворяет требованиям или проектным решениям, заданным программной спецификацией. В случае приемочного или системного тестирования речь идет о спецификации требований.
3.4.3. Разработка наборов тестовых данных (тест-кейсов)
После того, как тестовый план для какого-то уровня тестирования был составлен, следующим этапом разработки тестов является определение комплекта тест-кейсов или трасс тестирования для каждого элемента, подлежащего тестированию на данном уровне.
Количество тест-кейсов определяется для каждого подлежащего тестированию элемента для каждого уровня тестирования. Каждый тест-кейс определяет, как должна тестироваться реализация отдельного требования или проектного решения, и каковы критерии успешного прохождения теста.
Тест-кейсы могут быть документированы в тестовом плане, как раздел программной спецификации, или в отдельном документе, именуемом спецификацией тестов или описанием тестов:
· Спецификация приемочных тестов, определяющая тест-кейсы для приемочного тестирования ПО. Обычно она издается в виде отдельного документа, но может быть издана вместе с планом приемочного тестирования.
· Спецификация системных тестов, определяющая тест-кейсы для интеграции и тестирования системы. Обычно она тоже издается в виде отдельного документа, но может быть издана вместе с планом системного тестирования.
· Спецификации тестов интеграции ПО, определяющие тест-кейсы для каждого этапа интегрирования протестированных программных компонент. Они могут составлять разделы спецификации архитектурного дизайна.
· Спецификации тестирования компонент, определяющие тест-кейсы для отдельных программных компонент. Они могут составлять разделы спецификаций детализированного дизайна.
Системное и приемочное тестирование требует огромного количества отдельных тест-кейсов.
С целью того, чтобы знать, какие требования тестируется какими тест-кейсами, часто формируется указатель со ссылками между требованиями и тест-кейсами. Обычно его называют указателем ссылок верификации (Verification Cross Reference Index – VCRI) и помещают в виде приложения к тестовой спецификации. Указатели ссылок так же могут использоваться при тестировании компонент и интеграции ПО.
Для проектирования тест-кесов важно как позитивное тестирование, так и негативное тестирование. Позитивное тестирование проверяет, что ПО делает то, что должно.
Негативное тестирование проверяет, что ПО не делает того, что не должно.
Процесс разработки тест-кейсов, включая выполнение их как мысленные эксперименты, часто будет обнаруживать ошибки еще до того, как программное обеспечение было создано.
Нередко больше ошибок находят при разработке тестов, чем при выполнении тестов.
3.4.4. Тестовые процедуры
Заключительным этапом разработки тестов является реализация набора тест-кейсов в виде тестовой процедуры, определяющей точный процесс которому нужно следовать для проведения каждого тест-кейса. Это достаточно прямолинейный процесс, который можно сравнить с написанием фрагментов кода по более высокоуровневым функциональным описаниям.
Для каждого подлежащего тестированию элемента на каждом уровне тестирования тестовая процедура будет определять процесс, которому нужно следовать для проведения соответствующего тест-кейса. В тестовой процедуре не могут пропускаться шаги или делаться дополнения. Уровень детализации должен быть таким, чтобы тестовая процедура была детерминированной и повторяемой.
Тестовые процедуры всегда должны быть отделены друг от друга, так как они вполне могут иметь дело с деталями, не соответствующими программным спецификациям. Если используются AdaTEST или Cantata, то тестовые процедуры могут быть сразу написаны в виде сценариев AdaTEST или Cantata.
3.4.5. Протоколы
· Протокол о проведении тестовых испытаний составляется в обязательном порядке и должен содержать следующую информацию :
· дата;
· наименование Заказчика;
· наименование Исполнителя;
· объект тестовых испытаний;
· цель испытаний. Цель может быть сформулирована, например, следующим образом : “Определение соответствия адаптированной Исполнителем 1С:АС требованиям Заказчика”. Для повторного тестирования может быть добавлено, например, следующее : “Подтвердить исправление ошибок, выявленных при проведении тестовых испытаний, описанных Протоколом № … от ….”;
· вид тестирования;
· тип тестовых испытаний: первичное или повторное;
· методы испытаний: на контрольных примерах, моделирующий реальные данные, на реальных данных и т.д.;
· этап тестовых испытаний: промежуточные испытания или окончательные;
· тестирование функциональных возможностей. В таблице рекомендуется привести список контрольных примеров, для каждого из которых указать:
¾ технические требования (или группа требований) на проверку выполнения которого (которых) направлен пример;
¾ исходные данные примера;
¾ предполагаемые результаты/характеристики ( например для “правильно работающей 1С:АС”);
¾ фактически полученные результаты/ характеристики.
· Тестирование производительности. Формулировать наименование показателей требуется конкретно и доступно для понимания. Например:
¾ Допустимая скорость обработки операций при работе пользователей в сетевом режиме;
¾ Время работы отчета “Наименование”, и т.д.
· условия проведения тестовых испытаний. Необходимо указать последовательность тестирования, состав и структуру используемых технических средств;
· результат испытаний. Оформляется либо заключение о работоспособности ПС( например 1С:АС), либо перечень необходимых доработок;
· перечень необходимых доработок. Заполняется в случае, если ПС( например 1С:АС) признана непригодной к эксплуатации;
· контрольные суммы (размер) *.md – файла, содержащего информацию по адаптированной конфигурации и дата его последней модификации (для 1С:АС);
· подписи представителей Заказчика и Исполнителя, принимавших участие в проведении тестирования;
· в качестве приложения 1 к Протоколу- отчет (краткий или детальный) об изменениях, сделанных в типовой конфигурации. Отчет необходимо сформировать стандартными средствами конфигуратора, используя режим объединения конфигураций. Для этого рекомендуется запустить типовую конфигурацию в режиме “Конфигуратора” и запустив режим объединения конфигураций, выбрать *.md – файл адаптированной конфигурации. Не производя объединения сформировать отчет об изменениях и распечатать его (для 1С:АС).
3.4.6. Документирование результатов тестирования
Когда тесты выполняются, выходная информация каждого выполнения тестов должна записываться в файл результатов тестирования. Затем, для определения общего итога теста, эти результаты должны быть оценены по критериям, заданным в тестовой спецификации. Если используются AdaTEST или Cantata, то этот файл будет создан и результаты автоматически оценены по критериям, заданным в сценарии теста.
Каждое выполнение теста должно быть зарегистрировано в журнале тестирования. Журнал тестирования будет содержать записи о том, когда запускался каждый тест, итог выполнения каждого теста и может также содержать важные наблюдения, сделанные при выполнении теста. Зачастую журнал тестирования не ведут для нижних уровней тестирования (тестов компонент и интеграции ПО).
Отчеты о тестировании могут формироваться в различных точках в течение процесса тестирования. Отчеты о тестировании будут суммировать результаты тестирования и документировать любой анализ. Отчет о приемочном тестировании часто является договорным документом, подтверждающим приемку ПО.
4. ИНТЕГРАЦИОННОЕ ТЕСТИРОВАНИЕ
Интеграционное тестирование – это тестирование части системы, состоящей из двух и более модулей. Основная задача интеграционного тестирования – поиск дефектов, связанных с ошибками в реализации и интерпретации интерфейсного взаимодействия между модулями.
С технологической точки зрения интеграционное тестирование является количественным развитием модульного, поскольку так же, как и модульное тестирование, оперирует интерфейсами модулей и подсистем и требует создания тестового окружения, включая заглушки (Stub) на месте отсутствующих модулей. Основная разница между модульным и интеграционным тестированием состоит в целях, то есть в типах обнаруживаемых дефектов, которые, в свою очередь, определяют стратегию выбора входных данных и методов анализа. В частности, на уровне интеграционного тестирования часто применяются методы, связанные с покрытием интерфейсов, например, вызовов функций или методов, или анализ использования интерфейсных объектов, таких как глобальные ресурсы, средства коммуникаций, предоставляемых операционной системой.

Рис. 3. Пример структуры комплекса программ
На Рис. 3 приведена структура комплекса программ K, состоящего из оттестированных на этапе модульного тестирования модулей M1, M2, M11, M12, M21, M22. Задача, решаемая методом интеграционного тестирования: – тестирование межмодульных связей, реализующихся при исполнении программного обеспечения комплекса K. Интеграционное тестирование использует модель “белого ящика” на модульном уровне. Поскольку тестировщику текст программы известен с детальностью до вызова всех модулей, входящих в тестируемый комплекс, применение структурных критериев на данном этапе возможно и оправдано.
Интеграционное тестирование применяется на этапе сборки модульно оттестированных модулей в единый комплекс. Известны два метода сборки модулей:
· Монолитный, характеризующийся одновременным объединением всех модулей в тестируемый комплекс;
· Инкрементальный, характеризующийся пошаговым (помодульным) наращиванием комплекса программ с пошаговым тестированием собираемого комплекса. В инкрементальном методе выделяют две стратегии добавления модулей:
¾ “Сверху вниз” и соответствующее ему восходящее тестирование.
¾ “Снизу вверх” и соответственно нисходящее тестирование.
Особенности монолитного тестирования заключаются в следующем: для замены неразработанных к моменту тестирования модулей, кроме самого верхнего (К на Рис. 3), необходимо дополнительно разрабатывать драйверы (test driver) и/или заглушки (stub), замещающие отсутствующие на момент сеанса тестирования модули нижних уровней.
Сравнение монолитного и интегрального подхода дает следующее:
· Монолитное тестирование требует больших трудозатрат, связанных с дополнительной разработкой драйверов и заглушек и со сложностью идентификации ошибок, проявляющихся в пространстве собранного кода.
· Пошаговое тестирование связано с меньшей трудоемкостью идентификации ошибок за счет постепенного наращивания объема тестируемого кода и соответственно локализации добавленной области тестируемого кода.
· Монолитное тестирование предоставляет большие возможности распараллеливания работ особенно на начальной фазе тестирования.
Особенности нисходящего тестирования заключаются в следующем: организация среды для исполняемой очередности вызовов оттестированными модулями тестируемых модулей, постоянная разработка и использование заглушек, организация приоритетного тестирования модулей, содержащих операции обмена с окружением, или модулей, критичных для тестируемого алгоритма.
Недостатки нисходящего тестирования:
· Проблема разработки достаточно “интеллектуальных” заглушек, т.е. заглушек, способных к использованию при моделировании различных режимов работы комплекса, необходимых для тестирования;
· Сложность организации и разработки среды для реализации исполнения модулей в нужной последовательности;
· Параллельная разработка модулей верхних и нижних уровней приводит к не всегда эффективной реализации модулей из-за подстройки (специализации) еще не тестированных модулей нижних уровней к уже оттестированным модулям верхних уровней.
Особенности восходящего тестирования в организации порядка сборки и перехода к тестированию модулей, соответствующему порядку их реализации.
Недостатки восходящего тестирования:
· Запаздывание проверки концептуальных особенностей тестируемого комплекса;
· Необходимость в разработке и использовании драйверов.
Особенности интеграционного тестирования для процедурного программирования.
Процесс построения набора тестов при структурном тестировании определяется принципом, на котором основывается конструирование Графа Модели Программы (ГМП). От этого зависит множество тестовых путей и генерация тестов, соответствующих тестовым путям.
Первым подходом к разработке программного обеспечения является процедурное (модульное) программирование. Традиционное процедурное программирование предполагает написание исходного кода в императивном (повелительном) стиле, предписывающем определенную последовательность выполнения команд, а также описание программного проекта с помощью функциональной декомпозиции. Такие языки, как Pascal и C, являются императивными. В них порядок исходных строк кода определяет порядок передачи управления, включая последовательное исполнение, выбор условий и повторное исполнение участков программы. Каждый модуль имеет несколько точек входа (при строгом написании кода – одну) и несколько точек выхода (при строгом написании кода – одну). Сложные программные проекты имеют модульно-иерархическое построение, и тестирование модулей является начальным шагом процесса тестирования ПО. Построение графовой модели модуля является тривиальной задачей, а тестирование практически всегда проводится по критерию покрытия ветвей C1, т.е. каждая дуга и каждая вершина графа модуля должны содержаться, по крайней мере, в одном из путей тестирования.
Таким образом, M(P,C1) = E Nij , где Е – множество дуг, а Nij – входные вершины ГМП.
Сложность тестирования модуля по критерию С1 выражается уточненной формулой для оценки топологической сложности МакКейба:
V(P,C1) = q + kin, где q – число бинарных выборов для условий ветвления,
а kin – число входов графа.
Для интеграционного тестирования наиболее существенным является рассмотрение модели программы, построенной с использованием диаграмм потоков управления. Контролируются также связи через данные, подготавливаемые и используемые другими группами программ при взаимодействии с тестируемой группой. Каждая переменная межмодульного интерфейса проверяется на тождественность описаний во взаимодействующих модулях, а также на соответствие исходным программным спецификациям. Состав и структура информационных связей реализованной группы модулей проверяются на соответствие спецификации требований этой группы. Все реализованные связи должны быть установлены, упорядочены и обобщены.
При сборке модулей в единый программный комплекс появляется два варианта построения графовой модели проекта:
· Плоская или иерархическая модель проекта.
· Граф вызовов.
Если программа P состоит из p модулей, то при интеграции модулей в комплекс фактически получается громоздкая плоская или более простая – иерархическая – модель программного проекта. В качестве критерия тестирования на интеграционном уровне обычно используется критерий покрытия ветвей C1.
Сумма сложностей модульного дизайна для всех модулей по критерию С1 или сумма их аналогов для других критериев тестирования, исключая значения модулей самого нижнего уровня, дает сложность интеграционного тестирования для процедурного программирования.
Особенности интеграционного тестирования для объектно-ориентированного программирования.
Программный проект, написанный в соответствии с объектно-ориентированным подходом, будет иметь ГМП, существенно отличающийся от ГМП традиционной “процедурной” программы. Сама разработка проекта строится по другому принципу – от определения классов, используемых в программе, построения дерева классов к реализации кода проекта. При правильном использовании классов, точно отражающих прикладную область приложения, этот метод дает более короткие, понятные и легко контролируемые программы.
Объектно-ориентированное программное обеспечение является событийно управляемым. Передача управления внутри программы осуществляется не только путем явного указания последовательности обращений одних функций программы к другим, но и путем генерации сообщений различным объектам, разбора сообщений соответствующим обработчиком и передача их объектам, для которых данные сообщения предназначены. Рассмотренная ГМП в данном случае становится неприменимой. Эта модель, как минимум, требует адаптации к требованиям, вводимым объектно-ориентированным подходом к написанию программного обеспечения. При этом происходит переход от модели, описывающей структуру программы, к модели, описывающей поведение программы, что для тестирования можно классифицировать как положительное свойство данного перехода. Отрицательным аспектом совершаемого перехода для применения рассмотренных ранее моделей является потеря заданных в явном виде связей между модулями программы.
Перед тем как приступить к описанию графовой модели объектно-ориентированной программы, остановимся отдельно на одном существенном аспекте разработки программного обеспечения на языке объектно-ориентированного программирования (ООП), например, C++ или С#. Разработка программного обеспечения высокого качества для MS Windows или любой другой операционной системы, использующей стандарт “look and feel”, с применением только вновь созданных классов практически невозможна. Программист должен будет затратить массу времени на решение стандартных задач по созданию пользовательского интерфейса. Чтобы избежать работы над давно решенными вопросами, во всех современных компиляторах предусмотрены специальные библиотеки классов. Такие библиотеки включают в себя практически весь программный интерфейс операционной системы и позволяют задействовать при программировании средства более высокого уровня, чем просто вызовы функций. Базовые конструкции и классы могут быть переиспользованы при разработке нового программного проекта. За счет этого значительно сокращается время разработки приложений. В качестве примера подобной системы можно привести библиотеку Microsoft Foundation Class (MFC) для компилятора MS Visual C++.
Работа по тестированию приложения не должна включать в себя проверку работоспособности элементов библиотек, ставших фактически промышленным стандартом для разработки программного обеспечения, а только проверку кода, написанного непосредственно разработчиком программного проекта. Тестирование объектно-ориентированной программы должно включать те же уровни, что и тестирование процедурной программы – модульное, интеграционное и системное. Внутри класса отдельно взятые методы имеют императивный характер исполнения. Все языки ООП возвращают контроль вызывающему объекту, когда сообщение обработано. Поэтому каждый метод (функция – член класса) должен пройти традиционное модульное тестирование по выбранному критерию C (как правило, С1). В соответствии с введенными выше обозначениями, назовем метод Modi, а сложность тестирования – V(Modi,C). Все результаты, полученные в тестирования модулей, безусловно, подходят для тестирования методов классов. Каждый класс должен быть рассмотрен и как субъект интеграционного тестирования. Интеграция для всех методов класса проводится с использованием инкрементальной стратегии снизу вверх. При этом мы можем переиспользовать тесты для классов-родителей тестируемого класса, что следует из принципа наследования – от базовых классов, не имеющих родителей, к самым верхним уровням классов.
Графовая модель класса, как и объектно-ориентированной программы, на интеграционном уровне в качестве узлов использует методы. Дуги данной ГМП (вызовы методов) могут быть образованы двумя способами:
· Прямым вызовом одного метода из кода другого, в случае, если вызываемый метод виден (не закрыт для доступа средствами языка программирования) из класса, содержащего вызывающий метод, присвоим такой конструкции название Р-путь (P-path, Procedure path, процедурный путь).
· Обработкой сообщения, когда явного вызова метода нет, но в результате работы “вызывающего” метода порождается сообщение, которое должно быть обработано “вызываемым” методом.
Для второго случая “вызываемый” метод может породить другое сообщение, что приводит к возникновению цепочки исполнения последовательности методов, связанных сообщениями. Подобная цепочка носит название ММ-путь (MM-path, Metod/Message path, путь метод/сообщение). ММ-путь заканчивается, когда достигается метод, который при отработке не вырабатывает новых сообщений (т. е. вырабатывает “сообщение покоя”).
5. СИСТЕМНОЕ ТЕСТИРОВАНИЕ
Системный тест представляет собой более полную версию теста на проверку внешней функции, но в максимально приближенных к “боевым” условиям и среде. При системном тестировании техническая платформа должна быть как можно ближе к фактическим условиям эксплуатации, включая такие факторы, как комплектация оборудования, а также объем и сложность базы данных. В ходе воспроизведения будущих условий эксплуатации системы можно точнее протестировать более сложные черты системы (характеристики, безопасность и безотказность). Однако воспроизводить среду пользователя для системного теста слишком дорого, к тому же может не хватить времени на проведение испытания.
Системное тестирование обычно включает в себя тестирование характеристик, которое выявляет время ответа на запрос, использование памяти, оборудования и время выполнения операции. Тестирование в стрессовой ситуации подводит систему к некоторым пределам для оценки ее возможностей и способности справляться с ошибками. Тесты надежности оценивают реакцию системы на ввод данных, проводят подсчет отказов за определенный период времени с целью оценки или подтверждения степени надежности.
Системное тестирование качественно отличается от интеграционного и модульного уровней. Системное тестирование рассматривает тестируемую систему в целом и оперирует на уровне пользовательских интерфейсов, в отличие от последних фаз интеграционного тестирования, которое оперирует на уровне интерфейсов модулей. Различны и цели этих уровней тестирования. На уровне системы часто сложно и малоэффективно анализировать прохождение тестовых траекторий внутри программы или отслеживать правильность работы конкретных функций. Основная задача системного тестирования в выявлении дефектов, связанных с работой системы в целом, таких как неверное использование ресурсов системы, непредусмотренные комбинации данных пользовательского уровня, несовместимость с окружением, непредусмотренные сценарии использования, отсутствующая или неверная функциональность, неудобство в применении и тому подобное.
Системное тестирование производится над проектом в целом с помощью метода «черного ящика». Структура программы не имеет никакого значения, для проверки доступны только входы и выходы, видимые пользователю. Тестированию подлежат коды и пользовательская документация.
· Категории тестов системного тестирования:
· Полнота решения функциональных задач.
· Стрессовое тестирование – на предельных объемах нагрузки входного потока.
· Корректность использования ресурсов (утечка памяти, возврат ресурсов).
· Оценка производительности.
· Эффективность защиты от искажения данных и некорректных действий.
· Проверка инсталляции и конфигурации на разных платформах.
· Корректность документации.
Поскольку системное тестирование проводится на пользовательских интерфейсах, создается иллюзия того, что построение специальной системы автоматизации тестирования не всегда необходимо. Однако объемы данных на этом уровне таковы, что обычно более эффективным подходом является полная или частичная автоматизация тестирования, что приводит к созданию тестовой системы гораздо более сложной, чем система тестирования, применяемая на уровне тестирования модулей или их комбинаций.
6. МЕТОДЫ ТЕСТИРОВАНИЯ
6.1. Инспекция кода
Во время инспекции исходных текстов ПП происходит непосредственный поиск уязвимостей ПП путём анализа исходных текстов ПП.
Для облегчения труда экспертов, проводящих экспертизу исходных текстов ПП, разработчику следует дать базовое описание кода: его организация (иерархия) по модулям и проектам, описание классов, функций, оказать помощь в компиляции и запуске отладки. Возможность самостоятельной компиляции исходных текстов ПП позволяет команде инспекторов воспользоваться всеми доступными возможностями среды разработки, которую использует разработчик (функции intellisense, которые имеются практически во всех современных интегрированных средах разработки IDE) – для навигации по коду, контекстной справки о стандартных функциях и классах (назначение входных параметров, возвращаемые значения, генерируемые исключения (exceptions) и т.д.).
Предусловие для инспекции: обеспечение общего качества кода. Экспертам, проводящим экспертизу, будет значительно легче разобраться в коде, если весь код подчиняется некоторому единому стандарту оформления, в коде обосновано выбираются названия для классов, методов, констант и переменных, что позволяет быстрее определить назначение того или иного участка кода, в коде имеются достаточные комментарии и т.д.
Основные задачи на этом этапе:
· поиск проблемных мест в коде (поиск потенциальных уязвимостей и ошибок программирования). Рекомендуется применение автоматизированных средств, способствующих обнаружению типовых ошибок программирования (такие инструменты, как FlawFinder, ITS4, RATS для языков С/С++, FxCop для языков платформы dotNet и др.);
· исследование проблемного места: определение типа и категории ошибки, локализация её, разработка предложений по устранению. Ранжирование обнаруженных проблемных мест, чтобы помочь тем самым команде разработчиков разработать стратегию по их устранению;
· документирование обнаруженных ошибок.
6.2. Разбиение на эквивалентные части
Этот метод делит область входящих состояний системы на классы данных, которые используются в отдельных тестах. Разбиение на эквивалентные части стремится создавать такие тесты, которые открывали бы новые типы ошибок, тем самым, уменьшая общее число тестов. Это основывается на оценке классов эквивалентности для входящих состояний системы. Класс эквивалентности представляет собой набор правильных и неправильных входящих условий.
Классы эквивалентности могут определяться согласно следующим условиям:
1. Если входящее состояние определяется областью данных, то одно правильное и два неправильных класса эквивалентности должны быть определены.
2. Если входящее состояние определяется специфическим значением, то одно правильное и два неправильных класса эквивалентности должны быть определены.
3. Если входящее состояние определяется членами определенного набора данных, то один правильный и один неправильный класс эквивалентности должны быть определены.
4. Если входящее состояние определяется булевским значением, то один правильный и один неправильный класс эквивалентности должны быть определены.
6.3 Анализ граничных величин
Этот метод приводит к получению тестов, которые используют граничные значения. Он дополняет метод разбиения на эквивалентные части, т.к. использует тесты на границах классов. Причем анализ граничных величин проводится не только для входящих значений, но и для выходящих значений.
Имеются следующие правила использования метода анализа граничных величин:
1. Для входящих значений названных a и b, тесты должны включить значения а и b, значения меньше и больше а и b соответственно.
2. Если входящее состояние определяет многими значениями, то тест должен быть разработан так, чтобы использовать минимальное и максимальное количество таких значений и величины, лежащие выше и ниже границ данных.
3. Использование принципов 1 и 2 для выходящих значений.
4. Если внутренняя структура данных имеет заданные границы, то тест должен разрабатываться, чтобы использовать структуру данных и их границы.
Вырожденный тест. Этот тест затрагивает работу отлаживаемой программы в самой минимальной степени. Обычно тест служит для проверки правильности выполнения самых внешних функций программы, например обращения к ней и выхода из нее.
Тест граничных значений, или “стрессовый тест” (High-Low Bias Checking, Twin Check). Тест проверяет работу программы для граничных значений параметров, определяющих вычислительный процесс. Часто для граничных значений параметра работа программы носит особый характер, который тем самым требует и особого контроля.
Если в качестве примера рассмотреть тестирование подпрограммы сортировки, то нужно исследовать следующие ситуации:
· сортируемый массив пуст;
· сортируемый массив содержит только один элемент;
· все элементы в сортируемом массиве одинаковы;
· массив уже отсортирован.
Л.Питер приводит следующий поучительный пример. Компьютер одной компании по страхованию автомобилей выслал проживающему в Сент-Луисе клиенту счет на сумму 0.00 долларов. Когда же компьютер направил ему “последнее уведомление” с угрозой расторгнуть договор, этот человек обратился за помощью к своему финансовому агенту. Тот пришел к выводу, что лучший способ уладить дело – отправить компьютеру чек на 0.00 долларов. Это было сделано, и в ответ пришло подтверждение с благодарностью и заверением, что договор остается в силе!
Проверка в экстремальных условиях – аналогия метода “граничных испытаний”. Граничные испытания зачастую предоставляют наилучшие возможности для радикального сокращения объема тестов при высокой достоверности результатов. Данный способ проверки основывается на следующей гипотезе: если программа правильно работает в граничных условиях, то она с большой вероятностью будет нормально работать в любой другой области нормальных значений входных данных. Успех здесь во многом определяется тем, насколько удачно выбраны сами граничные условия. Это зависит от того, что тестируется. Поэтому рекомендации могут быть лишь самого общего плана и то только для того, чтобы проиллюстрировать суть дела. Обычно в качестве граничных условий есть смысл использовать:
· для цифровых данных очень большие, очень маленькие и нулевые значения;
· для нецифровых данных (символьных кодов) нулевые коды и некоторые типичные символы;
· для размерностей массивов нулевую (минимальную) размерность и размерность большую, чем объявлено в декларациях.
Для правильного назначения граничных значений нужно хорошо представлять работу алгоритма в программе. При этом нужно учесть, что выходные данные программы также желательно проверить в экстремальных условиях, для этого с помощью подбора входных данных нужно создать соответствующую ситуацию.
В результате проверки в экстремальных условиях подтверждается (или нет) тот факт, что поля данных для всех промежуточных данных имеют размеры, достаточные для реализации функций во всем диапазоне входных данных.
Проверка в исключительных ситуациях. Этот вид тестирования проводится с использованием входных данных, которые лежат за пределами допустимой для нормальной ситуации области. Он позволяет ответить на вопрос: “Что произойдет, если исходные данные выйдут за допустимые границы?” В принципе, программа может реагировать на исключительные ситуации тремя способами:
¾ неправильные данные воспринимаются как правильные; это наихудший случай, так как правдоподобный, но неверный результат будет преподнесен как правильный;
¾ программа осуществляет непредсказуемые, но явно необычные по внешним признакам действия (зависает, перестает реагировать на запросы и т.п.);
¾ программа сама анализирует и отвергает неверные данные, делая об этом соответствующие сообщения; это наилучший и идеальный случай, к которому следует стремиться, для чего в самой программе должны быть предусмотрены фильтры всех входных данных и средства реагирования на исключительные ситуации.
Для проверки в исключительных ситуациях полезно в качестве входных данных использовать что-либо необычное, например, вместо текстового файла подставить двоичный и наоборот, вместо требуемого внешнего устройства подключить другое (при условии согласования электрических входов), понажимать на клавиши случайным образом и т.п. Хороший эффект дает пробная эксплуатация не очень искушенным пользователем. Типичная картина – недоуменный новичок перед экраном зависшей программы, знакома всем. Начинающие пользователи часто умудряются нажимать такие сочетания клавиш, которые искушенному пользователю не придут и в голову. Никогда не стоит пренебрегать такой возможностью. Она ничего не стоит, но крайне эффективна.
6.4 Многократная разработка
Более важным является то, что работа над сложной программой состоит в многократном прохождении цикла разработки, так как в процессе тестирования могут быть обнаружены такие ошибки, для исправления которых придется вернуться не только к кодированию или алгоритмизации, но и к проектированию, а в особых случаях – пересмотреть и постановку задачи.
7. КЛАССИФИКАЦИЯ ОШИБОК
Ошибки можно объединить в следующие группы:
· ошибки обращения к данным – использование переменных с неустановленными значениями, ошибки индексации, несоответствие структур данных;
· ошибки описания данных – отсутствие явного описания или неполное описание данных, отсутствие или неправильное присвоение начальных значений, несогласованность инициализации переменной с ее описанием;
· ошибки вычислений – наличие в последовательных вычислениях данных недопустимых типов, несогласованность масштабов, приводящая к переполнению или потере точности, возможность деления на нуль;
· ошибки при сравнениях – использование при операциях сравнения величин несовместимых типов, искажение смысла операций отношения (>, =, · ошибки в передачах управления – ошибки организации циклов, приводящие к возможности зацикливания или неправильного выполнения цикла, наличие неполного числа выходов в операторах-переключателях;
· ошибки программного интерфейса – несоответствие количества, типов или размерности фактических и формальных параметров подпрограмм при их вызове, несоответствие описаний переменных требованиям на выходе модуля, несогласованность описаний глобальных переменных и их интерпретации операторами программы (модуль – это замкнутая программа, которую можно вызвать из любого другого модуля в программе и можно отдельно компилировать). Напомним, что программный интерфейс определяет совокупность допустимых процедур или операций и их параметров, список общих переменных, областей памяти или других объектов;
· ошибки ввода-вывода – неполное или неправильное описание атрибутов файлов или оператора обращения к файлу, несогласованность размера записи и выделенной памяти, неполный контроль и регистрация операций с файлами;
· помехозащита – отсутствие контроля входных данных, отсутствие сохранения исходных данных и возможности повторного запуска модуля при сбоях;
· никогда не считайте, что Вы точно знаете причину ошибочного выполнения программы; очень часто в этом виновна ошибка, встретившаяся гораздо раньше (иногда ее называют отложенной ошибкой);
· и, наконец, ошибки могут быть также следствием неверной работы оборудования – это так называемые аппаратные ошибки. Если в регистр памяти компьютера на одном из этапов работы программы занесено число 12, а при чтении из этого же регистра оно прочиталось как 11, то и дальнейшие результаты, разумеется, будут неверными. Возможен случай, когда из-за такой ошибки результат вовсе не будет получен (процесс решения задачи аварийно прекратится).
Разработаны надежные методы борьбы с аппаратными ошибками и их последствиями – повторные вычисления с последующим сравнением результатов, хранение нескольких экземпляров данных с целью их защиты от искажения и т.д. Поэтому среди встречающихся на практике случаев выдачи компьютерами неверных результатов или невыдачи их вообще доля ошибок, порожденных аппаратными средствами, составляет ничтожный процент.
Так, согласно одному из определений компьютер – это вычислительная машина с надежностью военной аппаратуры и ценой изделия бытовой электроники.
Приведенная классификация полезна тем, что для каждой из перечисленных групп ошибок и для каждого типа ошибки в группе можно выделить операторы каждого конкретного языка программирования, потенциально допускающие данный тип ошибок.
ЗАКЛЮЧЕНИЕ
Рассмотренные выше методы предотвращения и обнаружения ошибок, а также технологии отладки могут значительно повысить качество программного обеспечения прикладных программ и уменьшить затрачиваемые на проведение отладки материальные и временные ресурсы. Вышеупомянутые методы без особого труда могут быть использованы в разработке самых разных проектов программного обеспечения, причем накопленный опыт полностью сохраняет свою ценность и при реализации различных проектов и целевых технологий. Кроме того, они позволяют гарантировать простоту сопровождения, модификации и портации созданных программ в устройства новых типов. И говоря коротко, рассматриваемые методы дают возможность не только совершенствовать существующие встроенные приложения и процессы разработки, но и гарантировать, что с распространением новых встраиваемых устройств у вас уже будет накоплен опыт, необходимый для разработки высокоэффективных приложений для этих технологий причем вовремя и в соответствии с выделенным бюджетом.
БИБЛИОГРАФИЧЕСКИЙ СПИСОК
Г. Майерс. Надежность программного обеспечения. – М.: Мир, 1980. Д. Ван Тассел. Стиль, разработка, эффективность, отладка и испытание программ. – М.: Мир, 1985. Дж. Хьюз, Дж. Мичтом. Структурный подход к программированию. – М.: Мир, 1980. Дж. Фокс. Программное обеспечение и его разработка. – М.: Мир, 1985. М. Зелковиц, А. Шоу, Дж. Гэннон. Принципы разработки программного обеспечения. – М.: Мир, 1982. Ю.М. Безбородов. Индивидуальная отладка программ. – М.: Наука, 1982. В.В. Липаев. Тестирование программ. – М.: Радио и связь, 1986. Е.А. Жоголев. Введение в технологию программирования (конспект лекций). – М.: “ДИАЛОГ-МГУ”, 1994. Э. Дейкстра. Заметки по структурному программированию. //У. Дал, Э. Дейкстра, К. Хоор. Структурное программирование. – М.: Мир, 1975. Майерс Г. Надежность программного обеспечения. – М.: Мир, 1980. Майерс Г. Искусство тестирования программ. – М.: Финансы и статистика, 1982. Громов Г.Р. Профессиональные приложения персональных ЭВМ // Микропроцессорные средства и системы, 1985, №3. Котляров В.П. Основы тестирования программного обеспечения. Интернет-университет информационных технологий – INTUIT.ru, 2006 г. Смагин В.А., Солдатенко В.С., Кузнецов В.В. Моделирование и обеспечение надёжности программных средств АСУ. – СПб.: ВИКУ им. А.Ф. Можайского, 1999. Аджиев В. Мифы о безопасности ПО: уроки знаменитых катастроф. – Изд. “Открытые системы”, 1999. – № 3.