1. Модели
жизненного цикла программных средств
2. Основные
процессы жизненного цикла программного средства
3.
Вспомогательные процессы жизненного цикла программного средства
4. Качество
программного обеспечения
5. Методы
обеспечения надежности программных средств.
6. Тестирование
программных средств. Комплексное тестирование
Модель
жизненного цикла: структура, состоящая из процессов, работ и задач, включающих в
себя разработку, эксплуатацию и сопровождение программного продукта,
охватывающая жизнь системы от установления требований к ней до прекращения ее
использования.
К настоящему времени
наибольшее распространение получили следующие основные модели ЖЦ:
• каскадная модель (70-80-е годы 20 века);
• спиральная модель (80-90-е годы 20 века).
В изначально существовавших однородных ИС каждое приложение представляло
собой единое целое. Для разработки такого типа приложений применялся каскадный
способ. Его основной характеристикой является разбиение всей разработки на этапы,
причем переход с одного этапа на следующий происходит
только после того, как будет полностью завершена работа на текущем (рис. 2.19).
Каждый этап завершается выпуском полного комплекта документации, достаточной
для того, чтобы разработка могла быть продолжена другой командой
разработчиков.
Рис.
2.19. Каскадная схема разработки программного средства
Положительные стороны
применения каскадного подхода заключаются в следующем:
• на каждом этапе формируется законченный набор
проектной документации, отвечающий критериям полноты и согласованности;
• выполняемые в логичной последовательности
этапы работ позволяют планировать сроки завершения всех работ и соответствующие
затраты.
Каскадный подход
хорошо зарекомендовал себя при построении ИС, для которых в самом начале
разработки можно достаточно точно и полно сформулировать все требования, с тем чтобы предоставить разработчикам свободу реализовать их
как можно лучше с технической точки зрения. В эту категорию попадают сложные
расчетные системы, системы реального времени и другие подобные задачи. Однако
в процессе использования этого подхода обнаружился ряд его недостатков, вызванных прежде всего тем, что реальный процесс создания ПС
никогда полностью не укладывался в такую жесткую схему. В процессе создания ПС
постоянно возникала потребность в возврате к предыдущим этапам и уточнении или
пересмотре ранее принятых решений. В результате реальный процесс создания ПС
принимал следующий вид (рис. 2.20).
Рис.
2.20. Схема реального процесса разработки ПС по каскадной схеме
Основным недостатком
каскадного подхода является существенное запаздывание с получением
результатов. Согласование результатов с пользователями производится только в
точках, планируемых после завершения каждого этапа работ, требования к ИС «заморожены»
в виде технического задания на все время ее создания. Таким образом,
пользователи могут внести свои замечания только после того, как работа над
системой будет полностью завершена. В случае неточного изложения требований
или их изменения в течение длительного периода создания ПС пользователи получают
систему, не удовлетворяющую их потребностям. Модели (как функциональные, так и
информационные) автоматизируемого объекта могут устареть одновременно с их
утверждением.
Для преодоления
перечисленных проблем была предложена спиральная модель ЖЦ (рис. 2.21),
делающая упор на начальные папы ЖЦ: анализ и проектирование. На этих этапах
реализуемость технических решений проверяется путем создания прототипов.
Каждый виток спирали соответствует созданию фрагмента или версии ПС, на нем
уточняются цели и характеристики проекта, определяется его
качество и планируются работы следующего витка спирали. Таким образом,
углубляются и последовательно конкретизируются детали проекта, и в результате
выбирается обоснованный вариант, который доводится до реализации.
Рис.
2.21. Схема спиральной модели жизненного цикла
Разработка итерациями
отражает объективно существующий спиральный цикл создания системы. Неполное
завершение работ на каждом этапе позволяет переходить на следующий этап, не
дожидаясь полного завершения работы на текущем. При
итеративном способе разработки недостающую работу можно будет выполнить на
следующей итерации. Главная же задача — как можно быстрее показать
пользователям системы работоспособный продукт, тем самым, активизируя процесс
уточнения и дополнения требований.
Основная проблема
спирального цикла — определение момента перехода на следующий этап. Для ее
решения необходимо ввести временные ограничения на каждый из этапов жизненного цикла.
Переход осуществляется в соответствии с планом, даже если не вся
запланированная работа закончена. План составляется на основе статистических
данных, полученных в предыдущих проектах, и личного опыта разработчиков [45].
Процеcc приобретения (acquisition process) состоит из действий
и задач заказчика,
приобретающего программное средство (рис. 2.2).
Инициирование
приобретения включает следующие задачи:
·
определение заказчиком своих потребностей в приобретении,
разработке или усовершенствовании системы, программных продуктов или услуг;
·
анализ требований к
системе;
·
принятие решения относительно приобретения, разработки или
усовершенствования существующего ПС;
·
проверку наличия необходимой документации, гарантии, сертификатов,
лицензий и поддержки в случае приобретения программного продукта;
·
подготовку и утверждение плана приобретения, включающего
требования к системе, тип договора, ответственность сторон и т. д.
Заявочные
предложения должны содержать: требования к системе; перечень программных
продуктов; условия и соглашения;
технические
ограничения (например, среда функционирования системы).
Заявочные предложения
направляются выбранному поставщику (или нескольким поставщикам в случае
проведения тенде-). Поставщик — это организация, которая заключает
договор с заказчиком на поставку системы, ПС или программной услуги на условиях,
оговоренных в договоре.
Подготовка
и корректировка договора включают следующие задачи:
• определение заказчиком процедуры выбора
поставщика, включающей критерии оценки предложений возможных поставщиков;
• ныбор конкретного
поставщика на основе анализа предложений,
• подготовку и заключение договора с
поставщиком;
• внесение изменений (при необходимости) в
договор в процессе его выполнения.
Рис. 2.2. Схема процесса приобретения программного средства
Надзор
за деятельностью поставщика осуществляется в соответствии
с действиями, предусмотренными в процессах совместной оценки и аудита.
В процессе приемки
подготавливаются и выполняются необходимые тесты. Завершение работ по
договору осуществляется в случае удовлетворения всех условий приемки.
Процесс
поставки (supply process) охватывает действия и задачи,
выполняемые поставщиком, который снабжает заказчика программным продуктом или
услугой (рис. 2.3).
Рис.
2.3. Схема процесса поставки
Инициирование
поставки заключается в рассмотрении поставщиком заявочных предложений и
принятии решения о согласии с выставленными требованиями и условиями или
предложение своих. Планирование включает следующие задачи: принятие
решения поставщиком относительно выполнения работ своими силами или с
привлечением субподрядчика; разработку поставщиком плана
управления проектом, содержащего организационную структуру проекта,
разграничение ответственности, технические требования к среде разработки и
ресурсам, управление субподрядчиками и др. Процесс разработки (development process) предусматривает
Действия и задачи, выполняемые разработчиком, и охватывает работы по созданию
ПС и его компонентов в соответствии с заданными требованиями, включая
оформление проектной и эксплуатационной документации; подготовку
материалов, необходимых для проверки работоспособности и соответствующего
качества программных продуктов, материалов, необходимых для организации
обучения персонала, и т. д. (рис. 2.4).
Рис. 2.4. Схема процесса
разработки
Подготовительная
работа начинается с выбора модели ЖЦ ПС, соответствующей масштабу, значимости
и сложности проекта. Действия и задачи процесса разработки должны соответствовать
выбранной модели. Разработчик должен выбрать, адаптировать к условиям проекта
и использовать согласованные с заказчиком стандарты, методы и средства
разработки, а также составить план выполнения работ.
Анализ
требований к системе подразумевает определение ее функциональных возможностей,
пользовательских требований, требований к надежности и безопасности,
требований к внешним интерфейсам и т. д. Требования к системе оцениваются
исходя из критериев реализуемости и возможности проверки при тестировании.
Проектирование
архитектуры системы на высоком уровне заключается в определении компонентов ее
оборудования, ПС и операций, выполняемых эксплуатирующим систему персоналом.
Архитектура системы должна соответствовать требованиям, предъявляемым к
системе, а также принятым проектным стандартам и методам.
Анализ
требований к ПС предполагает определение следующих характеристик для каждого
компонента ПС:
• функциональных возможностей, включая
характеристики производительности и среды функционирования компонента;
• внешних интерфейсов;
• спецификаций надежности и безопасности;
• эргономических требований;
• требований к используемым данным;
• требований к установке и приемке;
• требований к пользовательской документации;
• требований к эксплуатации и сопровождению.
Требования к ПС
оцениваются исходя из критериев соответствия имя требованиям к системе,
реализуемости и возможности проверки при тестировании.
Проектирование
архитектуры ПС включает следующие задачи (для каждого компонента ПС):
·
трансформацию требований к ПС в архитектуру, определяющую на
высоком уровне структуру ПС и состав его компонентов;
·
разработку и документирование программных интерфейсов ПС
и баз данных;
·
разработку предварительной версии пользовательской докумен-
тации;
·
разработку и документирование предварительных требований к тестам
и плана интеграции ПС.
Архитектура
компонентов ПС должна соответствовать требованиям, предъявляемым к ним, а также
принятым проектным стандартам и методам.
Детальное
проектирование ПС включает следующие задачи:
·
писание компонентов ПС и интерфейсов между ними на более низком
уровне, достаточном для их последующего самостоятельного кодирования и
тестирования;
·
разработку и документирование детального
проекта базы данных;
·
обновление (при необходимости) пользовательской докумен-тации;
·
разработку и документирование требований к тестам и плана
тестирования компонентов ПС;
·
обновление плана интеграции ПС.
Кодирование
и тестирование ПС охватывают следующие задачи:
• разработку (кодирование) и документирование
каждого компонента ПС и базы данных, а также совокупности тестовых процедур и
данных для их тестирования;
• тестирование каждого компонента ПС и базы
данных на соответствие предъявляемым к ним требованиям. Результаты тестирования
компонентов должны быть документированы;
• обновление (при необходимости)
пользовательской документации;
• обновление плана интеграции ПС.
Интеграция
ПС предусматривает сборку разработанных компонентов ПС в
соответствии с планом интеграции и тестирование агрегированных компонентов.
Для каждого из агрегированных компонентов разрабатываются наборы тестов и
тестовые процедуры, предназначенные для проверки каждого из квалификационных
требований при последующем квалификационном тестировании. Квалификационное
требование — это набор критериев или условий, которые необходимо
выполнить, чтобы квалифицировать программный продукт как соответствующий своим
спецификациям и готовый к использованию в условиях эксплуатации.
Квалификационное
тестирование ПС проводится разработчиком в присутствии заказчика (по возможности)
для демонстрации того, что ПС удовлетворяет своим спецификациям и готово к
использованию в условиях эксплуатации. Квалификационное тестирование
выполняется для каждого компонента ПС по всем разделам требований при широком
варьировании тестов. При этом также проверяются полнота технической и
пользовательской документации и ее адекватность самим
компонентам ПС.
Интеграция
системы заключается в сборке всех ее компонентов, включая ПС и
оборудование. После интеграции система, в свою очередь, подвергается квалификационному
тестированию на соответствие совокупности требований к ней. При этом также
производятся оформление и проверка полного комплекта документации на систему.
Установка
ПС осуществляется разработчиком в соответствии с планом в той среде и
на том оборудовании, которые предусмотрены договором.
В процессе установки проверяется работоспособность ПС и баз данных. Если устанавливаемое
ПС заменяет существующую систему, разработчик должен обеспечить их параллельное
функционирование в соответствии с договором.
Приемка
ПС предусматривает оценку результатов квалификационного тестирования
ПС и системы и документирование
результатов оценки,
которые проводятся заказчиком с помощью разработчика. Разработчик выполняет
окончательную передачу ПС заказчику в соответствии с договором, обеспечивая при
этом необходимое обучение и поддержку.
Процесс
эксплуатации (operation process) охватывает действия и
задачи оператора — организации, эксплуатирующей систему (рис. 2.5).
Рис. 2.5. Схема процесса
эксплуатации
Подготовительная
работа включает проведение оператором следующих задач:
· планирование
действий и работ, выполняемых в процессе эксплуатации, и установку
эксплуатационных стандартов;
· определение
процедур локализации и разрешения проблем, возникающих в процессе эксплуатации.
Эксплуатационное
тестирование осуществляется для каждой очередной редакции программного продукта,
после чего она передается в эксплуатацию.
Эксплуатация
системы выполняется в предназначенной для этого среде в соответствии с
пользовательской документацией.
Поддержка
пользователей заключается в оказании помощи и консультации при обнаружении ошибок
в процессе эксплуатации ПС.
Процесс
сопровождения (maintenance process) предусматривает (действия
и задачи, выполняемые сопровождающей организацией (службой сопровождения).
Данный процесс активизируется при изменениях (модификациях) программного продукта
и соответствующей документации, вызванных возникшими проблема ми или
потребностями в модернизации либо адаптации ПС. соответствии со стандартом IEEE-90 под сопровождением пони
мается внесение изменений в ПС в целях исправления
ошибок повышения производительности или адаптации к изменившимся условиям
работы или требованиям.
Изменения, вносимые в существующее ПС, не должны нарушать
его целостность. Процесс сопровождения включает перенос ПС в другую среду
(миграцию) и заканчивается снятием ПС эксплуатации (рис. 2.6).
Рис.
2.6. Схема процесса сопровождения
Подготовительная
работа службы сопровождения включает следующие задачи:
• планирование действий и работ, выполняемых в
процессе сопровождения;
• определение процедур локализации и разрешения
проблем, возникающих в процессе сопровождения.
Анализ
проблем и запросов на модификацию ПС, выполняемый службой
сопровождения, включает следующие задачи:
• анализ сообщения о возникшей проблеме или
запроса на модификацию ПС относительно его влияния на организацию, существующую
систему и интерфейсы с другими системами. При этом определяются следующие
характеристики возможной модификации: тип (корректирующая, улучшающая,
профилактическая или адаптирующая к новой среде); масштаб (размеры модификации,
стоимость и время ее реализации); критичность (воздействие на
производительность, надежность или безопасность);
• оценку целесообразности проведения
модификации и возможных вариантов ее проведения;
• утверждение выбранного варианта модификации.
Модификация
ПС предусматривает определение компонентов ПС их версий и документации, подлежащих
модификации, и вне-
сение
необходимых изменений в соответствии с правилами про-цесса наработки. Подготовленные
изменения тестируются и про-веряются
по критериям, определенным в документации. При под-верждении
корректности изменений в программах проводится корректировка документации.
Проверка
и приемка заключаются в проверке целостности мо-дифицированной системы и утверждении внесенных
изменений. При переносе ПС в другую среду используются имеющиеся или
разрабатываются новые средства переноса, затем выполняется конвертирование
программ и данных в новую среду. С целью облегчить переход предусматривается параллельная эксплуатаци ПС в
старой и новой среде в течение некоторого периода,
когда проводится
необходимое обучение пользователей работе в новой среде.
Снятитие ПС
с эксплуатации осуществляется по решению за-казчика
при участии эксплуатирующей организации, службы со-провожения
и пользователей. При этом программные продук-ты соответствующая документация подлежат
архивированию в соответствии с договором. Аналогично переносу ПС в другую среду
с целью облегчить переход к новой системе предусматрива-ется
параллельная эксплуатация старого и нового ПС в течение некоторогоо
периода, когда выполняется необходимое обучение пользователей работе с новой
системой.
Процесс
документирования (documentation process) предусматривает
формализованное описание информации, созданной в течении ЖЦ ПС. Данный процесс состоит из набора действий, с помощью которых
планируют, проектируют, разрабатывают, выпускают, редактируют, распространяют и
сопровождают документы, необходимые
для всех заинтересованных лиц, таких, как руководители, технические специалисты
и пользователи системы (рис. 2.7).э
Рис.
2.7. Схема процесса документирования
Процесс
управления конфигурацией (configuration management process) предполагает применение
административных и технических процедур на всем протяжении ЖЦ ПС для
определения состояния компонентов ПС в системе, управления модификациями ПС,
описания и подготовки отчетов о состоянии компонентов ПС и запросов на
модификацию, обеспечения полноты, совместимости и корректности компонентов ПС,
управления хранением и поставкой ПС. Согласно стандарту IEEE-90 под конфигурацией ПС понимается
совокупность его функциональных и физических характеристик, установленных в
технической документации и реализованных в ПС.
Управление конфигурацией
позволяет организовать, систематически учитывать и контролировать внесение
изменений в ПС на всех стадиях ЖЦ (рис. 2.8).
Рис. 2.8. Схема процесса
управления конфигурацией
Подготовительная
работа заключается в планировании управления конфигурацией.
Идентификация
конфигурации устанавливает правила, с помощью которых можно однозначно
идентифицировать и различать компоненты ПС и их версии. Кроме того, каждому компоненту
и его версиям соответствует однозначно обозначаемый комплект документации. В
результате создается база для однозначного выбора и манипулирования версиями
компонентов ПС, использующая ограниченную и упорядоченную систему символов,
идентифицирующих различные версии ПС.
Контроль
конфигурации предназначен для систематической оценки предполагаемых
модификаций ПС и координированной их реализации с учетом эффективности каждой
модификации и затрат на выполнение. Он обеспечивает контроль состояния и
развития компонентов ПС и их версий, а также адекватность реально изменяющихся
компонентов их комплектной документации.
Учет
состояния конфигурации представляет собой регистрацию
состояния компонентов ПС, подготовку отчетов обо всех реализованных и
отвергнутых модификациях версий компонентов ПС. Совокупность отчетов
обеспечивает однозначное отражение текущего состояния системы и ее
компонентов, а также ведение истории модификаций.
Оценка
конфигурации заключается в оценке функциональной полноты компонентов ПС, а
также соответствия их физического состояния текущему техническому описанию.
Управление
выпуском и поставка охватывают изготовление эталонных копий программ и документации,
их хранение и поставку пользователям в соответствии с порядком, принятым в
организации.
Процесс
обеспечения качества (quality assurance process) обеспечивает
соответствующие гарантии того, что ПС и процессы его ЖЦ соответствуют заданным
требованиям и утвержденным планам. Под качеством ПС понимается
совокупность свойств, которые характеризуют способность ПС удовлетворять заданным
требованиям (рис. 2.9).
Рис.
2.9. Схема процесса обеспечения качества
Для получения
достоверных оценок создаваемого ПС процесс обеспечения его качества должен
происходить независимо от субъектов, непосредственно связанных с разработкой
ПС. При этом могут использоваться результаты других вспомогательных процессов,
таких, как верификация, аттестация, совместная оценка, аудит и разрешение
проблем.
Подготовительная
работа заключается в координации с другими вспомогательными процессами и
планировании самого процесса обеспечения качества с учетом используемых
стандартов, методов, процедур и средств.
Обеспечение
качества продукта подразумевает гарантирование полного соответствия программных
продуктов и документации на них требованиям заказчика, предусмотренным в
договоре.
Обеспечение
качества процесса предполагает гарантирование соответствия процессов ЖЦ ПС, методов
разработки, среды разработки и квалификации персонала условиям договора,
установленным стандартам и процедурам.
Обеспечение
прочих показателей качества системы осуществляется в
соответствии с условиями договора и стандартом качества ISO 9001.
Процесс
верификации (verification process) состоит в определении
того, что программные продукты, являющиеся результатами некоторого действия,
полностью удовлетворяют требованиям или условиям, обусловленным
предшествующими действиями (верификация в «узком» смысле означает
формальное доказательство правильности ПС). Для повышения эффективности верификация
должна как можно раньше интегрироваться с использующими ее процессами (такими,
как поставка, разработка, эксплуатация или сопровождение). Данный процесс
может включать анализ, оценку и тестирование (рис. 2.10).
Рис.
2.10. Схема процесса верификации
Верификация может
проводиться с различными степенями независимости. Степень
независимости может варьироваться от выполнения верификации самим исполнителем
или другим специалистом данной организации до ее выполнения специалистом
(ругой организации с различными вариациями. Если процесс верификации
осуществляется организацией, не зависящей от поставщика, разработчика,
оператора или службы сопровождения, ю он называется процессом
независимой верификации.
В процессе
верификации проверяются следующие условия:
• непротиворечивость требований к системе и
степень учета потребностей пользователей;
• возможности поставщика выполнить заданные
требования;
• соответствие выбранных процессов ЖЦ ПС
условиям договора;
• адекватность стандартов, процедур и среды
разработки процессам ЖЦ ПС;
• соответствие проектных спецификаций ПС
заданным требованиям;
• корректность описания в проектных
спецификациях входных и выходных данных, последовательности событий, интерфейсов,
логики и т.д.;
• соответствие кода проектным спецификациям и
требованиям;
• тестируемость и корректность кода, его
соответствие принятым стандартам кодирования;
• корректность интеграции компонентов ПС в
систему;
• адекватность, полнота и непротиворечивость
документации.
Процесс
аттестации (validation process) предусматривает определение
полноты соответствия заданных требований и созданной системы или программного
продукта их конкретному функциональному назначению. Под аттестацией обычно
понимаются подтверждение и оценка достоверности проведенного тестирования ПС.
Аттестация должна гарантировать полное соответствие ПС спецификациям,
требованиям и документации, а также возможность его безопасного и надежного
применения пользователем. Аттестацию рекомендуется выполнять путем тестирования
во всех возможных ситуациях и использовать при этом независимых специалистов.
Аттестация может проводиться на начальных стадиях ЖЦ ПС или как часть работы по
приемке ПС (рис. 2.11).
Рис. 2.11. Схема
процесса аттестации
Аттестация, так же как
и верификация, может осуществляться с различными степенями независимости. Если
процесс аттестации выполняется организацией, не зависящей от поставщика,
разработчика, оператора или службы сопровождения, то он называется процессом
независимой аттестации.
Процесс
совместной оценки (joint review process) предназначен для оценки
состояния работ по проекту и ПС, создаваемому при выполнении данных работ
(действий). Он сосредоточен в основном на контроле планирования и управления
ресурсами, персоналом, аппаратурой и инструментальными средствами проекта
(рис. 2.12).
Рис. 2.12. Схема
процесса оценки
Оценка применяется
как на уровне управления проектом, так и на уровне технической реализации
проекта и проводится в течение всего срока действия договора. Данный процесс
может выполняться двумя любыми сторонами, участвующими в договоре, при этом
одна сторона проверяет другую.
Процесс
аудита (audit process) представляет собой
определение соответствия требованиям, планам и условиям договора.
Аудит может
выполняться двумя любыми сторонами, участвующими в договоре, когда одна
сторона проверяет другую.
Аудит
— это ревизия (проверка), проводимая компетентным органом (лицом)
в целях обеспечения независимой оценки степени соответствия ПС или процессов
установленным требованиям. Аудит служит для установления соответствия реальных
работ и отчетов требованиям, планам и контракту. Аудиторы (ревизоры) не должны
иметь прямой зависимости от разработчиков ПС. Они определяют состояние работ,
использование ресурсов, соответствие документации спецификациям и стандартам,
корректность тестирования (рис. 2.13).
Рис. 2.13. Схема
процесса аудита
Процесс
разрешения проблем (problem resolution process) предусматривает анализ и
решение проблем (включая обнаруженные несоответствия) независимо от их
происхождения или источника, которые обнаружены в ходе разработки,
эксплуатации, сопровождения или других процессов. Каждая обнаруженная проблема
должна быть идентифицирована, описана, проанализирована и разрешена (рис.
2.14).
Рис.
2.14. Схема процесса разрешения проблем
Понятие хорошей
программы весьма относительно и включает в себя ряд
качественных характеристик (которые не всегда могут быть оценены
количественно). К основным из них принято относить:
- надежность;
- эффективность;
- модифицируемость;
- мобильность;
- понятность
(программа должна быть составлена так, чтобы ее легко было читать и
использовать, так как программа пишется для людей);
- учет человеческого фактора.
Эффективность программы - это минимальные
затраты оперативной и внешней памяти, времени работы процессора, необходимые
для выполнения программы.
Мобильность - программа является завершенной
и машинонезависимой.
Модифицируемость - это возможность
расширения вычислительных возможностей отдельных модулей.
Учет человеческого фактора - программа не
требует излишних затрат времени и усилий пользователя по поддержанию ее
функционирования.
Остановимся подробнее на характеристике
надежности программного обеспечения.
Надежность программы определяется
как свойство выполнять заданные функции в заданных условиях работы и на
заданной вычислительной машине. Отказ программы обусловлен ее несоответствием
поставленным задачам и может выражаться в виде следующих сбоев в работе:
o выдача
неверных результатов;
o отсутствие
результатов;
o уменьшение
производительности;
o порча
данных пользователя.
Отказ программного продукта может быть
обусловлен двумя причинами:
1) нарушение
разработчиком программы спецификации - технических требований к программе;
2) спецификация
неточная или не полная.
Поскольку не всегда возможно составить
точную спецификацию, предлагается классифицировать программы по степени
точности спецификации следующим образом:
1) программы, функции которых полностью
определяются спецификацией;
2) программы,
функции которых корректируются сопоставлением вычислительных и измеренных
результатов (сюда относятся моделирующие программы , реализующие
математическую модель физического объекта )
3) программы,
действующие в постоянно изменяющейся среде (эта среда состоит из других
программ, данных пользователей, реальных установок и схем и т.п.; к ним
относятся операционные системы, управляющие программы и т.д.).
В связи с этой классификацией введено
понятие корректности программы - ее
соответствие спецификации. Но поскольку спецификация не всегда и не полностью
соответствует фактическим требованиям к программе, возможны случаи, когда
некорректная программа работает надежно или, наоборот, корректная программа -
ненадежно.
Характерной особенностью ошибок,
вызывающих отказы программ, является их скрытность - проявление лишь в редких
комбинациях исходных данных.
Для обеспечения и повышения надежности
программ используются следующие мероприятия:
1) Усовершенствование
технологии программирования. Для реализации этого мероприятия применяют два
методологических подхода. Во-первых, необходимо широко использовать принципы
модульного программирования в сочетании с практикой минимизации числа
соединений между модулями. Во-вторых, необходимо искать и применять способы так
называемого оборонительного
программирования, направленного на уменьшение вероятности ошибок в программах.
Такое программирование опирается на две основные концепции: защиту и
устойчивость к ошибкам.
а) Под защитой в рассматриваемом аспекте
понимают ограничение неправильного использования программных объектов. Другими
словами, выдвигается требование проектировать и программировать таким образом,
чтобы не только гарантировать ожидаемое использование программы в строгом
соответствии со спецификациями, но и сделать невозможным ее неправильное
использование. Например, при проектировании системы, в которой взаимодействуют много модулей, мы можем потребовать, чтобы
какие-то взаимодействия между ними разрешались лишь в определенных ситуациях.
Таким образом, вызов модулем А модуля В может быть
разрешен всегда, в определенных ситуациях или же никогда, несмотря на то, что
модули А и В находятся в таких отношениях, что вызов возможен.
Простейший метод защитного
программирования заключается в использовании специальных ловушек ошибок,
рассчитанных на ошибки типа неправильного использования модулей. Разработчика
программы не интересует, что будет делать пользователь после того, как получит
сообщение о неправильном использовании модуля, но при этом он обязан
спроектировать модуль так, чтобы ошибки пользователя не вызывали необратимых
изменений в модуле. Таким образом, пользователь, пойманный на неправильном
употреблении модуля, принимает корректирующие действия и снова вызывает модуль,
не оставляя никаких следов ошибочных вызовов. Иначе говоря, речь идет о таком
программировании, когда программный продукт очень трудно или невозможно использовать
за пределами области действия его спецификации.
б) Основное допущение программирования,
устойчивого к ошибкам, заключается в том, что как бы хорошо ни была
спроектирована и реализована программа, в ней обязательно будет содержаться
несколько остаточных ошибок. А раз так, то модули программы, которые могут дать
сбой, должны иметь “резервный запас”. С этой целью модуль проектируется в виде
блоков восстановления. Каждый блок восстановления содержит пропускной тест и
один или несколько вариантов реализации. Основной вариант инициируется при
вызове блока восстановления, и когда его выполнение завершается, происходит
проверка значения пропускного теста. Если он дает «истину», то считается, что
выполнение блока восстановления успешно завершено. Если же тест дает «ложь», то
инициируется другой вариант, за которым следует определение значения
пропускного теста и т. д. , и так до успешного
выполнения блока восстановления. Если же ни один вариант не прошел пропускного
теста, то блок восстановления рассматривается как ошибочный и начинается
исполнение другого варианта вызываемого модуля.
2) Выбор
алгоритмов не чувствительных к различного
рода нарушениям вычислительного процесса (использование алгоритмической
избыточности).
Мерой чувствительности алгоритма может
являться погрешность вычислений. Результаты вычислений искажаются следующими
погрешностями:
а) исходных данных, трансформированными в ходе вычислений;
б) округления;
в) погрешностями
метода;
г) погрешностями,
обусловленными отказами, сбоями и ошибками в программе;
3)
Резервирование программ (введение структурной избыточности).
Резервирование программ основано на
осознании того факта, что достижение необходимого уровня надежности программы
путем использования технологических мер обычно ограничено. Для этого
подготавливаются две или более версий программы для решения одной и той же
задачи.
При дуальном программировании (если
разрабатываются две версии программы) в случае обнаружения расхождения в
результатах, необходимо определить по дополнительным критериям, какой результат
правильный и отбросить другой результат. При N-версионном
программировании подготавливаются N версий программы
и правильный результат определяется по мажоритарному признаку, т.е. выбирается
тот результат, который наблюдается в большинстве вариантов программы.
Рассмотренные способы резервирования
требуют в 2 или N раз больше времени для вычислений и увеличение объема труда
программистов во столько же раз.
В связи с этим представляет интерес модифицированное
дуальное программирование, при котором наряду с достаточно точной, но
сложной основной программой используется менее точная, но простая резервная
программа. Если при одинаковых исходных данных результаты работы программ
отличаются на величину большую, чем допустимая погрешность, делается
предположение, что отказала основная программа, как менее надежная, и в
качестве правильного результата принимается результат работы резервной
программы.
4) Тестирование программ. Тестирование - проверка
работы программы по результатам ее выполнения на специально подобранных наборах
исходных данных - тестах.
Программа может быть
тестирована либо полностью, либо выборочно в отдельных точках пространства
исходных данных.
При выборочном
тестировании надежность программы не может быть полностью гарантирована.
Если тесты предлагаются программистом, то они могут охватить только те части
программы, с которыми программист наиболее знаком. Поэтому многие скрытые
ошибки могут оставаться не обнаруженными.
Полное
тестирование при всех возможных входных наборах программы или даже
тестирование всех путей в структуре программы нереально, так как число тестов
будет недопустимо большим. Поэтому предлагается использовать структурное выборное тестирование, основанное на разделении
пространства исходных данных на классы, причем каждый класс позволяет
подтвердить определенные свойства или работоспособность определенных элементов
структуры программы.
В современных автоматизированных технологиях создания и развития сложных ПС с позиции обеспечения их необходимой и
заданной надежности можно выделить методы и средства, позволяющие:
• создавать программные модули и
функциональные компоненты высокого, гарантированного качества;
• предотвращать дефекты проектирования за
счет эффективных технологий и средств автоматизации
обеспечения всего жизненного цикла комплексов программ и баз данных;
• обнаруживать и устранять различные дефекты
и ошибки проектирования, разработки и сопровождения программ путем
систематического тестирования на всех этапах жизненного цикла ПС;
• удостоверять достигнутое качество и
надежность функционирования ПС в процессе их испытаний и
сертификации перед передачей в регулярную эксплуатацию;
• оперативно выявлять последствия
дефектов программ и данных и восстанавливать нормальное, надежное
функционирование комплексов программ.
Комплексное,
скоординированное применение этих методов и средств в процессе
создания, развития и применения ПС позволяет исключать некоторые виды угроз
или значительно ослаблять их влияние. Тем самым уровень достигаемой надежности
ПС становится предсказуемым и управляемым, непосредственно зависящим от
ресурсов, выделяемых на его достижение, а главное от качества и эффективности
технологии, используемой на всех этапах жизненного цикла ПС.
Все принципы и методы
обеспечения надежности в соответствии с их целью можно разбить на четыре
группы: предупреждение ошибок, обнаружение ошибок, исправление ошибок и
обеспечение устойчивости к ошибкам. К первой группе относятся
принципы и методы, позволяющие минимизировать или вообще исключить ошибки.
Методы второй группы сосредоточивают внимание на функциях самого программного
обеспечения, помогающих выявлять ошибки. К третьей группе относятся функции программного
обеспечения, предназначенные для исправления ошибок или их последствий.
Устойчивость к ошибкам (четвертая группа) — это мера способности системы
программного обеспечения продолжать функционирование при наличии ошибок.
Предупреждение ошибок
К этой группе
относятся принципы и методы, цель которых — не допустить появления ошибок в
готовой программе. Большинство методов концентрируется на отдельных процессах
перевода и направлено на предупреждение ошибок в этих процессах. Их можно
разбить на следующие категории:
1) методы,
позволяющие справиться со сложностью, свести ее к минимуму, так как это —
главная причина ошибок перевода;
2) методы достижения большей точности при
переводе;
3) методы улучшения обмена информацией;
4) методы немедленного обнаружения и устранения
ошибок. Эти методы направлены на обнаружение ошибок на каждом шаге перевода, не
откладывая до тестирования программы после ее написания.
Должно быть очевидно, что предупреждение ошибок — оптимальный путь
к достижению надежности программного обеспечения.
Лучший способ
обеспечить надежность — прежде всего не допустить возникновения ошибок.
Гарантировать отсутствие ошибок, однако, невозможно никогда. Другие три группы
методов опираются на предположение, что ошибки все-таки будут.
Обнаружение ошибок
Если предполагать,
что в программном обеспечении какие-то ошибки все же будут, то лучшая (после
предупреждения ошибок) стратегия — включить средства обнаружения ошибок в само
программное обеспечение.
Большинство методов
направлено по возможности на незамедлительное обнаружение сбоев. Немедленное
обнаружение имеет два преимущества: можно минимизировать влияние ошибки и
последующие затруднения для человека, которому придется извлекать информацию о
ней, находить ее и исправлять.
Меры по обнаружению
ошибок можно разбить на две подгруппы: пассивные попытки обнаружить
симптомы ошибки в процессе «обычной» работы программного обеспечения и активные
попытки программной системы периодически обследовать свое состояние в
поисках признаков ошибок.
Пассивное
обнаружение. Меры по обнаружению ошибок могут быть приняты на нескольких
структурных уровнях программной системы. Здесь мы будем рассматривать уровень
подсистем, или компонентов, т.е. нас будут интересовать меры по обнаружению
симптомов ошибок, предпринимаемые при переходе от одного компонента к
другому, а также внутри компонента. Все это, конечно, применимо также к
отдельным модулям внутри компонента.
Разрабатывая эти
меры, мы будем опираться на следующее.
1. Взаимное недоверие. Каждый из компонентов
должен предполагать, что все другие содержат ошибки. Когда он получает
какие-нибудь данные от другого компонента или из источника вне системы, он
должен предполагать, что данные могут быть неправильными, и пытаться найти в
них ошибки.
2.
Немедленное обнаружение. Ошибки необходимо обнаружить как можно
раньше. Это не только ограничивает наносимый ими ущерб, но и значительно
упрощает задачу отладки.
3. Избыточность. Все средства обнаружения
ошибок основаны на некоторой форме избыточности (явной или неявной).
Когда разрабатываются
меры по обнаружению ошибок, важно принять согласованную стратегию для всей
системы. Действия, предпринимаемые после обнаружения ошибки в программном
обеспечении, должны быть единообразными для всех компонентов системы. Это
ставит вопрос о том, какие именно действия следует предпринять, когда ошибка
обнаружена. Наилучшее решение — немедленно завершить выполнение программы или
(в случае операционной системы) перевести центральный процессор в состояние
ожидания. С точки зрения предоставления человеку, отлаживающему программу,
например системному программисту, самых благоприятных условий для диагностики
ошибок немедленное завершение представляется наилучшей стратегией. Конечно, во
многих системах подобная стратегия бывает нецелесообразной (например, может
оказаться, что приостанавливать работу системы нельзя). В таком случае
используется метод регистрации ошибок. Описание симптомов ошибки и
«моментальный снимок» состояния системы сохраняются во внешнем файле, после
чего система может продолжать работу. Этот файл позднее будет изучен
обслуживающим персоналом.
Всегда, когда это
возможно, лучше приостановить выполнение программы, чем регистрировать ошибки
(либо обеспечить как дополнительную возможность работу системы в любом из этих
режимов). Различие между этими методами проиллюстрируем на способах выявления
причин возникающего иногда скрежета вашего автомобиля. Если автомеханик
находится на заднем сиденье, то он может обследовать состояние машины в тот момент,
когда скрежет возникает. Если вы выбираете метод регистрации ошибок, задача
диагностики станет сложнее.
Активное
обнаружение ошибок. Не все ошибки можно выявить пассивными методами, поскольку эти
методы обнаруживают ошибку лишь тогда, когда ее симптомы подвергаются соответствующей
проверке. Можно делать и дополнительные проверки, если спроектировать
специальные программные средства для активного поиска признаков ошибок в
системе. Такие средства называются средствами активного обнаружения ошибок.
Активные средства
обнаружения ошибок обычно объединяются в диагностический монитор: параллельный
процесс, который периодически анализирует состояние системы с целью обнаружить
ошибку. Большие программные системы, управляющие ресурсами, часто содержат
ошибки, приводящие к потере ресурсов на длительное время. Например, управление
памятью операционной системы сдает блоки памяти «в аренду» программам
пользователей и другим частям операционной системы. Ошибка в этих самых «других
частях» системы может иногда вести к неправильной работе блока управления
памятью, занимающегося возвратом сданной ранее в аренду памяти, что вызывает
медленное вырождение системы.
Диагностический
монитор можно реализовать как периодически выполняемую задачу (например, она
планируется на каждый час) либо как задачу с низким приоритетом, которая планируется
для выполнения в то время, когда система переходит в состояние ожидания. Как и
прежде, выполняемые монитором конкретные проверки зависят от специфики системы,
но некоторые идеи будут понятны из примеров. Монитор может обследовать
основную память, чтобы обнаружить блоки памяти, не выделенные ни одной из
выполняемых задач и не включенные в системный список свободной памяти. Он
может проверять также необычные ситуации: например, процесс не планировался для
выполнения в течение некоторого разумного интервала времени. Монитор может
осуществлять поиск «затерявшихся» внутри системы сообщений или операций
ввода-вывода, которые необычно долгое время остаются незавершенными, участков
памяти на диске, которые не помечены как выделенные и не включены в список
свободной памяти, а также различного рода странностей в файлах данных.
Иногда желательно,
чтобы в чрезвычайных обстоятельствах монитор выполнял диагностические тесты
системы. Он может вызывать определенные системные функции, сравнивая их
результат с заранее определенным и проверяя, насколько
разумно время выполнения. Монитор может также периодически предъявлять системе
«пустые» или «легкие» задания, чтобы убедиться, что система функционирует хотя
бы самым примитивным образом.
Исправление ошибок
Следующий шаг —
методы исправления ошибок; после того как ошибка обнаружена, либо она сама,
либо ее последствия должны быть исправлены программным обеспечением.
Исправление ошибок самой системой — плодотворный метод проектирования надежных
систем аппаратного обеспечения. Некоторые устройства способны обнаружить
неисправные компоненты и перейти к использованию идентичных запасных.
Аналогичные методы неприменимы к программному обеспечению вследствие глубоких
внутренних различий между сбоями аппаратуры и ошибками в программах. Если
некоторый программный модуль содержит ошибку, идентичные «запасные» модули
также будут содержать ту же ошибку.
Другой подход к
исправлению связан с попытками восстано-вить
разрушения, вызванные ошибками, например искажения записей в базе данных или
управляющих таблицах системы. Польза от методов борьбы с искажениями
ограничена, поскольку предполагается, что разработчик заранее предугадает
несколько возможных типов искажений и предусмотрит программно реализуемые
функции для их устранения. Это похоже на парадокс, поскольку, если знать
заранее, какие ошибки возникнут, можно было бы принять дополнительные меры по
их предупреждению. Если методы ликвидации последствий сбоев не могут быть обобщены
для работы со многими типами искажений, лучше всего направлять силы и средства
на предупреждение ошибок. Вместо того, чтобы,
разрабатывая операционную систему, оснащать ее средствами обнаружения и
восстановления цепочки искаженных таблиц или управляющих блоков, следовало бы
лучше спроектировать систему так, чтобы только один модуль имел доступ к этой
цепочке, а затем настойчиво пытаться убедиться в правильности этого модуля.
Устойчивость к ошибкам
Методы этой группы
ставят своей целью обеспечить функционирование программной системы при наличии
в ней ошибок. Они разбиваются на три подгруппы: динамическая избыточность,
методы отступления и методы изоляции ошибок.
1. Истоки концепции динамической
избыточности лежат в проектировании аппаратного обеспечения. Один из
подходов к динамической избыточности — метод голосования. Данные обрабатываются
независимо несколькими идентичными устройствами, и результаты сравниваются.
Если большинство устройств выработало одинаковый результат, этот результат и
считается правильным. И опять, вследствие особой природы ошибок в программном
обеспечении ошибка, имеющаяся в копии программного модуля, будет также
присутствовать во всех других его копиях, поэтому идея голосования здесь,
видимо, неприемлема. Предлагаемый иногда подход к решению этой проблемы состоит
в том, чтобы иметь несколько неидентичных копий модуля. Это значит, что все
копии выполняют одну и ту же функцию, но либо реализуют различные алгоритмы,
либо созданы разными разработчиками. Этот подход бесперспективен по следующим
причинам. Часто трудно получить существенно разные
версии модуля, выполняющие одинаковые функции. Кроме того, возникает необходимость
в дополнительном программном обеспечении для организации выполнения этих версий
параллельно или последовательно и сравнения результатов. Это дополнительное
программное обеспечение повышает уровень сложности системы, что, конечно,
противоречит основной идее предупреждения ошибок — стремиться в первую очередь
минимизировать сложность.
Второй подход к
динамической избыточности — выполнять эти запасные копии только тогда, когда
результаты, полученные с помощью основной копии, признаны неправильными. Если
это происходит, система автоматически вызывает запасную копию. Если и ее
результаты неправильны, вызывается другая запасная копия и т. д.
2. Вторая подгруппа методов обеспечения
устойчивости к ошибкам называется методами отступления или сокращенного
обслуживания. Эти методы приемлемы обычно лишь тогда, когда для системы
программного обеспечения существенно важно корректно
закончить работу. Например, если ошибка оказывается в системе, управляющей
технологическими процессами, и в результате эта система выходит из строя, то
может быть загружен и выполнен особый фрагмент программы, призванный подстраховать
систему и обеспечить безаварийное завершение всех управляемых системой
процессов. Аналогичные средства часто необходимы в операционных системах. Если
операционная система обнаруживает, что вот-вот выйдет из строя, она может загрузить
аварийный фрагмент, ответственный за оповещение пользователей у терминалов о
предстоящем сбое и за сохранение всех критических для системы данных.
3. Последняя подгруппа — методы изоляции
ошибок. Основная их идея — не дать последствиям ошибки выйти за пределы
как можно меньшей части системы программного обеспечения, так чтобы, если
ошибка возникнет, то не вся система оказалась неработоспособной; отключаются лишь отдельные функции в системе либо некоторые
ее пользователи. Например, во многих операционных системах изолируются ошибки
отдельных пользователей, так что сбой влияет лишь на некоторое подмножество
пользователей, а система в целом продолжает функционировать. В телефонных
переключательных системах для восстановления после ошибки, чтобы не рисковать
выходом из строя всей системы, просто разрывают телефонную связь. Другие методы
изоляции ошибок связаны с защитой каждой из программ в системе от ошибок
других программ. Ошибка в прикладной программе, выполняемой под управлением
операционной системы, должна оказывать влияние только на эту программу. Она не
должна сказываться на операционной системе или других программах,
функционирующих в этой системе.
В большой
вычислительной системе изоляция программ является ключевым фактором,
гарантирующим, что отказы в программе одного пользователя не приведут к
отказам в программах других пользователей или к полному выводу системы из
строя. Основные правила изоляции ошибок перечислены ниже. Хотя в формулировке
многих из них употребляются слова «операционная система», они применимы к
любой программе (будь то операционная система, монитор телеобработки или
подсистема управления файлами), которая занята обслуживанием других программ.
1. Прикладная программа не должна иметь
возможности непосредственно ссылаться на другую прикладную программу или
данные в другой программе и изменять их.
2. Прикладная программа не должна иметь
возможности непосредственно ссылаться на программы или данные операционной
системы и изменять их. Связь между двумя программами (или программой и
операционной системой) может быть разрешена только при условии использования
четко определенных сопряжений и только в случае, когда обе программы дают
согласие на эту связь.
3. Прикладные программы и их данные должны быть
защищены от операционной системы до такой степени, чтобы ошибки в
операционной системе не могли привести к случайному изменению прикладных
программ или их данных.
4. Операционная система должна защищать все
прикладные программы и данные от случайного их изменения операторами системы
или обслуживающим персоналом.
5. Прикладные программы не должны иметь
возможности ни остановить систему, ни вынудить ее изменить другую прикладную
программу или ее данные.
6. Когда прикладная программа обращается к
операционной системе, должна проверяться допустимость всех параметров,
Прикладная программа не должна иметь возможности изменить эти параметры между
моментами проверки и реального их использования операционной системой.
7. Никакие системные данные, непосредственно
доступные прикладным программам, не должны влиять на функционирование
операционной системы. Ошибка в прикладной программе, вследствие которой
содержимое этой памяти может быть случайно изменено, приводит
в конце концов к сбою системы.
8. Прикладные программы не должны иметь возможности
в обход операционной системы прямо использовать управляемые ею аппаратные
ресурсы. Прикладные программы не должны прямо вызывать компоненты операционной
системы, предназначенные для использования только ее подсистемами.
9. Компоненты операционной системы должны быть
изолированы друг от друга так, чтобы ошибка в одной из них не привела к
изменению других компонентов или их данных.
10. Если операционная система обнаруживает ошибку
в себе самой, она должна попытаться ограничить влияние этой ошибки одной
прикладной программой и в крайнем случае прекратить
выполнение только этой программы.
11. Операционная система должна давать прикладным
программам возможность по требованию исправлять обнаруженные в них ошибки, а
не безоговорочно прекращать их выполнение.
Реализация многих из
этих принципов влияет на архитектуру лежащего в основе системы аппаратного
обеспечения.
Обработка сбоев аппаратуры
Улучшая общую
надежность системы, следует заботиться не только об ошибках в программном
обеспечении (хотя надежность программного обеспечения требует наибольшего
внимания). Другая сторона, о которой необходимо подумать, — это ошибки во
входных данных системы (ошибки пользователя).
Наконец, еще один
интересующий нас класс ошибок — сбои аппаратуры. Во многих случаях они
обрабатываются самой аппаратурой за счет использования кодов, исправляющих
ошибки, исправления последствий сбоев (например, переключением на запасные
компоненты) и средств, обеспечивающих устойчивость к ошибкам (например,
голосование). Некоторые сбои, однако, нельзя обработать только аппаратными
средствами, они требуют помощи со стороны программного обеспечения. Ниже
приводится список возможностей, которые часто бывают необходи-мы в программных системах для борьбы со
сбоями аппаратуры.
1. Повторное выполнение операций. Многие
сбои аппаратуры не постоянны (например, скачки напряжения, шум в телекоммуникационных
линиях, колебания при механическом движении). Всегда имеет смысл попытаться
выполнить операцию, искаженную сбоем (например, команду машины или операцию
ввода-вывода), несколько раз, прежде чем принимать другие меры.
2. Восстановление
памяти. Если обнаруженный случайный сбой аппаратуры вызывает искажение
области основной памяти и эта область содержит статические данные (например,
команды объектной программы), то последствия сбоя можно ликвидировать,
повторно загрузив эту область памяти.
3. Динамическое изменение конфигурации. Если
аппаратная подсистема, такая, как процессор, канал ввода-вывода, блок основной
памяти или устройство ввода-вывода, выходит из строя, работоспособность системы
можно сохранить, динамически исключая неисправное
устройство из набора ресурсов системы.
4. Восстановление файлов. Системы
управления базами данных обычно обеспечивают избыточность данных, сохраняя копию
текущего состояния базы данных на выделенных устройствах ввода-вывода,
регистрируя все изменения базы данных или периодически автономно копируя всю
базу данных. Поэтому программы восстановления могут воссоздать базу данных в
случае катастрофического сбоя ввода-вывода.
5. Контрольная точка/рестарт. Контрольная
точка — это периодически обновляемая копия состояния прикладной программы или
всей системы. Если происходит отказ аппаратуры, такой, как ошибка ввода-вывода,
сбой памяти или питания, программа может быть запущена повторно с последней
контрольной точки.
6. Предупреждение
отказов питания. Некоторые вычислительные системы, особенно те, в которых
используется энергозависимая память, предусматривают прерывание,
предупреждающее программу о предстоящем отказе питания. Это дает возможность
организовать контрольную точку или перенести жизненно важные данные во
вторичную память.
7. Регистрация
ошибок. Все сбои аппаратуры, с которыми удилось справиться, должны
регистрироваться во внешнем файле, чтобы обслуживающий персонал мог получать
сведения о постепенном износе устройств.
Из рассмотренных выше
трех подгрупп методов обеспечения устойчивости к ошибкам только третья,
изоляция ошибок, применима для большинства систем программного обеспечения.
Важное обстоятельство,
касающееся всех четырех подходов, состоит в том, что обнаружение, исправление
ошибок и устойчивость к ошибкам в некотором отношении противоположны методам
предупреждения ошибок. В частности, обнаружение, исправление и устойчивость
требуют дополнительных функций от самого программного обеспечения. Тем самым не
только увеличивается сложность готовой системы, но и появляется возможность
внести новые ошибки при реализации этих функций. Как правило, все
рассматриваемые методы предупреждения и многие методы обнаружения ошибок
применимы к любому программному проекту. Методы исправления ошибок и
обеспечения устойчивости применяются не очень широко. Это, однако, зависит от
области приложения. Если рассматривается, скажем, система реального времени, то
ясно, что она должна сохранить работоспособность и при наличии ошибок, а тогда
могут оказаться желательными и методы исправления и обеспечения устойчивости.
К системам такого типа относятся телефонные переключательные системы, системы
управления технологическими процессами, аэрокосмические и авиационные
диспетчерские системы и операционные системы широкого назначения [51].
Многие организации,
занимающиеся созданием программного обеспечения, до 50% средств, выделенных на
разработку программ, тратят на тестирование, что составляет миллиарды долларов
по всему миру в целом. И все же, несмотря на громадные капиталовложения, знаний
о сути тестирования явно не хватает, и большинство программных продуктов
неприемлемо, ненадежно даже после «основательного тестирования».
О состоянии дел лучше
всего свидетельствует тот факт, что большинство людей, работающих в области
обработки данных, даже не могу `т
правильно определить понятие «тестирование», и это на самом деле главная
причина неудач. Если попросить любого профессионала определить понятие
«тестирование» либо открыть (как правило, слишком краткую) главу о тестировании
любого учебника программирования, то скорее всего
можно встретить такое определение:
«Тестирование —
процесс, подтверждающий правильность программы и демонстрирующий, что ошибок в
программе нет». Основной недостаток подобного определения заключается в том,
что оно совершенно неправильно; фактически это почти определение антонима
слова «тестирование». Поэтому определение описывает невыполнимую задачу, а так
как тестирование зачастую все же выполняется с успехом, по крайней мере с некоторым успехом, то такое определение логически
некорректно. Правильное определение тестирования таково:
Тестирование
— процесс выполнения программы с намерением найти ошибки.
Тестирование
оказывается довольно необычным процессом (вот почему оно и считается трудным),
так как это процесс разрушительный. Ведь цель проверяющего
(тестовика) — заставить программу сбиться. Он
доволен, если это ему удается; если же программа на его тесте не сбивается, он
не удовлетворен.
Невозможно
гарантировать отсутствие ошибок в программе; в лучшем случае можно попытаться
показать наличие ошибок. Если программа правильно ведет себя для значительного
набора тестов, нет оснований утверждать, что в ней нет ошибок; со всей
определенностью можно лишь утверждать, что неизвестно,
когда эта программа не работает. Конечно, если есть причины считать данный
набор тестов способным с большой вероятностью обнаружить все возможные ошибки,
то можно говорить о некотором уровне уверенности в правильности программы, устанавливаемой
этими тестами.
О тестировании
говорить довольно трудно, поскольку, хотя оно и способствует повышению надежности
программного обеспечения, его значение ограничено. Надежность невозможно внести
в программу в результате тестирования, она определяется правильностью этапов
проектирования. Наилучшее решение проблемы надежности — с самого начала не
допускать ошибок в программе. Однако вероятность того, что удастся безупречно
спроектировать большую программу, бесконечно мала. Роль тестирования состоит
как раз в том, чтобы определить местонахождение немногочисленных ошибок,
оставшихся в хорошо спроектированной программе. Попытки с помощью тестирования
достичь надежности плохо спроектированной программы совершенно бесплодны.
Комплексное тестирование, вероятно, самая непонятная
форма тестирования. Во всяком случае комплексное
тестирование не является тестированием всех функций полностью собранной
системы; тестирование такого типа называется тестированием внешних функций. Комплексное
тестирование — процесс поисков несоответствия системы ее исходным целям.
Элементами, участвующими в комплексном тестировании, служат сама система, описание
целей продукта и вся документация, которая будет поставляться вместе с
системой. Внешние спецификации, которые были ключевым элементом тестирования
внешних функций, играют лишь незначительную роль в комплексном тестировании.
Часть аргументов в
пользу этого должна быть уже очевидной: измеримые цели необходимы, чтобы
определить правила для процессов проектирования. Остальные соображения должны
проясниться сейчас. Если цели сформулированы, например, в виде требования,
чтобы система была достаточно быстрой, вполне надежной и чтобы в разумных
пределах обеспечивалась безопасность, тогда нет способа определить при
тестировании, в какой степени система достигает своих целей.
Если
вы не сформулировали цели вашего продукта или если эти цели неизмеримы, вы не
можете выполнить комплексное тестирование.
Комплексное
тестирование может быть процессом и контроля, и испытаний. Процессом испытаний
оно является тогда, когда выполняется в реальной среде пользователя или в
обстановке, которая специально создана так, чтобы напоминать среду пользователя.
Однако такая роскошь часто недоступна по ряду причин, и в подобных случаях комплексное тестирование системы
является процессом контроля (т.е. выполняется в имитируемой, или тестовой,
среде). Например, в случае бортовой вычислительной системы космического корабля
или системы противоракетной защиты вопрос о реальной среде (запуск настоящего
космического корабля или выстрел настоящей ракетой) обычно не стоит. Кроме
того, как мы увидим дальше, некоторые типы комплексных тестов не осуществимы в
реальной обстановке по экономическим соображениям, и лучше всего выполнять их
в моделируемой среде.
Проектирование
комплексного теста
Комплексное
тестирование — наиболее творческий из всех обсуждавшихся до сих пор видов
тестирования. Разработка хороших комплексных тестов требует часто даже больше
изобретательности, чем само проектирование системы. Здесь нет простых
рекомендаций типа тестирования всех ветвей или построения функциональных
диаграмм. Однако следующие 15 пунктов дают некоторое представление о том, какие
виды тестов могут понадобиться (рис. 5.3).
1. Тестирование
стрессов. Распространенный недостаток больших систем состоит в том, что
они функционируют как будто бы нормально при слабой или умеренной нагрузке, но
выходят из строя при большой нагрузке и в стрессовых ситуациях реальной среды.
Тестирование стрессов представляет собой попытки подвергнуть систему крайнему
«давлению», например, попытку одновременно подключить к системе разделения времени
100 терминалов, насытить банковскую систему мощным потоком входных сообщений
или систему управления — процессами аварийных сигналов от всех ее процессов.
Одна
из причин, по которой тестирование стрессов опускают (кроме очевидных
логических проблем, рассматриваемых в следующем разделе), состоит в том, что
персонал, занимающийся тестированием, хотя и признает потенциальную пользу
таких тестов, считает, что столь жесткие стрессовые ситуации никогда не
возникнут в реальной среде. Это предположение редко
оправдывается. Например, бывают случаи, когда все пользователи системы
разделения времени пытаются подключиться в одно и то же время (например, когда
произошел отказ системы на 1-2 минуты и система только что восстановлена). У
банковских систем, обслуживающих терминалы покупателей, бывают пиковые нагрузки
в первые часы работы магазинов и в час обеденного
перерыва.
2. Тестирование
объема. В то время как при тестировании стрессов делается попытка
подвергнуть систему серьезным нагрузкам в короткий интервал времени,
тестирование объема представляет собой попытку предъявить системе большие
объемы данных в течение более длительного времени. На вход компилятора следует
подать до нелепости громадную программу. Программа обработки текстов —
огромный документ. Очередь заданий операционной системы следует заполнить до
предела. Цель тестирования объема — показать, что система или программа не
может обрабатывать данные в количествах, указанных в их спецификациях.
3. Тестирование конфигурации. Многие
системы, например операционные системы или системы управления файлами, обеспечивают
работу различных конфигураций аппаратуры и программного обеспечения. Число
таких конфигураций часто слишком велико, чтобы можно было проверить все
варианты. Однако следует тестировать по крайней мере
максимальную и минимальную конфигурации. Система должна быть проверена со
всяким аппаратным устройством, которое она обслуживает, или со всякой
программой, с которой она должна взаимодействовать. Если сама программная
система допускает несколько конфигураций (т.е. покупатель может выбрать только
определенные части или варианты системы), должна быть тестирована каждая из
них.
4. Тестирование совместимости. В
большинстве своем разрабатываемые системы не являются совершенно новыми; они
представляют собой улучшение прежних версий или замену устаревших систем. В
таких случаях на систему, вероятно, накладывается дополнительное требование
совместимости, в соответствии с которым взаимодействие пользователя с прежней
версией должно полностью сохраниться и в новой системе. Например, возможно,
потребуется, чтобы в новом выпуске операционной системы язык управления
заданиями, язык общения с терминалом и скомпилированные прикладные программы,
использовавшиеся раньше, могли применяться без изменений. Такие требования
совместимости следует тестировать. Как периодически подчеркивалось при
разговоре обо всех других формах тестирования, цель при тестировании
совместимости должна состоять в том, чтобы показать наличие несовместимости.
5. Тестирование защиты. Так как внимание
к вопросам сохранения секретности в обществе возрастает, к большинству систем
предъявляются определенные требования по обеспечению защиты от
несанкционированного доступа. Например, операционная система должна устранить
всякую возможность для программы пользователя увидеть данные или программу
другого пользователя. Административная информационная система не должна
позволять подчиненному получить сведения о зарплате тех, кто стоит выше его по
служебной лестнице. Цель тестирования защиты — нарушить секретность в системе.
Один из методов нанять профессиональную группу «взломщиков», т. е. людей с
опытом разрушения средств обеспечения защиты в системах. Для тестирования
защиты важно построить такие тесты, которые нарушат программные средства
защиты, например, механизм защиты памяти операционной системы или механизмы
защиты данных системы управления базой данных. Одним из путей разработки
подобных тестов является изучение известных проблем защиты в этих системах и
генерация тестов, которые позволяют проверить, как решаются аналогичные
проблемы в тестируемой системе.
6. Тестирование требований к памяти. При
проектировании многих систем ставятся цели, определяющие объем основной и
вторичной памяти, которую системе разрешено использовать в различных условиях.
С помощью специальных тестов нужно попытаться показать, что система этих целей
не достигает.
7. Тестирование производительности. При
разработке многих программ ставится задача — обеспечить их производительность,
или эффективность. Определяются такие характеристики, как время отклика и
уровень пропускной способности при определенной нагрузке и конфигурации
оборудования. Проверка системы в этих случаях сводится к демонстрации того, что
данная программа не удовлетворяет поставленным целям. Поэтому необходимо
разработать тесты, с помощью которых можно попытаться показать, что система не
обладает требуемой производительностью.
8. Тестирование настройки. К сожалению,
процедуры настройки многих систем сложны. Тестирование процесса настройки системы
очень важно, поскольку одна из наиболее обескураживающих ситуаций, с которыми
сталкивается покупатель, заключается в том, что он оказывается не в состоянии
даже настроить новую систему.
9. Тестирование надежности/готовности. Конечно,
назначение всех видов тестирования — повысить надежность тестируемой
программы, но если в исходном документе, отражающем цели программы, есть особые
указания, касающиеся надежности, можно разработать специальные тесты для ее
проверки. Ключевой момент в комплексном тестировании заключается в попытке доказать,
что система не удовлетворяет исходным требованиям к надежности (среднее время
между отказами, количество ошибок, способность к обнаружению, исправлению
ошибок и/или устойчивость к ошибкам и т. д.). Тестирование надежности крайне
сложно, и все же следует постараться тестировать как можно больше этих
требований. Например, в систему можно намеренно внести ошибки (как аппаратные,
так и программные), чтобы тестировать средства обнаружения, исправления и
обеспечения устойчивости. Другие требования к надежности тестировать почти
невозможно.
10. Тестирование средств восстановления. Важная
составная часть требований к операционным системам, системам управления базами
данных и системам передачи данных — обеспечение способности к восстановлению,
например восстановлению утраченных данных в базе данных или восстановлению
после отказа в телекоммуникационной линии. Во многих проектах совершенно
забывают о тестировании соответствующих средств. Лучше всего попытаться
показать, что эти средства работают неправильно, при комплексном тестировании
системы. Можно намеренно ввести'в операционную
систему программные ошибки, чтобы проверить, восстановится ли она после их
устранения. Неисправности аппаратуры, например ошибки устройств
ввода-вывода и контроля на четность ячеек памяти, можно промоделировать.
Ошибки в данных (помехи в линиях связи и неправильные значения указателей в
базе данных) можно намеренно создать или промоделировать для анализа реакции на
них системы.
11. Тестирование удобства обслуживания. Либо
в требованиях к продукту, либо в требованиях к проекту должны быть перечислены
задачи, определяющие удобство обслуживания (сопровождения) системы. Все
сервисные средства системы нужно проверять при комплексном тестировании. Все
документы, описывающие внутреннюю логику, следует проанализировать глазами
обслуживающего персонала, чтобы понять, как быстро и точно можно указать
причину ошибки, если известны только некоторые се симптомы. Все средства, обеспечивающие
сопровождение и поставляемые вместе с системой, также должны быть проверены.
12. Тестирование публикаций. Проверка
точности всей документации для пользователя является важной частью комплексного
тестирования. Все комплексные тесты следует строить только на основе
документации для пользователя. Ее проверяют при определении правильности
представления предшествующих гестов системы. Пользовательская документация
должна быть и предметом инспекции при проверке ее на точность
И ясность. Любые примеры, приведенные в документации, следует оформить
как тест и подать на вход программы.
13. Тестирование психологических факторов. Хотя
во время тестирования системы следует проверить и психологические факторы, эта
сторона не так важна, как другие, потому что обычно при тестировании уже слишком поздно исправлять серьезные просчеты в таких
вопросах. Однако мелкие недостатки могут быть обнаружены и устранены при
тестировании системы. Например, может оказаться, что ответы или сообщения
системы плохо сфор-мулированы
или ввод команды пользователя требует постоянных переключений верхнего и
нижнего регистров.
14. Тестирование удобства установки. Процедуры
установки (настройки) некоторых типов систем программного обеспечения весьма
сложны. Тестирование подобных процедур является частью процесса тестирования
системы.
15. Тестирование удобства эксплуатации. Не
менее важным видом тестирования системы является попытка выявления психологических
(пользовательских) проблем, или проблем удобства эксплуатации. Анализ психологических
факторов является, к со-' жалению,
до сих пор весьма субъективным, так как при производстве вычислительной
техники уделялось недостаточное внимание изучению и определению
психологических аспектов программных систем. Большинство систем обработки
данных либо является компонентами более крупных систем, предполагающих
деятельность человека, либо сами регламентируют такую деятельность во время
своей работы. Нужно проверить, что вся эта деятельность (например, поведение
оператора или пользователя за терминалом) удовлетворяет определенным условиям.
Не все из
перечисленных 15 пунктов применимы к тестированию всякой системы (например,
когда тестируется отдельная прикладная программа), но
тем не менее это перечень вопросов, которые разумно иметь в виду.
Основное правило при
комплексном тестировании — все годится. Пишите разрушительные тесты,
проверяйте все функциональные границы системы, пишите тесты, представляющие
ошибки пользователя (например, оператора системы). Нужно, чтобы тестовик думал так же, как пользователь или покупатель, а
это предполагает доскональное понимание того, для чего система будет
применяться. Поэтому возникает вопрос: «Кто же должен выполнять комплексное
тестирование и, в частности, кто должен проектировать тесты?» Во всяком случае этого не должны делать программисты или организации,
ответственные за разработку системы. Группа тестирования системы должна быть
независимой организацией и должна включать профессиональных специалистов по
комплексному тестированию систем; несколько пользователей, для которых система
разрабатывалась; основных аналитиков и проектировщиков системы и, возможно,
одного или двух психологов. Очень важно включить самих проектировщиков системы.
Это не противоречит аксиоме, согласно которой невозможно тестировать свою
собственную систему, поскольку система прошла через много рук после того, как
ее описали архитектор или проектировщик. В действительности проектировщик и не
пытается тестировать собственную систему; он ищет расхождения между окончательной
версией и тем, что он первоначально, возможно год или два назад, имел в виду.
Для комплексного тестирования желательно также иметь информацию о рынке или
пользователях, уточняющую предположения о конфигурации и характере применения
системы.
Комплексное
тестирование системы — такая особая и такая важная работа, что в будущем
возможно появление компаний, специализирующихся в основном на комплексном
тестировании систем, разработанных другими.
Как уже упоминалось,
компонентами комплексного теста являются исходные цели, документация,
публикации для пользователей и сама система. Все комплексные тесты должны быть
подготовлены на основе публикаций для пользователя (а не внешних
спецификаций). Ко внешним спецификациям обращаться
следует только для того, чтобы разбираться в противоречиях между системой и
публикациями о ней.
По своей природе
комплексные тесты никогда не сводятся к проверке отдельных функций системы. Они
часто пишутся в форме сценариев, представляющих ряд последовательных действий
пользователя. Например, один комплексный тест может представлять подключение
терминала к системе, выдачу последовательно 10—20 команд и затем отключение от
системы. Вследствие их особой сложности тесты системы состоят из нескольких
компонентов: сценария, входных данных и ожидаемых выходных данных В сценарии точно указываются действия, которые должны быть совершены
во время выполнения теста.
Вторым по важности
аспектом тестирования после проектирования тестов является последовательность
слияния всех модулей в систему или программу. Эта сторона вопроса обычно не
получает достаточного внимания и часто рассматривается слишком поздно. Выбор
этой последовательности, однако, является одним из самых жизненно важных
решений, принимаемых на этапе тестирования, поскольку он определяет форму, в
которой записываются тесты, типы необходимых инструментов тестирования,
последовательность программирования модулей, а также тщательность и
экономичность всего этапа тестирования. По этой причине
такое решение должно приниматься на уровне проекта в целом и на достаточно
ранней его стадий.
Тестирование модулей
(или блоков) представляет собой процесс тестирования отдельных подпрограмм или
процедур программы. Здесь подразумевается, что, прежде чем начинать тестирование
программы в целом, следует протестировать отдельные небольшие модули,
образующие эту программу. Такой подход мотивируется
тремя причинами. Во-первых, появляется возможность управлять комбинаторикой
тестирования, поскольку первоначально внимание концентрируемся на небольших
модулях программы. Во-вторых, облегчается задача отладки программы, т.е.
обнаружение места ошибки и исправление текста программы. В-третьих,
допускается параллелизм, что позволяет одновременно тестировать несколько
модулей.
Цель тестирования
модулей — сравнение функций, реализуемых модулем, со спецификациями его
функций или интерфейса. Тестирование модулей в основном ориентировано на принцип
«белого ящика». Это объясняется прежде всего тем, что
принцип «белого ящика» труднее реализовать при переходе в последующем к
тестированию более крупных единиц, например программ в целом. Кроме того,
последующие этапы тестирования ориентированы на обнаружение ошибок различного
типа, т. е. ошибок, не обязательно связанных с логикой программы, а возникающих,
например, из-за несоответствия программы требованиям пользователя.
Имеется большой выбор
возможных подходов, которые могут быть использованы для слияния модулей в
более крупные единицы. В большинстве своем они могут рассматриваться как варианты
шести основных подходов, описанных ниже.
Пошаговое
тестирование
Реализация процесса
тестирования модулей опирается на два ключевых положения: построение
эффективного набора тестов и выбор способа, посредством которого модули
комбинируются при построении из них рабочей программы. Второе положение
является важным, так как оно задает форму написания тестов модуля, типы
средств, используемых при тестировании, порядок кодирования и тестирования
модулей, стоимость генерации гестов и стоимость отладки. Рассмотрим два
подхода к комбинированию модулей: пошаговое и монолитное тестирование.
Возникает вопрос:
«Что лучше — выполнить по отдельности тестирование каждого модуля, а затем,
комбинируя их, сформировать рабочую программу или же каждый модуль для тестирования
подключать к набору ранее оттестированных модулей?». Первый подход обычно
называют монолитным методом, или методом «большого удара», при
тестировании и сборке программы;
второй подход известен как пошаговый метод тестирования или
сборки.
Метод
пошагового тестирования предполагает, что модули тестируются не
изолированно друг от друга, а подключаются поочередно для выполнения теста к
набору уже ранее оттестированных модулей. Пошаговый процесс продолжается до
тех пор, пока к набору оттестированных модулей не будет подключен последний
модуль.
Детального разбора
обоих методов мы делать не будем, приведем лишь некоторые общие выводы.
1. Монолитное тестирование требует больших
затрат труда. При пошаговом же тестировании «снизу-вверх» затраты труда
сокращаются.
2. Расход машинного времени при монолитном
тестировании меньше.
3. Использование монолитного метода
предоставляет большие возможности для параллельной организации работы на начальной
фазе тестирования (тестирования всех модулей одновременно). Это положение
может иметь важное значение при выполнении больших
проектов, в которых много модулей и много исполнителей, поскольку численность
персонала, участвующего в проекте, максимальна на начальной фазе.
4. При пошаговом тестировании раньше
обнаруживаются ошибки в интерфейсах между модулями, поскольку раньше начинается
сборка программы. В противоположность этому при монолитном тестировании модули
«не видят друг друга» до последней фазы процесса тестирования.
5. Отладка программ
при пошаговом тестировании легче. Если есть ошибки в межмодульных интерфейсах,
а обычно так и бывает, то при монолитном тестировании они могут быть обнаружены лишь тогда, когда собрана вся программа. В этот
момент локализовать ошибку довольно трудно, поскольку она может находиться в
любом месте программы. Напротив, при пошаговом тестировании ошибки такого типа
в основном связаны с тем модулем, который подключается последним.
6. Результаты пошагового тестирования более
совершенны.
В заключение отметим,
что п. 1, 4, 5, 6 демонстрируют преимущества пошагового тестирования, а п. 2 и
3 — его недостатки. Поскольку для современного этапа развития вычислительной
техники характерны тенденции к уменьшению стоимости аппаратуры и увеличению
стоимости труда, последствия ошибок в математическом обеспечении весьма
серьезны, а стоимость устранения ошибки тем меньше, чем раньше она обнаружена;
пре имущества, указанные в п. 1, 4, 5, 6, выступают на первый план. В то же
время ущерб, наносимый недостатками (п. 2 и 3), невелик. Все это позволяет нам
сделать вывод, что пошаговое тестирование является предпочтительным.
Убедившись в
преимуществах пошагового тестирования перед монолитным,
исследуем две возможные стратегии тестирования: нисходящее и восходящее.
Прежде всего внесем ясность в терминологию.
Во-первых, термины
«нисходящее тестирование», «нисходящая разработка», «нисходящее
проектирование» часто используются как синонимы. Действительно, термины
«нисходящее тестирование» и «нисходящая разработка» являются синонимами (в том
смысле, что они подразумевают определенную стратегию при тестировании и
создании текстов модулей), но нисходящее проектирование — это совершенно иной и
независимый процесс. Программа, спроектированная нисходящим методом, может тестироваться
и нисходящим, и восходящим методами.
Во-вторых, восходящая
разработка, или тестирование, часто отождествляется с монолитным тестированием.
Это недоразумение возникает из-за того, что начало восходящего тестирования
идентично монолитному при тестировании нижних или
терминальных модулей. Но выше мы показали, что восходящее тестирование на
самом деле представляет собой пошаговую стратегию.
Восходящее
тестирование
При восходящем
подходе программа собирается и тестируется «снизу вверх». Только модули самого
нижнего уровня («терминальные» модули; модули, не вызывающие других модулей)
тестируются изолированно, автономно. После того как тестирование этих модулей
завершено, вызов их должен быть так же надежен, как вызов встроенной функции
языка или оператор присваивания. Затем тестируются модули, непосредственно
вызывающие уже проверенные. Эти модули более высокого уровня тестируются не
автономно, а вместе с уже проверенными модулями более низкого уровня. Процесс
повторяется до тех пор, пока не будет достигнута вершина. Здесь завершается и
тестирование модулей, и тестирование сопряжений программы.
При восходящем
тестировании для каждого модуля необходим драйвер: нужно подавать тесты
в соответствии с сопряжением тестируемого модуля. Одно из возможных решений — написать
для каждого модуля небольшую ведущую программу. Тестовые данные представляются
как «встроенные» непосредственно в эту программу переменные и структуры данных,
и она многократно вызывает тестируемый модуль, с каждым вызовом передавая ему
новые тестовые данные. Имеется и лучшее решение: воспользоваться программой
тестирования модулей — это инструмент тестирования, позволяющий описывать
тесты на специальном языке и избавляющий от необходимости писать драйверы.
Здесь отсутствуют
проблемы, связанные с невозможностью или трудностью создания всех тестовых
ситуаций, характерные для нисходящего тестирования. Драйвер как средство
тестирования применяется непосредственно к тому модулю, который тестируется,
где нет промежуточных модулей, которые следует принимать во внимание.
Анализируя другие проблемы, возникающие при нисходящем тестировании, можно
заметить, что при восходящем тестировании невозможно принять неразумное решение
о совмещении тестирования с проектированием программы, поскольку нельзя начать
тестирование до тех пор, пока не спроектированы модули нижнего уровня. Не
существует также и трудностей с незавершенностью тестирования одного модуля при
переходе к тестированию другого, потому что при восходящем тестировании с
применением нескольких версий заглушки нет сложностей с представлением тестовых
данных.
Нисходящее
тестирование
Нисходящее
тестирование (называемое также нисходящей разработкой) не является полной
противоположностью восходящему, но в первом
приближении может рассматриваться как таковое. При нисходящем подходе
программа собирается и тестируется «сверху вниз». Изолированно тестируется
только головной модуль. После того как тестирование этого модуля завершено, с
ним соединяются (например, редактором связей) один за другим модули,
непосредственно вызываемые им, и тестируется полученная комбинация. Процесс
повторяется до тех пор, пока не будут собраны и проверены все модули.
При этом подходе
немедленно возникают два вопроса: 1. «Что делать, когда тестируемый модуль
вызывает модуль более низкого уровня (которого в данный момент еще не
существует)?» и 2. «Как подаются тестовые данные?»
Ответ на первый
вопрос состоит в том, что для имитации функций недостающих модулей
программируются модули-«заг-лушки»,
которые моделируют функции отсутствующих модулей.
Интересен и второй вопрос: в какой форме готовятся тестовые данные и как они
передаются программе? Если бы головной модуль содержал все нужные операции
ввода и вывода, ответ был бы прост: тесты пишутся в виде обычных для
пользователей внешних данных и передаются программе через выделенные ей устройства
ввода. Так, однако, случается редко. В хорошо спроектированной программе
физические операции ввода-вывода выполняются на нижних уровнях структуры,
поскольку физический ввод-вывод — абстракция довольно низкого уровня. Поэтому
для того, чтобы решить проблему экономически эффективно, модули добавляются не
в строго нисходящей последовательности (все модули одного горизонтального
уровня, затем модули следующего уровня), а таким образом, чтобы обеспечить
функционирование операций физического ввода-вывода как можно быстрее. Когда
эта цель достигнута, нисходящее тестирование получает значительное
преимущество: все дальнейшие тесты готовятся в той же форме, которая рассчитана
на пользователя.
Нисходящий метод
имеет как достоинства, так и недостатки по сравнению с восходящим.
Самое значительное достоинство — то, что этот метод совмещает тестирование
модуля, тестирование сопряжений и частично тестирование внешних функций. С
этим же связано другое его достоинство: когда модули ввода-вывода уже
подключены, тесты можно готовить в удобном виде. Нисходящий подход выгоден
также в том случае, когда есть сомнения относительно осуществимости программы
в целом или когда в проекте программы могут оказаться серьезные дефекты.
Преимуществом
нисходящего подхода очень часто считают отсутствие необходимости в драйверах;
вместо драйверов вам просто следует написать «заглушки».
Нисходящий метод
тестирования имеет, к сожалению, некоторые недостатки. Основным из них
является то, что модуль ред
ко тестируется досконально сразу после его подключения. Дело в том, что
основательное тестирование некоторых модулей может потребовать крайне
изощренных заглушек. Программист часто решает не тратить массу времени на их
программирование, а вместо этого пишет простые заглушки и проверяет лишь часть
условий в модуле. Он, конечно, собирается вернуться и закончить тестирование
рассматриваемого модуля позже, когда уберет заглушки. Такой план тестирования
— определенно не лучшее решение, поскольку об отложенных условиях часто
забывают.
Второй тонкий
недостаток нисходящего подхода состоит в том, что он может породить веру в
возможность начать программирование и тестирование верхнего уровня программы
до того, как вся программа будет полностью спроектирована. Эта идея на первый
взгляд кажется экономичной, но обычно дело обстоит совсем наоборот. Большинство
опытных проектировщиков признает, что проектирование программы — процесс
итеративный. Редко первый проект оказывается совершенным. Нормальный стиль
проектирования структуры программы предполагает по окончании проектирования
нижних уровней вернуться назад и подправить верхний уровень, внеся в него
некоторые усовершенствования или исправляя ошибки, либо иногда даже выбросить
проект и начать все сначала, потому что разработчик
внезапно увидел лучший подход. Если же головная часть программы уже
запрограммирована и оттестирована, то возникает серьезное сопротивление любым
улучшениям ее структуры. В конечном итоге за счет таких улучшений обычно можно
сэкономить больше, чем те несколько дней или недель, которые рассчитывает
выиграть проектировщик, приступая к программированию слишком рано.
Метод
«большого скачка»
Вероятно, самый
распространенный подход к интеграции модулей — метод «большого скачка». В
соответствии с этим методом каждый модуль тестируется автономно. По окончании
тестирования модулей они интегрируются в систему все сразу.
Метод «большого
скачка» по сравнению с другими подходами имеет много недостатков и мало
достоинств. Заглушки и драйверы необходимы для каждого модуля. Модули не
интегрируются до самого последнего момента, а это означает, что в течение долгого времени серьезные ошибки в сопряжениях могут остаться
необнаруженными.
Если программа мала
(как, например, программа загрузчика) и хорошо спроектирована, метод «большого
скачка» может оказаться приемлемым. Однако для крупных программ метод «большого
скачка» обычно губителен.
Метод
сандвича
Тестирование
методом сандвича представляет собой компромисс между восходящим и нисходящим
подходами. Здесь делается попытка воспользоваться достоинствами обоих
методов, избежав их недостатков.
При использовании
этого метода одновременно начинают восходящее и нисходящее тестирование,
собирая программу как снизу, так и сверху и встречаясь
в конце концов где-то в середине. Точка встречи зависит от конкретной
тестируемой программы и должна быть заранее определена при изучении ее структуры.
Например, если разработчик может представить свою систему в виде уровня
прикладных модулей, затем уровня модулей обработки запросов, затем уровня
примитивных функций, то он может решить применять нисходящий метод на уровне
прикладных модулей (программируя заглушки вместо модулей обработки запросов),
а на остальных уровнях применить восходящий метод.
Метод сандвича
сохраняет такое достоинство нисходящего и восходящего подходов, как начало
интеграции системы на самом раннем этапе. Поскольку вершина программы вступает
в строй рано, мы, как в нисходящем методе, уже на раннем этапе получаем
работающий каркас программы. Так как нижние уровни программы создаются
восходящим методом, снимаются проблемы нисходящего метода, которые были связаны
с невозможностью тестировать некоторые условия в
глубине программы.
Модифицированный
метод сандвича
При тестировании
методом сандвича возникает та же проблема, что и при нисходящем подходе, хотя
здесь она СТОИТ не так остро. Проблема эта в том, что невозможно досконально тестировать
отдельные модули. Восходящий этап тестирования по методу сандвича решает эту
проблему для модулей нижних уровней, но она может по-прежнему оставаться
открытой для нижней половины верхней части программы. В модифицированном
методе сандвича нижние уровни также тестируются строго снизу вверх. А модули
верхних уровней сначала тестируются изолированно, а затем собираются
нисходящим методом. Таким образом, модифицированный метод
сандвича также представляет собой компромисс между восходящим и нисходящим
подходами [50].