Основы программирования

Фирмы по обслуживанию компьютерных сетей. Абонентское компьютерное обслуживание фирм.
Методы анализа
Индекс материала
Методы анализа
Описание потоков данных и процессов
Описание потоков данных и процессов
Методы анализа, ориентированные на структуры данных
Методика Джексона
Шаг объект-структура
Шаг начального моделирования
Контрольные вопросы
Основы проектирования программных систем
Особенности этапа проектирования
Структурирование системы
Моделирование управления
Декомпозиция подсистем на модули
Связность модуля
Функциональная связность
Коммуникативная связность
Временная связность
Связность по совпадению
Сцепление модулей
Контрольные вопросы
Классические методы проектирования
Проектирование для потока данных типа «преобразование»
Проектирование для потока данных типа «запрос»
Доопределение функций
Учет системного времени
Структурное тестирование программного обеспечения
Тестирование «черного ящика»
Потоковый граф
Цикломатическая сложность
Шаги способа тестирования базового пути
Тестирование ветвей и операторов отношений
Тестирование циклов
Неструктурированные циклы
Функциональное тестирование программного обеспечения
Способ разбиения по эквивалентности
Способ анализа граничных значений
Способ диаграмм причин-следствий
Организация процесса тестирования программного обеспечения
Тестирование интеграции
Восходящее тестирование интеграции
Системное тестирование
Стрессовое тестирование
Все страницы

ГЛАВА 3. Классические методы анализа

 

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

Структурный анализ

 

Структурный анализ — один из формализованных методов анализа требований к ПО. Автор этого метода — Том Де Марко (1979) [27]. В этом методе программное изделие рассматривается как преобразователь информационного потока данных. Основной элемент структурного анализа — диаграмма потоков данных.

Диаграммы потоков данных

 

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

Пример системы взаимосвязанных диаграмм показан на рис. 3.2.

Диаграмма высшего (нулевого) уровня представляет систему как единый овал со стрелкой, ее называют основной или контекстной моделью. Контекстная модель используется для указания внешних связей программного изделия. Для детализации (уточнения системы) вводится диаграмма 1-го уровня. Каждый из преобразователей этой диаграммы — подфункция общей системы. Таким образом, речь идет о замене преобразователя F на целую систему преобразователей.

Дальнейшее уточнение (например, преобразователя F3) приводит к диаграмме 2-го уровня. Говорят, что ПДД1 разбивается на диаграммы 2-го уровня.

 

Рис. 3.2. Система взаимосвязанных диаграмм потоков данных

 

ПРИМЕЧАНИЕ

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

 

Диаграмма потоков данных — это абстракция, граф. Для связи графа с проблемной областью (превращения в граф-модель) надо задать интерпретацию ее компонентов — дуг и вершин.


Описание потоков данных и процессов

 

Базовые средства диаграммы не обеспечивают полного описания требований к программному изделию. Очевидно, что должны быть описаны стрелки — потоки данных — и преобразователи — процессы. Для этих целей используются словарь требований (данных) и спецификации процессов.

Словарь требований (данных) содержит описания потоков данных и хранилищ данных. Словарь требований является неотъемлемым элементом любой CASE-утилиты автоматизации анализа. Структура словаря зависит от особенностей конкретной CASE-утилиты. Тем не менее можно выделить базисную информацию типового словаря требований.

Большинство словарей содержит следующую информацию.

1. Имя (основное имя элемента данных, хранилища или внешнего объекта).

2. Прозвище (Alias) — другие имена того же объекта.

3. Где и как используется объект — список процессов, которые используют данный элемент, с указанием способа использования (ввод в процесс, вывод из процесса, как внешний объект или как память).

4. Описание содержания — запись для представления содержания.

5. Дополнительная информация — дополнительные сведения о типах данных, допустимых значениях, ограничениях и т. д.

Спецификация процесса — это описание преобразователя. Спецификация поясняет: ввод данных в преобразователь, алгоритм обработки, характеристики производительности преобразователя, формируемые результаты.

Количество спецификаций равно количеству преобразователей диаграммы.

Расширения для систем реального времени

 

Как известно, программное изделие (ПИ) является дискретной моделью проблемной области, взаимодействующей с непрерывными процессами физического мира (рис. 3.3).

 

Рис. 3.3. Программное изделие как дискретная модель проблемной области

 

П. Вард и С. Меллор приспособили диаграммы потоков данных к следующим требованиям систем реального времени [73].

1. Информационный поток накапливается или формируется в непрерывном времени.

2. Фиксируется управляющая информация. Считается, что она проходит через систему и связывается с управляющей обработкой.

3. Допускается множественный запрос на одну и ту же обработку (из внешней среды).

 

Рис. 3.4. Расширения диаграмм для систем реального времени

 

Новые элементы имеют обозначения, показанные на рис. 3.4.

Приведем два примера использования новых элементов.

Пример 1. Использование потоков, непрерывных во времени.

На рис. 3.5 представлена модель анализа программного изделия для системы слежения за газовой турбиной.

 

Рис. 3.5. Модель ПО для системы слежения за газовой турбиной

 

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

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

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

Пример 2. Использование потоков управления.

Рассмотрим компьютерную систему, которая управляет роботом (рис. 3.6).

 

Рис. 3.6. Модель ПО для управления роботом

 

Установка в прибор деталей, собранных роботом, фиксируется установкой бита в буфере состояния деталей (он показывает присутствие или отсутствие каждой детали). Информация о событиях, запоминаемых в буфере, посылается в виде строки битов в преобразователь «Наблюдение за прибором». Преобразователь читает команды оператора только тогда, когда управляющая информация (битовая строка) показывает наличие всех деталей. Флаг события (Старт-Стоп) посылается в управляющий преобразователь «Начать движение», который руководит дальнейшей командной обработкой. Потоки данных посылаются в преобразователь команд роботу при наличии события «Процесс активен».


 

Расширение возможностей управления

 

Д. Хетли и И. Пирбхаи сосредоточили внимание на аспектах управления программным продуктом [34]. Они выделили системные состояния и механизм перехода из одного состояния в другое. Д. Хетли и И. Пирбхаи предложили не вносить в ПДД элементы управления, такие как потоки управления и управляющие процессы. Вместо этого они ввели диаграммы управляющих потоков (УПД).

Диаграмма управляющих потоков содержит:

q обычные преобразователи (управляющие преобразователи исключены вообще);

q потоки управления и потоки событий (без потоков данных).

Вместо управляющих преобразователей в УПД используются указатели — ссылки на управляющую спецификацию УСПЕЦ. Как показано на рис. 3.7, ссылка изображается как косая пунктирная стрелка, указывающая на окно УСПЕЦ (вертикальную черту).

 

Рис. 3.7. Изображение ссылки на управляющую спецификацию

УСПЕЦ управляет преобразователями в ПДД на основе события, которое проходит в ее окно (по ссылке). Она предписывает включение конкретных преобразователей как результат конкретного события.

Иллюстрация модели программной системы, использующей описанные средства, приведена на рис. 3.8.

 

Рис. 3.8. Композиция модели обработки и управления

 

В модель обработки входит набор диаграмм потоков данных и набор спецификаций процессов. Модель управления образует набор диаграмм управляющих потоков и набор управляющих спецификаций. Модель обработки подключается к модели управления с помощью активаторов процессов. Активаторы включают в конкретной ПДД конкретные преобразователи. Обратная связь модели обработки с моделью управления осуществляется с помощью условий данных. Условия данных формируются в ПДД (когда входные данные преобразуются в события).

Модель системы регулирования давления космического корабля

 

Обсудим модель системы регулирования давления космического корабля, представленную на рис. 3.9.

Начнем с диаграммы потоков данных. Основной процесс в ПДД — Слежение и регулирование давления. На его входы поступают: измеренное Давление в кабине и Мах давление: На выходе процесса — поток данных Изменение давления. Содержание процесса описывается в его спецификации ПСПЕЦ.

Спецификация процесса ПСПЕЦ может включать:

1) поясняющий текст (обязательно);

2) описание алгоритма обработки;

3) математические уравнения;

4) таблицы;

5) диаграммы.

Элементы со второго по пятый не обязательны.

 

Рис. 3.9. Модель системы регулирования давления космического корабля

 

С помощью ПСПЕЦ разработчик создает описание для каждого преобразователя, которое рассматривается как:

q первый шаг создания спецификации требований к программному изделию;

q руководство для проектирования программ, которые будут реализовывать процессы.

В нашем примере спецификация процесса имеет вид

если Давление в кабине > мах

то Избыточное давление:=11;

иначе Избыточное давление:=0;

алгоритм регулирования;

выч.Изменение давления;

конец если;

Таким образом, когда давление в кабине превышает максимум, генерируется управляющее событие Избыточное давление. Оно должно быть показано на диаграмме управляющих потоков УПД. Это событие входит в окно управляющей спецификации УСПЕЦ.

Управляющая спецификация моделирует поведение системы. Она содержит:

q таблицу активации процессов (ТАП);

q диаграмму переходов-состояний (ДПС).

Таблица активации процессов показывает, какие процессы будут вызываться (активироваться) в потоковой модели в результате конкретных событий.

ТАП включает три раздела — Входные события, Выходные события, Активация процессов. Логика работы ТАП такова: входное событие вызывает выходное событие, которое активирует конкретный процесс. Для нашей модели ТАП имеет вид, представленный в табл. 3.1.

Таблица 3.1. Таблица активации процессов

Входные события:

Включение системы

1

0

0

Избыточное давление

0

1

0

Норма

0

0

1

Выходные события:

Тревога

0

1

0

Работа

1

0

1

Активация процессов:

Слежение и регулирование давления

1

0

1

Уменьшение давления

0

1

0

 

Видим, что в нашем примере входных событий три: два внешних события (Включение системы, Норма) и одно — условие данных (Избыточное Давление). Работа ТАП инициируется входным событием, «втекающим» в окно УСПЕЦ. В результате ТАП вырабатывает выходное событие — активатор. В нашем примере активаторами являются события Работа и Тревога. Активатор «вытекает» из окна УСПЕЦ, запуская в УПД конкретный процесс.

Другой элемент УСПЕЦ — Диаграмма переходов-состояний. ДПС отражает состояния системы и показывает, как она переходит из одного состояния в другое.

ДПС для нашей модели показана на рис. 3.10.

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

 

Изучая ДПС, разработчик может анализировать поведение модели и установить, нет ли «дыр» в определении поведения.


Методы анализа, ориентированные на структуры данных

 

Элементами проблемной области для любой системы являются потоки, процессы и структуры данных. При структурном анализе активно работают только с потоками данных и процессами.

Методы, ориентированные на структуры данных, обеспечивают:

1) определение ключевых информационных объектов и операций;

2) определение иерархической структуры данных;

3) компоновку структур данных из типовых конструкций — последовательности, выбора, повторения;

4) последовательность шагов для превращения иерархической структуры данных в структуру программы.

Наиболее известны два метода: метод Варнье-Орра и метод Джексона.

В методе Варнье-Орра для представления структур применяют диаграммы Варнье [54].

Для построения диаграмм Варнье используют 3 базовых элемента: последовательность, выбор, повторение (рис. 3.11) [74].

 

Рис. 3.11. Базовые элементы в диаграммах Варнье

 

Как показано на рис. 3.12, с помощью этих элементов можно строить информационные структуры с любым количеством уровней иерархии.

 

Рис. 3.12. Структура газеты в виде диаграммы Варнье

 

Как видим, для представления структуры газеты здесь используются три уровня иерархии.

Метод анализа Джексона

 

Как и метод Варнье-Орра, метод Джексона появился в период революции структурного программирования. Фактически оба метода решали одинаковую задачу: распространить базовые структуры программирования (последовательность, выбор, повторение) на всю область конструирования сложных программных систем. Именно поэтому основные выразительные средства этих методов оказались так похожи друг на друга.


 

Методика Джексона

 

Метод Джексона (1975) включает 6 шагов [39]. Три шага выполняются на этапе анализа, а остальные — на этапе проектирования.

1. Объект-действие. Определяются объекты — источники или приемники информации и действия — события реального мира, воздействующие на объекты.

2. Объект-структура. Действия над объектами представляются диаграммами Джексона.

3. Начальное моделирование. Объекты и действия представляются как обрабатывающая модель. Определяются связи между моделью и реальным миром.

4. Доопределение функций. Выделяются и описываются сервисные функции.

5. Учет системного времени. Определяются и оцениваются характеристики планирования будущих процессов.

6. Реализация. Согласование с системной средой, разработка аппаратной платформы.

Шаг объект-действие

 

Начинается с определения проблемы на естественном языке.

Пример:

Разработать компьютерную систему для обслуживания университетских перевозок. Университет размещается на двух территориях. Для перемещения студентов используется один транспорт. Он перемещается между двумя фиксированными остановками. На каждой остановке имеется кнопка вызова.

При нажатии кнопки:

q если транспорт на остановке, то студенты заходят в него и перемещаются на другую остановку;

q если транспорт в пути, то студенты ждут прибытия на другую остановку, приема студентов и возврата на текущую остановку;

q если транспорт на другой остановке, то он ее покидает, прибывает на текущую остановку и принимает студентов, нажавших кнопку.

Транспорт должен стоять на остановке до появления запроса на обслуживание.

Описание исследуется для выделения объектов. Производится грамматический разбор. Возможны следующие кандидаты в объекты: территория, студенты, транспорт, остановка, кнопка. У нас нет нужды прямо использовать территорию, студентов, остановку — все они лежат вне области модели и отвергаются как возможные объекты. Таким образом, мы выбираем объекты транспорт и кнопка.

Для выделения действий исследуются все глаголы описания.

Кандидатами действий являются: перемещаться, прибывает, нажимать, принимать, покидать. Мы отвергаем перемещаться, принимать потому, что они относятся к студентам, а студенты не выделены как объект. Мы выбираем действия: прибывает, нажимать, покидать.

Заметим, что при выделении объектов и действий возможны ошибки. Например, отвергнув студентов, мы лишились возможности исследовать загрузку транспорта. Впрочем, список объектов и действий может модифицироваться в ходе дальнейшего анализа.


 

Шаг объект-структура

 

Структура объектов описывает последовательность действий над объектами (в условном времени).

Для представления структуры объектов Джексон предложил 3 типа структурных диаграмм. Они показаны на рис. 3.13. В первой диаграмме к объектам применяется такое действие, как последовательность, во второй — выбор, в третьей — повторение.

Рассмотрим объектную структуру для транспорта (см. рис. 3.14). Условимся, что начало и конец истории транспорта — у первой остановки. Действиями, влияющими на объект, являются Покинуть и Прибыть.

 

Рис. 3.13. Три типа структурных диаграмм Джексона

 

 

 

Рис. 3.14. Объектная структура для транспорта

 

Диаграмма показывает, что транспорт начинает работу у остановки 1, тратит основное время на перемещение между остановками 1 и 2 и окончательно возвращается на остановку 1. Прибытие на остановку, следующее за отъездом с другой остановки, представляется как пара действий Прибыть(i) и Покинуть(i). Заметим, что диаграмму можно сопровождать комментариями, которые не могут прямо представляться средствами метода. Например, «значение г в двух последовательных остановках должно быть разным».

Структурная диаграмма для объекта Кнопка показывает (рис. 3.15), что к нему многократно применяется действие Нажать.

 

Рис. 3.15. Структурная диаграмма для объекта Кнопка

 

В заключение заметим, что структурная диаграмма — время-ориентированное описание действий, выполняемых над объектом. Она создается для каждого объекта модели.


 

Шаг начального моделирования

 

Начальное моделирование — это шаг к созданию описания системы как модели реального мира. Описание создается с помощью диаграммы системной спецификации.

Элементами диаграммы системной спецификации являются физические процессы (имеют суффикс 0) и их модели (имеют суффикс 1). Как показано на рис. 3.16, предусматриваются 2 вида соединений между физическими процессами и моделями.

 

Рис. 3.16. Соединения между физическими процессами и их моделями

 

Соединение потоком данных производится, когда физический процесс передает, а модель принимает информационный поток. Полагают, что поток передается через буфер неограниченной емкости типа FIFO (обозначается овалом).

Соединение по вектору состояний происходит, когда модель наблюдает вектор состояния физического процесса. Вектор состояния обозначается ромбиком.

Диаграмма системной спецификации для системы обслуживания перевозок приведена на рис. 3.17.

 

ПРИМЕЧАНИЕ

При нажатии кнопки формируется импульс, который может быть передан в модель как элемент данных, поэтому для кнопки выбрано соединение потоком данных.

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

 

Рис. 3.17. Диаграмма системной спецификации для системы обслуживания перевозок

 

Для фиксации особенностей процессов-моделей Джексон предлагает специальное описание — структурный текст. Например, структурный текст для модели КНОПКА-1 имеет вид

КНОПКА-1

читать BD;

НАЖАТЬ цикл ПОКА BD

нажать;

читать ВD;

конец НАЖАТЬ;

конец КНОПКА-1;

Структура модели КНОПКА-1 отличается от структуры физического процесса КНОПКА-0 добавлением оператора для чтения буфера ВD, который соединяет физический мир с моделью.

Прежде чем написать структурный текст для модели ТРАНСПОРТ-1, мы должны сделать ряд замечаний.

Во-первых, состояние транспорта будем отслеживать по переменным ПРИБЫЛ, УБЫЛ. Они отражают состояние электронного переключателя физического транспорта.

Во-вторых, для учета инерционности процессов в физическом транспорте в модель придется ввести дополнительные операции:

q ЖДАТЬ (ожидание в изменении состояния физического транспорта);

q ТРАНЗИТ (операция задержки в модели на перемещение транспорта между остановками).

С учетом замечаний структурная диаграмма модели примет вид, изображенный на рис. 3.18.

Соответственно, структурный текст модели записывается в форме

ТРАНСПОРТ-1

опрос TSV;

ЖДАТЬ цикл ПОКА ПРИБЫЛ(1)

опрос TSV;

конец ЖДАТЬ;

покинуть(1);

ТРАНЗИТ цикл ПОКА УБЫЛ(1)

опрос TSV;

конец ТРАНЗИТ;

ТРАНСПОРТ цикл

ОСТАНОВКА

прибыть(i);

ЖДАТЬ цикл ПОКА ПРИБЫЛ(i)

опрос TSV;

конец ЖДАТЬ;

покинуть(i);

ТРАНЗИТ цикл ПОКА УБЫЛ(i)

опрос TSV;

конец ТРАНЗИТ;

конец ОСТАНОВКА;

конец ТРАНСПОРТ;

прибыть(1);

конец ТРАНСПОРТ-1;

 



 

Контрольные вопросы

 

1. Какие задачи решает аппарат анализа?

2. Что такое диаграмма потоков данных?

3. Чем отличается диаграмма потоков данных от блок-схемы алгоритма?

4. Какие элементы диаграммы потоков данных вы знаете?

5. Как формируется иерархия диаграмм потоков данных?

6. Какую задачу решает диаграмма потоков данных высшего (нулевого) уровня? Почему ее называют контекстной моделью?

7. Чем нагружены вершины диаграммы потоков данных?

8. Чем нагружены дуги диаграммы потоков данных?

9. Как организован словарь требований?

10. С чем связана необходимость расширения диаграмм потоков данных для систем реального времени? Какие средства расширения вы знаете?

11. Как решается проблема расширения возможностей управления на базе диаграмм потоков данных?

12. Каковы особенности диаграммы управляющих потоков?

13. Поясните понятие активатора процесса.

14. Поясните понятие условия данных.

15. Поясните понятие управляющей спецификации.

16. Поясните понятие окна управляющей спецификации.

17. Как организована спецификация процесса?

18. Поясните назначение таблицы активации процессов.

19. Поясните организацию диаграммы переходов-состояний.

20. Какие задачи решают методы анализа, ориентированные на структуры данных?

21. Какие методы анализа, ориентированные на структуры данных, вы знаете?

22. Из каких базовых элементов состоят диаграммы Варнье?

23. Какие шаги выполняет метод Джексона на этапе анализа?

24. Какие типы структурных диаграмм Джексона вы знаете?

25. Как организовано в методе Джексона обнаружение объектов?

26. Что такое структура объектов Джексона?

27. Как создается структура объектов Джексона?

28. Поясните диаграмму системной спецификации Джексона.

29. Чем отличается соединение потоком данных от соединения по вектору состояний?

30. Какова задача структурного текста Джексона?


 

ГЛАВА 4. Основы проектирования программных систем

 

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

Особенности процесса синтеза программных систем

 

Известно, что технологический цикл конструирования программной системы (ПС) включает три процесса — анализ, синтез и сопровождение.

В ходе анализа ищется ответ на вопрос: «Что должна делать будущая система?». Именно на этой стадии закладывается фундамент успеха всего проекта. Известно множество неудачных реализаций из-за неполноты и неточностей в определении требований к системе.

В процессе синтеза формируется ответ на вопрос: «Каким образом система будет реализовывать предъявляемые к ней требования?». Выделяют три этапа синтеза: проектирование ПС, кодирование ПС, тестирование ПС (рис. 4.1).

Рассмотрим информационные потоки процесса синтеза.

Этап проектирования питают требования к ПС, представленные информационной, функциональной и поведенческой моделями анализа. Иными словами, модели анализа поставляют этапу проектирования исходные сведения для работы. Информационная модель описывает информацию, которую, по мнению заказчика, должна обрабатывать ПС. Функциональная модель определяет перечень функций обработки. Поведенческая модель фиксирует желаемую динамику системы (режимы ее работы). На выходе этапа проектирования — разработка данных, разработка архитектуры и процедурная разработка ПС.

Разработка данных — это результат преобразования информационной модели анализа в структуры данных, которые потребуются для реализации программной системы.

 

 

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

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

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

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


Особенности этапа проектирования

 

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

Обычно в проектировании выделяют две ступени: предварительное проектирование и детальное проектирование. Предварительное проектирование формирует абстракции архитектурного уровня, детальное проектирование уточняет эти абстракции, добавляет подробности алгоритмического уровня. Кроме того, во многих случаях выделяют интерфейсное проектирование, цель которого — сформировать графический интерфейс пользователя (GUI). Схема информационных связей процесса проектирования приведена на рис. 4.2.


 

Предварительное проектирование обеспечивает:

q идентификацию подсистем;

q определение основных принципов управления подсистемами, взаимодействия подсистем.

Предварительное проектирование включает три типа деятельности:

1. Структурирование системы. Система структурируется на несколько подсистем, где под подсистемой понимается независимый программный компонент. Определяются взаимодействия подсистем.

2. Моделирование управления. Определяется модель связей управления между частями системы.

3. Декомпозиция подсистем на модули. Каждая подсистема разбивается на модули. Определяются типы модулей и межмодульные соединения.

Рассмотрим вопросы структурирования, моделирования и декомпозиции более подробно.


Структурирование системы

 

Известны четыре модели системного структурирования:

q модель хранилища данных;

q модель клиент-сервер;

q трехуровневая модель;

q модель абстрактной машины.

В модели хранилища данных (рис. 4.3) подсистемы разделяют данные, находящиеся в общей памяти. Как правило, данные образуют БД. Предусматривается система управления этой базой.

 

Рис. 4.3. Модель хранилища данных

Модель клиент-сервер используется для распределенных систем, где данные распределены по серверам (рис. 4.4). Для передачи данных применяют сетевой протокол, например TCP/IP.

 

Рис. 4.4. Модель клиент-сервер

 

Трехуровневая модель является развитием модели клиент-сервер (рис. 4.5).

 

Рис. 4.5. Трехуровневая модель

 

Уровень графического интерфейса пользователя запускается на машине клиента. Бизнес-логику образуют модули, осуществляющие функциональные обязанности системы. Этот уровень запускается на сервере приложения. Реляционная СУБД хранит данные, требуемые уровню бизнес-логики. Этот уровень запускается на втором сервере — сервере базы данных.

Преимущества трехуровневой модели:

q упрощается такая модификация уровня, которая не влияет на другие уровни;

q отделение прикладных функций от функций управления БД упрощает оптимизацию всей системы.

Модель абстрактной машины отображает многослойную систему (рис. 4.6).

Каждый текущий слой реализуется с использованием средств, обеспечиваемых слоем-фундаментом.

 

Рис. 4.6. Модель абстрактной машины


Моделирование управления

 

Известны два типа моделей управления:

q модель централизованного управления;

q модель событийного управления.

В модели централизованного управления одна подсистема выделяется как системный контроллер. Ее обязанности — руководить работой других подсистем. Различают две разновидности моделей централизованного управления: модель вызов-возврат (рис. 4.7) и Модель менеджера (рис. 4.8), которая используется в системах параллельной обработки.

 

Рис. 4.7. Модель вызов-возврат

 

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

 

Рис. 4.8. Модель менеджера

 

В широковещательной модели (рис. 4.9) каждая подсистема уведомляет обработчика о своем интересе к конкретным событиям. Когда событие происходит, обработчик пересылает его подсистеме, которая может обработать это событие. Функции управления в обработчик не встраиваются.

 

Рис. 4.9. Широковещательная модель

 

 

Рис. 4.10. Модель, управляемая прерываниями

 

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


Декомпозиция подсистем на модули

 

Известны два типа моделей модульной декомпозиции:

q модель потока данных;

q модель объектов.

В основе модели потока данных лежит разбиение по функциям.

Модель объектов основана на слабо сцепленных сущностях, имеющих собственные наборы данных, состояния и наборы операций.

Очевидно, что выбор типа декомпозиции должен определяться сложностью разбиваемой подсистемы.

Модульность

 

Модуль — фрагмент программного текста, являющийся строительным блоком для физической структуры системы. Как правило, модуль состоит из интерфейсной части и части-реализации.

Модульность — свойство системы, которая может подвергаться декомпозиции на ряд внутренне связанных и слабо зависящих друг от друга модулей.

По определению Г. Майерса, модульность — свойство ПО, обеспечивающее интеллектуальную возможность создания сколь угодно сложной программы [52]. Проиллюстрируем эту точку зрения.

Пусть С(х) — функция сложности решения проблемы х, Т(х) — функция затрат времени на решение проблемы х. Для двух проблем р1 и р2 из соотношения С(р1) > С(р2) следует, что

T(pl)>T(p2). (4.1)

Этот вывод интуитивно ясен: решение сложной проблемы требует большего времени.

Далее. Из практики решения проблем человеком следует:

С(р1+ р2)>С(р1) + С(р2).

Отсюда с учетом соотношения (4.1) запишем:

T(pl+p2)>T(pl) + T(p2). (4.2)

Соотношение (4.2) — это обоснование модульности. Оно приводит к заключению «разделяй и властвуй» — сложную проблему легче решить, разделив ее на управляемые части. Результат, выраженный неравенством (4.2), имеет важное значение для модульности и ПО. Фактически, это аргумент в пользу модульности.

Однако здесь отражена лишь часть реальности, ведь здесь не учитываются затраты на межмодульный интерфейс. Как показано на рис. 4.11, с увеличением количества модулей (и уменьшением их размера) эти затраты также растут.

 

Рис. 4.11. Затраты на модульность

 

Таким образом, существует оптимальное количество модулей Opt, которое приводит к минимальной стоимости разработки. Увы, у нас нет необходимого опыта для гарантированного предсказания Opt. Впрочем, разработчики знают, что оптимальный модуль должен удовлетворять двум критериям:

q снаружи он проще, чем внутри;

q его проще использовать, чем построить.

Информационная закрытость

 

Принцип информационной закрытости (автор — Д. Парнас, 1972) утверждает: содержание модулей должно быть скрыто друг от друга [60]. Как показано на рис. 4.12, модуль должен определяться и проектироваться так, чтобы его содержимое (процедуры и данные) было недоступно тем модулям, которые не нуждаются в такой информации (клиентам).

 

Рис. 4.12. Информационная закрытость модуля

 

Информационная закрытость означает следующее:

1) все модули независимы, обмениваются только информацией, необходимой для работы;

2) доступ к операциям и структурам данных модуля ограничен.

Достоинства информационной закрытости:

q обеспечивается возможность разработки модулей различными, независимыми коллективами;

q обеспечивается легкая модификация системы (вероятность распространения ошибок очень мала, так как большинство данных и процедур скрыто от других частей системы).

Идеальный модуль играет роль «черного ящика», содержимое которого невидимо клиентам. Он прост в использовании — количество «ручек и органов управления» им невелико (аналогия с эксплуатацией телевизора). Его легко развивать и корректировать в процессе сопровождения программной системы. Для обеспечения таких возможностей система внутренних и внешних связей модуля должна отвечать особым требованиям. Обсудим характеристики внутренних и внешних связей модуля.


Связность модуля

 

Связность модуля (Cohesion) — это мера зависимости его частей [58], [70], [77]. Связность — внутренняя характеристика модуля. Чем выше связность модуля, тем лучше результат проектирования, то есть тем «черней» его ящик (капсула, защитная оболочка модуля), тем меньше «ручек управления» на нем находится и тем проще эти «ручки».

Для измерения связности используют понятие силы связности (СС). Существует 7 типов связности:

1. Связность по совпадению (СС=0). В модуле отсутствуют явно выраженные внутренние связи.

2. Логическая связность (СС=1). Части модуля объединены по принципу функционального подобия. Например, модуль состоит из разных подпрограмм обработки ошибок. При использовании такого модуля клиент выбирает только одну из подпрограмм.

Недостатки:

q сложное сопряжение;

q большая вероятность внесения ошибок при изменении сопряжения ради одной из функций.

3. Временная связность (СС=3). Части модуля не связаны, но необходимы в один и тот же период работы системы.

Недостаток: сильная взаимная связь с другими модулями, отсюда — сильная чувствительность внесению изменений.

4. Процедурная связность (СС=5). Части модуля связаны порядком выполняемых ими действий, реализующих некоторый сценарий поведения.

5. Коммуникативная связность (СС=7). Части модуля связаны по данным (работают с одной и той же структурой данных).

6. Информационная (последовательная) связность (СС=9). Выходные данные одной части используются как входные данные в другой части модуля.

7. Функциональная связность (СС=10). Части модуля вместе реализуют одну функцию.

Отметим, что типы связности 1,2,3 — результат неправильного планирования архитектуры, а тип связности 4 — результат небрежного планирования архитектуры приложения.

Общая характеристика типов связности представлена в табл. 4.1.

 

Таблица 4.1. Характеристика связности модуля

Тип связности

Сопровождаемость

Роль модуля

Функциональная

 

«Черный ящик»

Информационная

( последовательная )

Лучшая сопровождаемость

Не совсем «черный ящик»

Кэммуникативная

 

«Серый ящик»

Процедурная

 

«Белый» или «просвечивающий ящик»

Временная

Худшая сопровождаемость

 

Логическая

 

«Белый ящик»

По совпадению

 

 


Функциональная связность

 

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

q Вычислять синус угла;

q Проверять орфографию;

q Читать запись файла;

q Вычислять координаты цели;

q Вычислять зарплату сотрудника;

q Определять место пассажира.

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

Некоторые из функционально связных модулей очень просты (например, Вычислять синус угла или Читать запись файла), другие сложны (например, Вычислять координаты цели). Модуль Вычислять синус угла, очевидно, реализует единичную функцию, но как может модуль Вычислять зарплату сотрудника выполнять только одно действие? Ведь каждый знает, что приходится определять начисленную сумму, вычеты по рассрочкам, подоходный налог, социальный налог, алименты и т. д.! Дело в том, что, несмотря на сложность модуля и на то, что его обязанность исполняют несколько подфункций, если его действия можно представить как единую проблемную функцию (с точки зрения клиента), тогда считают, что модуль функционально связен.

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

Информационная связность

 

При информационной (последовательной) связности элементы-обработчики модуля образуют конвейер для обработки данных — результаты одного обработчика используются как исходные данные для следующего обработчика. Приведем пример:

Модуль Прием и проверка записи

прочитать запись из файла

проверить контрольные данные в записи

удалить контрольные поля в записи

вернуть обработанную запись

Конец модуля

В этом модуле 3 элемента. Результаты первого элемента (прочитать запись из файла) используются как входные данные для второго элемента (проверить контрольные данные в записи) и т. д.

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


Коммуникативная связность

 

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

Модуль Отчет и средняя зарплата

используется Таблица зарплаты служащих

сгенерировать Отчет по зарплате

вычислить параметр Средняя зарплата

вернуть Отчет по зарплате. Средняя зарплата

Конец модуля

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

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

Попытаемся провести аналогию между информационной и коммуникативной связностью.

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

Процедурная связность

 

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

Модуль Вычисление средних значений

используется Таблица-А. Таблица-В

вычислить среднее по Таблица-А

вычислить среднее по Таблица-В

вернуть среднееТабл-А. среднееТабл-В

Конец модуля

Этот модуль вычисляет средние значения для двух полностью несвязанных таблиц Таблица-А и Таблица-В, каждая из которых имеет по 300 элементов.

Теперь представим себе программиста, которому поручили реализовать данный модуль. Соблазнившись возможностью минимизации кода (использовать один цикл в интересах двух обработчиков, ведь они находятся внутри единого модуля!), программист пишет:

Модуль Вычисление средних значений

используется Таблица-А. Таблица-В

суммаТабл-А := 0

суммаТабл-В := 0

для i := 1 до 300

суммаТабл-А := суммаТабл-А + Таблица-А(i)

суммаТабл-В :- суммаТабл-В + Таблица-В(i)

конец для

среднееТабл-А := суммаТабл-А / 300

среднееТабл-В := суммаТабл-В / 300

вернуть среднееТабл-А, среднееТабл-В

Конец модуля

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


Временная связность

 

При связности по времени элементы-обработчики модуля привязаны к конкретному периоду времени (из жизни программной системы).

Классическим примером временной связности является модуль инициализации:

Модуль Инициализировать Систему

перемотать магнитную ленту 1

Счетчик магнитной ленты 1 := 0

перемотать магнитную ленту 2

Счетчик магнитной ленты 2 := 0

Таблица текущих записей : = пробел..пробел

Таблица количества записей := 0..0

Переключатель 1 : = выкл

Переключатель 2 := вкл

Конец модуля

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

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

Так, при желании инициализировать магнитную ленту 2 в другое время вы столкнетесь с неудобствами. Чтобы не сбрасывать всю систему, придется или ввести флажки, указывающие инициализируемую часть, или написать другой код для работы с лентой 2. Оба решения ухудшают сопровождаемость.

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

Логическая связность

 

Элементы логически связного модуля принадлежат к действиям одной категории, и из этой категории клиент выбирает выполняемое действие. Рассмотрим следующий пример:

Модуль Пересылка сообщения

переслать по электронной почте

переслать по факсу

послать в телеконференцию

переслать по ftp-протоколу

Конец модуля

Как видим, логически связный модуль — мешок доступных действий. Действия вынуждены совместно использовать один и тот же интерфейс модуля. В строке вызова модуля значение каждого параметра зависит от используемого действия. При вызове отдельных действий некоторые параметры должны иметь значение пробела, нулевые значения и т. д. (хотя клиент все же должен использовать их и знать их типы).

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

q уродливый внешний вид с различными параметрами, обеспечивающими, например, четыре вида доступа;

q запутанную внутреннюю структуру со множеством переходов, похожую на волшебный лабиринт.

В итоге модуль становится сложным как для понимания, так и для сопровождения.


Связность по совпадению

 

Элементы связного по совпадению модуля вообще не имеют никаких отношений друг с другом:

Модуль Разные функции (какие-то параметры)

поздравить с Новым годом (...)

проверить исправность аппаратуры (...)

заполнить анкету героя (...)

измерить температуру (...)

вывести собаку на прогулку (...)

запастись продуктами (...)

приобрести «ягуар» (...)

Конец модуля

Связный по совпадению модуль похож на логически связный модуль. Его элементы-действия не связаны ни потоком данных, ни потоком управления. Но в логически связном модуле действия, по крайней мере, относятся к одной категории; в связном по совпадению модуле даже это не так. Словом, связные по совпадению модули имеют все недостатки логически связных модулей и даже усиливают их. Применение таких модулей вселяет ужас, поскольку один параметр используется для разных целей.

Чтобы клиент мог воспользоваться модулем Разные функции, этот модуль (подобно всем связным по совпадению модулям) должен быть «белым ящиком», чья реализация полностью видима. Такие модули делают системы менее понятными и труднее сопровождаемыми, чем системы без модульности вообще!

К счастью, связность по совпадению встречается редко. Среди ее причин можно назвать:

q бездумный перевод существующего монолитного кода в модули;

q необоснованные изменения модулей с плохой (обычно временной) связностью, приводящие к добавлению флажков.

Определение связности модуля

 

Приведем алгоритм определения уровня связности модуля.

1. Если модуль — единичная проблемно-ориентированная функция, то уровень связности — функциональный; конец алгоритма. В противном случае перейти к пункту 2.

2. Если действия внутри модуля связаны, то перейти к пункту 3. Если действия внутри модуля никак не связаны, то перейти к пункту 6.

3. Если действия внутри модуля связаны данными, то перейти к пункту 4. Если действия внутри модуля связаны потоком управления, перейти к пункту 5.

4. Если порядок действий внутри модуля важен, то уровень связности — информационный. В противном случае уровень связности — коммуникативный. Конец алгоритма.

5. Если порядок действий внутри модуля важен, то уровень связности — процедурный. В противном случае уровень связности — временной. Конец алгоритма.

6. Если действия внутри модуля принадлежат к одной категории, то уровень связности — логический. Если действия внутри модуля не принадлежат к одной категории, то уровень связности — по совпадению. Конец алгоритма.

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

q правило параллельной цепи. Если все действия модуля имеют несколько уровней связности, то модулю присваивают самый сильный уровень связности;

q правило последовательной цепи. Если действия в модуле имеют разные уровни связности, то модулю присваивают самый слабый уровень связности.

Например, модуль может содержать некоторые действия, которые связаны процедурно, а также другие действия, связные по совпадению. В этом случае применяют правило последовательной цепи и в целом модуль считают связным по совпадению.


Сцепление модулей

 

Сцепление (Coupling) — мера взаимозависимости модулей поданным [58], [70], [77]. Сцепление — внешняя характеристика модуля, которую желательно уменьшать.

Количественно сцепление измеряется степенью сцепления (СЦ). Выделяют 6 типов сцепления.

1. Сцепление по данным (СЦ=1). Модуль А вызывает модуль В.

Все входные и выходные параметры вызываемого модуля — простые элементы данных (рис. 4.13).

 

Рис. 4.13. Сцепление поданным

 

2. Сцепление по образцу (СЦ=3). В качестве параметров используются структуры данных (рис. 4.14).

 

Рис. 4.14. Сцепление по образцу

3. Сцепление по управлению (СЦ=4). Модуль А явно управляет функционированием модуля В (с помощью флагов или переключателей), посылая ему управляющие данные (рис. 4.15).

 

Рис. 4.15. Сцепление по управлению

4. Сцепление по внешним ссылкам (СЦ=5). Модули А и В ссылаются на один и тот же глобальный элемент данных.

5. Сцепление по общей области (СЦ=7). Модули разделяют одну и ту же глобальную структуру данных (рис. 4.16).

6. Сцепление по содержанию (СЦ=9). Один модуль прямо ссылается на содержание другого модуля (не через его точку входа). Например, коды их команд перемежаются друг с другом (рис. 4.16).

 

Рис. 4.16. Сцепление по общей области и содержанию

 

На рис. 4.16 видим, что модули В и D сцеплены по содержанию, а модули С, Е и N сцеплены по общей области.

Сложность программной системы

 

В простейшем случае сложность системы определяется как сумма мер сложности ее модулей. Сложность модуля может вычисляться различными способами.

Например, М. Холстед (1977) предложил меру длины N модуля [33]:

N » n1log2 (n1) + n2log2(n2),

где n1 — число различных операторов, п2 число различных операндов.

В качестве второй метрики М. Холстед рассматривал объем V модуля (количество символов для записи всех операторов и операндов текста программы):

V = N x log2 (n1 + n2).

Вместе с тем известно, что любая сложная система состоит из элементов и системы связей между элементами и что игнорировать внутрисистемные связи неразумно.

Том МакКейб (1976) при оценке сложности ПС предложил исходить из топологии внутренних связей [49]. Для этой цели он разработал метрику цикломатической сложности:

V(G) = E-N + 2,

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

Таким образом, при комплексной оценке сложности ПС необходимо рассматривать меру сложности модулей, меру сложности внешних связей (между модулями) и меру сложности внутренних связей (внутри модулей) [28], [56]. Традиционно со внешними связями сопоставляют характеристику «сцепление», а с внутренними связями — характеристику «связность».

Вопросы комплексной оценки сложности обсудим в следующем разделе.

Характеристики иерархической структуры программной системы

 

Иерархическая структура программной системы — основной результат предварительного проектирования. Она определяет состав модулей ПС и управляющие отношения между модулями. В этой структуре модуль более высокого уровня (начальник) управляет модулем нижнего уровня (подчиненным).

Иерархическая структура не отражает процедурные особенности программной системы, то есть последовательность операций, их повторение, ветвления и т. д. Рассмотрим основные характеристики иерархической структуры, представленной на рис. 4.17.

 

Рис. 4.17. Иерархическая структура программной системы

 

Первичными характеристиками являются количество вершин (модулей) и количество ребер (связей между модулями). К ним добавляются две глобальные характеристики — высота и ширина:

q высота — количество уровней управления;

q ширина — максимальное из количеств модулей, размещенных на уровнях управления.

В нашем примере высота = 4, ширина = 6.

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

Коэффициент объединения по входу Fan_in(i) — это количество модулей, которые прямо управляют i-м модулем.

В примере для модуля n: Fan_in(n)=4.

Коэффициент разветвления по выходу Fan_out(i) — это количество модулей, которыми прямо управляет i-й модуль.

В примере для модуля m: Fan_out(m)=3.

Возникает вопрос: как оценить качество структуры? Из практики проектирования известно, что лучшее решение обеспечивается иерархической структурой в виде дерева.

Степень отличия реальной проектной структуры от дерева характеризуется невязкой структуры. Как определить невязку?

Вспомним, что полный граф (complete graph) с п вершинами имеет количество ребер

ес=n(n-1)/2,

а дерево (tree) с таким же количеством вершин — существенно меньшее количество ребер

et=n-l.

Тогда формулу невязки можно построить, сравнивая количество ребер полного графа, реального графа и дерева.

Для проектной структуры с п вершинами и е ребрами невязка определяется по выражению

.

Значение невязки лежит в диапазоне от 0 до 1. Если Nev = 0, то проектная структура является деревом, если Nev = 1, то проектная структура — полный граф.

Ясно, что невязка дает грубую оценку структуры. Для увеличения точности оценки следует применить характеристики связности и сцепления.

Хорошая структура должна иметь низкое сцепление и высокую связность.

Л. Констентайн и Э. Йордан (1979) предложили оценивать структуру с помощью коэффициентов Fan_in(i) и Fan_out(i) модулей [77].

Большое значение Fan_in(i) — свидетельство высокого сцепления, так как является мерой зависимости модуля. Большое значение Fan_out(i) говорит о высокой сложности вызывающего модуля. Причиной является то, что для координации подчиненных модулей требуется сложная логика управления.

Основной недостаток коэффициентов Fan_in(i) и Fan_out(i) состоит в игнорировании веса связи. Здесь рассматриваются только управляющие потоки (вызовы модулей). В то же время информационные потоки, нагружающие ребра структуры, могут существенно изменяться, поэтому нужна мера, которая учитывает не только количество ребер, но и количество информации, проходящей через них.

С. Генри и Д. Кафура (1981) ввели информационные коэффициенты ifan_in(i) и ifan_out(j) [35]. Они учитывают количество элементов и структур данных, из которых i-й модуль берет информацию и которые обновляются j-м модулем соответственно.

Информационные коэффициенты суммируются со структурными коэффициентами sfan_in(i) и sfan_out( j), которые учитывают только вызовы модулей.

В результате формируются полные значения коэффициентов:

Fan_in (i) = sfan_in (i) + ifan_in (i),

Fan_out (j) = sfan_out (j) + ifan_out (j).

На основе полных коэффициентов модулей вычисляется метрика общей сложности структуры:

S = length(i) x (Fan_in(i) + Fan_out(i))2,

где length(i) — оценка размера i-го модуля (в виде LOC- или FP-оценки).


Контрольные вопросы

 

1. Какова цель синтеза программной системы? Перечислите этапы синтеза.

2. Дайте определение разработки данных, разработки архитектуры и процедурной разработки.

3. Какие особенности имеет этап проектирования?

4. Решение каких задач обеспечивает предварительное проектирование?

5. Какие модели системного структурирования вы знаете?

6. Чем отличается модель клиент-сервер от трехуровневой модели?

7. Какие типы моделей управления вы знаете?

8. Какие существуют разновидности моделей централизованного управления?

9. Поясните разновидности моделей событийного управления.

10. Поясните понятия модуля и модульности. Зачем используют модули?

11. В чем состоит принцип информационной закрытости? Какие достоинства он имеет?

12. Что такое связность модуля?

13. Какие существуют типы связности?

14. Дайте характеристику функциональной связности.

15. Дайте характеристику информационной связности.

16. Охарактеризуйте коммуникативную связность.

17. Охарактеризуйте процедурную связность.

18. Дайте характеристику временной связности.

19. Дайте характеристику логической связности.

20. Охарактеризуйте связность по совпадению.

21. Что значит «улучшать связность» ?

22. Что такое сцепление модуля?

23. Какие существуют типы сцепления?

24. Дайте характеристику сцепления по данным.

25. Дайте характеристику сцепления по образцу.

26. Охарактеризуйте сцепление по управлению.

27. Охарактеризуйте сцепление по внешним ссылкам.

28. Дайте характеристику сцепления по общей области.

29. Дайте характеристику сцепления по содержанию.

30. Что значит «улучшать сцепление»?

31. Какие подходы к оценке сложности системы вы знаете?

32. Что определяет иерархическая структура программной системы?

33. Поясните первичные характеристики иерархической структуры.

34. Поясните понятия коэффициента объединения по входу и коэффициента раз ветвления по выходу.

35. Что определяет невязка структуры?

36. Поясните информационные коэффициенты объединения и разветвления.


 

ГЛАВА 5. Классические методы проектирования

 

В этой главе рассматриваются классические методы проектирования, ориентированные на процедурную реализацию программных систем (ПС). Повторим, что эти методы появились в период революции структурного программирования. Учитывая, что на современном этапе программной инженерии процедурно-ориентированные ПС имеют преимущественно историческое значение, конспективно обсуждаются только два (наиболее популярных) метода: метод структурного проектирования и метод проектирования Майкла Джексона (этот Джексон не имеет никакого отношения к известному певцу). Зачем мы это делаем? Да чтобы знать исторические корни современных методов проектирования.

Метод структурного проектирования

 

Исходными данными для метода структурного проектирования являются компоненты модели анализа ПС, которая представляется иерархией диаграмм потоков данных [34], [52], [58], [73], [77]. Результат структурного проектирования — иерархическая структура ПС. Действия структурного проектирования зависят от типа информационного потока в модели анализа.

Типы информационных потоков

 

Различают 2 типа информационных потоков:

1) поток преобразований;

2) поток запросов.

Как показано на рис. 5.1, в потоке преобразований выделяют 3 элемента: Входящий поток, Преобразуемый поток и Выходящий поток.

Потоки запросов имеют в своем составе особые элементы — запросы.

Назначение элемента-запроса состоит в том, чтобы запустить поток данных по одному из нескольких путей. Анализ запроса и переключение потока данных на один из путей действий происходит в центре запросов.

Структуру потока запроса иллюстрирует рис. 5.2.

 

Рис. 5.1. Элементы потока преобразований

 

Рис. 5.2. Структура потока запроса


 

Проектирование для потока данных типа «преобразование»

Шаг 1. Проверка основной системной модели. Модель включает: контекстную диаграмму ПДД0, словарь данных и спецификации процессов. Оценивается их согласованность с системной спецификацией.

Шаг 2. Проверки и уточнения диаграмм потоков данных уровней 1 и 2. Оценивается согласованность диаграмм, достаточность детализации преобразователей.

Шаг 3. Определение типа основного потока диаграммы потоков данных. Основной признак потока преобразований — отсутствие переключения по путям действий.

Шаг 4. Определение границ входящего и выходящего потоков, отделение центра преобразований. Входящий поток — отрезок, на котором информация преобразуется из внешнего во внутренний формат представления. Выходящий поток обеспечивает обратное преобразование — из внутреннего формата во внешний. Границы входящего и выходящего потоков достаточно условны. Вариация одного преобразователя на границе слабо влияет на конечную структуру ПС.

Шаг 5. Определение начальной структуры ПС. Иерархическая структура ПС формируется нисходящим распространением управления. В иерархической структуре:

q модули верхнего уровня принимают решения;

q модули нижнего уровня выполняют работу по вводу, обработке и выводу;

q модули среднего уровня реализуют как функции управления, так и функции обработки.

Начальная структура ПС (для потока преобразования) стандартна и включает главный контроллер (находится на вершине структуры) и три подчиненных контроллера:

1. Контроллер входящего потока (контролирует получение входных данных).

2. Контроллер преобразуемого потока (управляет операциями над данными во внутреннем формате).

3. Контроллер выходящего потока (управляет получением выходных данных).

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

Начальная структура ПС представлена на рис. 5.3.

Диаграмма потоков данных ПДД

 

Рис. 5.3. Начальная структура ПС для потока «преобразование»

Шаг 6. Детализация структуры ПС. Выполняется отображение преобразователей ПДД в модули структуры ПС. Отображение выполняется движением по ПДД от границ центра преобразования вдоль входящего и выходящего потоков. Входящий поток проходится от конца к началу, а выходящий поток — от начала к концу. В ходе движения преобразователи отображаются в модули подчиненных уровней структуры (рис. 5.4).

Центр преобразования ПДД отображается иначе (рис. 5.5). Каждый преобразователь отображается в модуль, непосредственно подчиненный контроллеру центра.

Проходится преобразуемый поток слева направо.

Возможны следующие варианты отображения:

q 1 преобразователь отображается в 1 модуль;

q 2-3 преобразователя отображаются в 1 модуль;

q 1 преобразователь отображается в 2-3 модуля.

 

Рис. 5.4. Отображение преобразователей ПДД в модули структуры

 

 

Рис. 5.5. Отображение центра преобразования ПДД

 

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

Шаг 7. Уточнение иерархической структуры ПС. Модули разделяются и объединяются для:

1) повышения связности и уменьшения сцепления;

2) упрощения реализации;

3) упрощения тестирования;

4) повышения удобства сопровождения.


 

Проектирование для потока данных типа «запрос»

Шаг 1. Проверка основной системной модели. Модель включает: контекстную диаграмму ПДДО, словарь данных и спецификации процессов. Оценивается их согласованность с системной спецификацией.

Шаг 2. Проверки и уточнения диаграмм потоков данных уровней 1 и 2. Оценивается согласованность диаграмм, достаточность детализации преобразователей.

Шаг 3. Определение типа основного потока диаграммы потоков данных. Основной признак потоков запросов — явное переключение данных на один из путей действий.

Шаг 4. Определение центра запросов и типа для каждого из потоков действия. Если конкретный поток действия имеет тип «преобразование», то для него указываются границы входящего, преобразуемого и выходящего потоков.

Шаг 5. Определение начальной структуры ПС. В начальную структуру отображается та часть диаграммы потоков данных, в которой распространяется поток запросов. Начальная структура ПС для потока запросов стандартна и включает входящую ветвь и диспетчерскую ветвь.

Структура входящей ветви формируется так же, как и в предыдущей методике.

Диспетчерская ветвь включает диспетчер, находящийся на вершине ветви, и контроллеры потоков действия, подчиненные диспетчеру; их должно быть столько, сколько имеется потоков действий

 

Рис. 5.6. Отображение в модульную структуру ПС потока действия 1

Шаг 6. Детализация структуры ПС. Производится отображение в структуру каждого потока действия. Каждый поток действия имеет свой тип. Могут встретиться поток-«преобразование» (отображается по предыдущей методике) и поток запросов. На рис. 5.6 приведен пример отображения потока действия 1. Подразумевается, что он является потоком преобразования.

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

Метод проектирования Джексона

 

Для иллюстрации проектирования по этому методу продолжим пример с системой обслуживания перевозок.

Метод Джексона включает шесть шагов [39]. Три первых шага относятся к этапу анализа. Это шаги: объект — действие, объект — структура, начальное моделирование. Их мы уже рассмотрели.


Доопределение функций

 

Следующий шаг — доопределение функций. Этот шаг развивает диаграмму системной спецификации этапа анализа. Уточняются процессы-модели. В них вводятся дополнительные функции. Джексон выделяет 3 типа сервисных функций:

1. Встроенные функции (задаются командами, вставляемыми в структурный текст процесса-модели).

2. Функции впечатления (наблюдают вектор состояния процесса-модели и вырабатывают выходные результаты).

3. Функции диалога.

Они решают следующие задачи:

q наблюдают вектор состояния процесса-модели;

q формируют и выводят поток данных, влияющий на действия в процессе-модели;

q выполняют операции для выработки некоторых результатов.

Встроенную функцию введем в модель ТРАНСПОРТ-1. Предположим, что в модели есть панель с лампочкой, сигнализирующей о прибытии. Лампочка включается командой LON(i), а выключается командой LOFF(i). По мере перемещения транспорта между остановками формируется поток LAMP-команд. Модифицированный структурный текст модели ТРАНСПОРТ-1 принимает вид

ТРАНСПОРТ-1

LON(l);

опрос SV;

ЖДАТЬ цикл ПОКА ПРИБЫЛ(1)

опрос SV;

конец ЖДАТЬ;

LOFF(l);

покинуть(1);

ТРАНЗИТ цикл ПОКА УБЫЛ(1)

опрос SV;

конец ТРАНЗИТ;

ТРАНСПОРТ цикл

ОСТАНОВКА;

прибыть(i);

LON(i);

ЖДАТЬ цикл ПОКА ПРИБЫЛ(i)

опрос SV;

конец ЖДАТЬ;

LOFF(i);

покинуть(i);

ТРАНЗИТ цикл ПОКА УБЫЛ(i)

опрос SV;

конец ТРАНЗИТ;

конец ОСТАНОВКА;

конец ТРАНСПОРТ;

прибыть(1);

конец ТРАНСПОРТ-1;

Теперь введем функцию впечатления. В нашем примере она может формировать команды для мотора транспорта: START, STOP.

Условия выработки этих команд.

q Команда STOP формируется, когда датчики регистрируют прибытие транспорта на остановку.

q Команда START формируется, когда нажата кнопка для запроса транспорта и транспорт ждет на одной из остановок.

Видим, что для выработки команды STOP необходима информация только от модели транспорта. В свою очередь, для выработки команды START нужна информация как от модели КНОПКА-1, так и от модели ТРАНСПОРТ-1. В силу этого для реализации функции впечатления введем функциональный процесс М-УПРАВЛЕНИЕ. Он будет обрабатывать внешние данные и формировать команды START и STOP.

Ясно, что процесс М-УПРАВЛЕНИЕ должен иметь внешние связи с моделями ТРАНСПОРТ-1 и КНОПКА. Соединение с моделью КНОПКА организуем через вектор состояния BV. Соединение с моделью ТРАНСПОРТ-1 организуем через поток данных S1D.

Для обеспечения М-УПРАВЛЕНИЯ необходимой информацией опять надо изменить структурный текст модели ТРАНСПОРТ-1. В нем предусмотрим занесение сообщения Прибыл в буфер S1D:

ТРАНСПОРТ-1

LON(l);

опрос SV;

ЖДАТЬ цикл ПОКА ПРИБЫЛ(1)

опрос SV;

конец ЖДАТЬ;

LOFF(l);

Покинуть(1);

ТРАНЗИТ цикл ПОКА УБЫЛ(1)

опрос SV;

конец ТРАНЗИТ;

ТРАНСПОРТ цикл

ОСТАНОВКА;

прибыть(i):

записать Прибыл в S1D;

LON(i);

ЖДАТЬ цикл ПОКА ПРИБЫЛ(i)

опрос SV;

конец ЖДАТЬ;

LOFF(i);

покинуть(i);

ТРАНЗИТ цикл ПОКА УБЫЛ(i)

опрос SV;

конец ТРАНЗИТ;

конец ОСТАНОВКА;

конец ТРАНСПОРТ;

прибыть(1);

записать Прибыл в S1D;

конец ТРАНСПОРТ-1;

Очевидно, что при такой связи процессов необходимо гарантировать, что процесс ТРАНСПОРТ-1 выполняет операции опрос SV, а процесс М-УПРАВЛЕНИЕ читает сообщения Прибытия в S1D с частотой, достаточной для своевременной остановки транспорта. Временные ограничения, планирование и реализация должны рассматриваться в последующих шагах проектирования.

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

Диаграмма дополнительного процесса КНОПКА-2, в котором учтено это уточнение, показана на рис. 5.7.

 

Рис. 5.7. Диаграмма дополнительного процесса КНОПКА-2

 

Внешние связи модели КНОПКА-2 должны включать:

q одно соединеннее моделью КНОПКА-1 — организуется через поток данных BID (для приема сообщения о нажатии кнопки);

q два соединения с процессом М-УПРАВЛЕНИЕ — одно организуется через поток данных MBD (для приема сообщения о прибытии транспорта), другое организуется через вектор состояния BV (для передачи состояния переключателя Запрос).

Таким образом, КНОПКА-2 читает два буфера данных, заполняемых процессами КНОПКА-1 и М-УПРАВЛЕНИЕ, и формирует состояние внутреннего электронного переключателя Запрос. Она реализует функцию диалога.

Структурный текст модели КНОПКА-2 может иметь следующий вид:

КНОПКА-2

Запрос := НЕТ;

читать B1D;

ГрНАЖ цикл

ЖдатьНАЖ цикл ПОКА Не НАЖАТА

читать B1D;

конец ЖдатьНАЖ;

Запрос := ДА;

читать MBD;

ЖдатьОБСЛУЖ цикл ПОКА Не ПРИБЫЛ

читать MBD;

конец ЖдатьОБСЛУЖ;

Запрос := НЕТ; читать B1D;

конец ГрНАЖ;

конец КНОПКА-2;

Диаграмма системной спецификации, отражающая все изменения, представлена на рис. 5.8.

 

Рис. 5.8. Полная диаграмма системной спецификации

 

Встроенная в ТРАНСПОРТ-1 функция вырабатывает LAMP-команды, функция впечатления модели М-УПРАВЛЕНИЕ генерирует команды управления мотором, а модель КНОПКА-2 реализует функцию диалога (совместно с процессом М-УПРАВЛЕНИЕ).


Учет системного времени

 

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

Временные ограничения для системы обслуживания перевозок, в частности, включают:

q временной интервал на выработку команды STOP; он должен выбираться путем анализа скорости транспорта и ограничения мощности;

q время реакции на включение и выключение ламп панели.

Для рассмотренного примера нет необходимости вводить специальный механизм синхронизации. Однако при расширении может потребоваться некоторая синхронизация обмена данными.

Контрольные вопросы

 

1. В чем состоит суть метода структурного проектирования?

2. Какие различают типы информационных потоков?

3. Что такое входящий поток?

4. Что такое выходящий поток?

5. Что такое центр преобразования?

6. Как производится отображение входящего потока?

7. Как производится отображение выходящего потока?

8. Как производится отображение центра преобразования?

9. Какие задачи решают главный контроллер, контроллер входящего потока, контроллер выходящего потока и контроллер центра преобразования?

10. Поясните шаги метода структурного проектирования.

11. Что такое входящая ветвь?

12. Что такое диспетчерская ветвь?

13. Какие существуют различия в методике отображения потока преобразований и потока запросов?

14. Какие задачи уточнения иерархической структуры программной системы вы знаете?

15. Какие шаги предусматривает метод Джексона на этапе проектирования?

16. В чем состоит суть развития диаграммы системной спецификации Джексона?

17. Поясните понятие встроенной функции.

18. Поясните понятие функции впечатления.

19. Поясните понятие функции диалога.

20. В чем состоит учет системного времени (в методе Джексона)?


 

ГЛАВА 6. Структурное тестирование программного обеспечения

 

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

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

 

Тестирование — процесс выполнения программы с целью обнаружения ошибок. Шаги процесса задаются тестами.

Каждый тест определяет:

q свой набор исходных данных и условий для запуска программы;

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

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

Хорошим считают тестовый вариант с высокой вероятностью обнаружения еще не раскрытой ошибки. Успешным называют тест, который обнаруживает до сих пор не раскрытую ошибку.

Целью проектирования тестовых вариантов является систематическое обнаружение различных классов ошибок при минимальных затратах времени и стоимости.

Важен ответ на вопрос: что может тестирование?

Тестирование обеспечивает:

q обнаружение ошибок;

q демонстрацию соответствия функций программы ее назначению;

q демонстрацию реализации требований к характеристикам программы;

q отображение надежности как индикатора качества программы.

А чего не может тестирование? Тестирование не может показать отсутствия дефектов (оно может показывать только присутствие дефектов). Важно помнить это (скорее печальное) утверждение при проведении тестирования.

Рассмотрим информационные потоки процесса тестирования. Они показаны на рис. 6.1.

 

Рис. 6.1. Информационные потоки процесса тестирования

 

На входе процесса тестирования три потока:

q текст программы;

q исходные данные для запуска программы;

q ожидаемые результаты.

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

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

q качество и надежность ПО удовлетворительны;

q тесты не способны обнаруживать серьезные ошибки.

В конечном счете, если тесты не обнаруживают ошибок, появляется сомнение в том, что тестовые варианты достаточно продуманы и что в ПО нет скрытых ошибок. Такие ошибки будут, в конечном итоге, обнаруживаться пользователями и корректироваться разработчиком на этапе сопровождения (когда стоимость исправления возрастает в 60-100 раз по сравнению с этапом разработки).

Результаты, накопленные в ходе тестирования, могут оцениваться и более формальным способом. Для этого используют модели надежности ПО, выполняющие прогноз надежности по реальным данным об интенсивности ошибок.

Существуют 2 принципа тестирования программы:

q функциональное тестирование (тестирование «черного ящика»);

q структурное тестирование (тестирование «белого ящика»).


Тестирование «черного ящика»

Известны: функции программы.

Исследуется: работа каждой функции на всей области определения.

Как показано на рис. 6.2, основное место приложения тестов «черного ящика» — интерфейс ПО.

 

Рис. 6.2. Тестирование «черного ящика»

 

Эти тесты демонстрируют:

q как выполняются функции программ;

q как принимаются исходные данные;

q как вырабатываются результаты;

q как сохраняется целостность внешней информации.

При тестировании «черного ящика» рассматриваются системные характеристики программ, игнорируется их внутренняя логическая структура. Исчерпывающее тестирование, как правило, невозможно. Например, если в программе 10 входных величин и каждая принимает по 10 значений, то потребуется 1010 тестовых вариантов. Отметим также, что тестирование «черного ящика» не реагирует на многие особенности программных ошибок.

Тестирование «белого ящика»

Известна: внутренняя структура программы.

Исследуются: внутренние элементы программы и связи между ними (рис. 6.3).

 

Рис. 6.3. Тестирование «белого ящика»

 

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

Особенности тестирования «белого ящика»

 

Обычно тестирование «белого ящика» основано на анализе управляющей структуры программы [2], [13]. Программа считается полностью проверенной, если проведено исчерпывающее тестирование маршрутов (путей) ее графа управления.

В этом случае формируются тестовые варианты, в которых:

q гарантируется проверка всех независимых маршрутов программы;

q проходятся ветви True, False для всех логических решений;

q выполняются все циклы (в пределах их границ и диапазонов);

q анализируется правильность внутренних структур данных.

Недостатки тестирования «белого ящика»:

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

.

При п = 5 и k = 20 количество маршрутов т = 1014. Примем, что на разработку, выполнение и оценку теста по одному маршруту расходуется 1 мс. Тогда при работе 24 часа в сутки 365 дней в году на тестирование уйдет 3170 лет.

2. Исчерпывающее тестирование маршрутов не гарантирует соответствия программы исходным требованиям к ней.

3. В программе могут быть пропущены некоторые маршруты.

4. Нельзя обнаружить ошибки, появление которых зависит от обрабатываемых данных (это ошибки, обусловленные выражениями типа if abs (a-b) < eps..., if(a+b+c)/3=a...).

Достоинства тестирования «белого ящика» связаны с тем, что принцип «белого ящика» позволяет учесть особенности программных ошибок:

1. Количество ошибок минимально в «центре» и максимально на «периферии» программы.

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

3. При записи алгоритма ПО в виде текста на языке программирования возможно внесение типовых ошибок трансляции (синтаксических и семантических).

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

Каждая из этих причин является аргументом для проведения тестирования по принципу «белого ящика». Тесты «черного ящика» не смогут реагировать на ошибки таких типов.

Способ тестирования базового пути

 

Тестирование базового пути — это способ, который основан на принципе «белого ящика». Автор этого способа — Том МакКейб (1976) [49].

Способ тестирования базового пути дает возможность:

q получить оценку комплексной сложности программы;

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

Тестовые варианты разрабатываются для проверки базового множества путей (маршрутов) в программе. Они гарантируют однократное выполнение каждого оператора программы при тестировании.


 

Потоковый граф

 

Для представления программы используется потоковый граф. Перечислим его особенности.

1. Граф строится отображением управляющей структуры программы. В ходе отображения закрывающие скобки условных операторов и операторов циклов (end if; end loop) рассматриваются как отдельные (фиктивные) операторы.

2. Узлы (вершины) потокового графа соответствуют линейным участкам программы, включают один или несколько операторов программы.

3. Дуги потокового графа отображают поток управления в программе (передачи управления между операторами). Дуга — это ориентированное ребро.

4. Различают операторные и предикатные узлы. Из операторного узла выходит одна дуга, а из предикатного — две дуги.

4. Предикатные узлы соответствуют простым условиям в программе. Составное условие программы отображается в несколько предикатных узлов. Составным называют условие, в котором используется одна или несколько булевых операций (OR, AND).

5. Например, фрагмент программы

if a OR b

then x

else у

end if;

вместо прямого отображения в потоковый граф вида, показанного на рис. 6.4, отображается в преобразованный потоковый граф (рис. 6.5).

 

Рис. 6.4. Прямое отображение в потоковый граф

 

 

Рис. 6.5. Преобразованный потоковый граф

 

6. Замкнутые области, образованные дугами и узлами, называют регионами.

7. Окружающая граф среда рассматривается как дополнительный регион. Например, показанный здесь граф имеет три региона — Rl, R2, R3.

Пример 1. Рассмотрим процедуру сжатия:

процедура сжатие

1 выполнять пока нет EOF

1 читать запись;

2 если запись пуста

3 то удалить запись:

4 иначе если поле а >= поля b

5 то удалить b;

6 иначе удалить а;

7а конец если;

7а конец если;

7b конец выполнять;

8 конец сжатие;

 

Рис. 6.6. Преобразованный потоковый граф процедуры сжатия

 

Она отображается в потоковый граф, представленный на рис. 6.6. Видим, что этот потоковый граф имеет четыре региона.


Цикломатическая сложность

 

Цикломатическая сложность — метрика ПО, которая обеспечивает количественную оценку логической сложности программы. В способе тестирования базового пути Цикломатическая сложность определяет:

q количество независимых путей в базовом множестве программы;

q верхнюю оценку количества тестов, которое гарантирует однократное выполнение всех операторов.

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

ПРИМЕЧАНИЕ

Путь начинается в начальном узле, а заканчивается в конечном узле графа. Независимые пути формируются в порядке от самого короткого к самому длинному.

 

Перечислим независимые пути для потокового графа из примера 1:

Путь 1: 1-8.

Путь 2: 1-2-3-7а-7b-1-8.

Путь 3: 1-2-4-5-7а-7b-1-8.

Путь 4: 1-2-4-6-7а-7b-1-8.

Заметим, что каждый новый путь включает новую дугу.

Все независимые пути графа образуют базовое множество.

Свойства базового множества:

1) тесты, обеспечивающие его проверку, гарантируют:

q однократное выполнение каждого оператора;

q выполнение каждого условия по True-ветви и по False-ветви;

2) мощность базового множества равна цикломатической сложности потокового графа.

Значение 2-го свойства трудно переоценить — оно дает априорную оценку количества независимых путей, которое имеет смысл искать в графе.

Цикломатическая сложность вычисляется одним из трех способов:

1) цикломатическая сложность равна количеству регионов потокового графа;

2) цикломатическая сложность определяется по формуле

V(G)-E-N+2,

где Е — количество дуг, N — количество узлов потокового графа;

3) цикломатическая сложность формируется по выражению V(G) =p+ 1, где р — количество предикатных узлов в потоковом графе G.

Вычислим цикломатическую сложность графа из примера 1 каждым из трех способов:

1) потоковый граф имеет 4 региона;

2) V(G) = 11 дуг - 9 узлов + 2 = 4;

3) V(G) = 3 предикатных узла +1=4.

Таким образом, цикломатическая сложность потокового графа из примера 1 равна четырем.


Шаги способа тестирования базового пути

 

Для иллюстрации шагов данного способа используем конкретную программу — процедуру вычисления среднего значения:

процедура сред;

1 i := 1;

1 введено := 0;

1 колич := 0;

1 сум := 0;

вып пока 2 -вел( i ) <> stop и введено <=500 - 3

4 введено:= введено + 1;

если 5 -вел( i ) >= мин и вел( i ) <= макс - 6

7 то колич := колич + 1;

7 сум := сум + вел( i );

8 конец если;

8 i := i + 1;

9 конец вып;

10 если колич > 0

11 то сред := сум / колич;

12 иначе сред := stop;

13 конец если;

13 конец сред;

Заметим, что процедура содержит составные условия (в заголовке цикла и условном операторе). Элементы составных условий для наглядности помещены в рамки.

Шаг 1. На основе текста программы формируется потоковый граф:

q нумеруются операторы текста (номера операторов показаны в тексте процедуры);

q производится отображение пронумерованного текста программы в узлы и вершины потокового графа (рис. 6.7).

 

Рис. 6.7. Потоковый граф процедуры вычисления среднего значения

Шаг 2. Определяется цикломатическая сложность потокового графа — по каждой из трех формул:

1) V(G) = 6 регионов;

2) V(G) = 17 дуг - 13 узлов + 2 = 6;

3) V(G) = 5 предикатных узлов + 1 = 6.

Шаг 3. Определяется базовое множество независимых линейных путей:

Путь 1: 1-2-10-11-13; /вел=stор, колич>0.

Путь 2: 1-2-10-12-13;/вел=stop, колич=0.

Путь 3: 1-2-3-10-11-13; /попытка обработки 501-й величины.

Путь 4: 1-2-3-4-5-8-9-2-... /вел<мин.

Путь 5: 1-2-3-4-5-6-8-9-2-... /вел>макс.

Путь 6: 1-2-3-4-5-6-7-8-9-2-... /режим нормальной обработки.

ПРИМЕЧАНИЕ

Для удобства дальнейшего анализа по каждому пути указаны условия запуска. Точки в конце путей 4, 5, 6 указывают, что допускается любое продолжение через остаток управляющей структуры графа.

Шаг 4. Подготавливаются тестовые варианты, инициирующие выполнение каждого пути.

Каждый тестовый вариант формируется в следующем виде:

Исходные данные (ИД):

Ожидаемые результаты (ОЖ.РЕЗ.):

Исходные данные должны выбираться так, чтобы предикатные вершины обеспечивали нужные переключения — запуск только тех операторов, которые перечислены в конкретном пути, причем в требуемом порядке.

Определим тестовые варианты, удовлетворяющие выявленному множеству независимых путей.

Тестовый вариант для пути 1 ТВ1:

ИД: вел(k) = допустимое значение, где k < i; вел(i) = stop, где 2 < i < 500.

ОЖ.РЕЗ.: корректное усреднение основывается на k величинах и правильном подсчете.

ПРИМЕЧАНИЕ

Путь не может тестироваться самостоятельно, а должен тестироваться как часть путей 4, 5, 6 (трудности проверки 11-го оператора).

 

Тестовый вариант для пути 2 ТВ2:

ИД: вел(1)=stор.

ОЖ.РЕЗ.: сред=stор, другие величины имеют начальные значения.

Тестовый вариант для пути 3 ТВЗ:

ИД: попытка обработки 501-й величины, первые 500 величин должны быть правильными.

ОЖ.РЕЗ.: корректное усреднение основывается на k величинах и правильном подсчете.

Тестовый вариант для пути 4 ТВ4:

ИД: вел(i)=допустимое значение, где i ≤ 500; вел(k) < мин, где k < i.

ОЖ.РЕЗ.: корректное усреднение основывается на k величинах и правильном подсчете.

Тестовый вариант для пути 5 ТВ5:

ИД: вел(i)=допустимое значение, где i ≤ 500; вел(k) > макс, где k < i.

ОЖ.РЕЗ.: корректное усреднение основывается на п величинах и правильном подсчете.

Тестовый вариант для пути 6 ТВ6:

ИД: вел(i)=допустимое значение, где i ≤ 500.

ОЖ.РЕЗ.: корректное усреднение основывается на п величинах и правильном подсчете.

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

Важно отметить, что некоторые независимые пути не могут проверяться изолированно. Такие пути должны проверяться при тестировании другого пути (как часть другого тестового варианта).

Способы тестирования условий

 

Цель этого семейства способов тестирования — строить тестовые варианты для проверки логических условий программы. При этом желательно обеспечить охват операторов из всех ветвей программы.

Рассмотрим используемую здесь терминологию.

Простое условие — булева переменная или выражение отношения.

Выражение отношения имеет вид

Е1 <оператор отношения> E2,

где El, Е2 — арифметические выражения, а в качестве оператора отношения используется один из следующих операторов: <, >, =, , .

Составное условие состоит из нескольких простых условий, булевых операторов и круглых скобок. Будем применять булевы операторы OR, AND (&), NOT. Условия, не содержащие выражений отношения, называют булевыми выражениями.

Таким образом, элементами условия являются: булев оператор, булева переменная, пара скобок (заключающая простое или составное условие), оператор отношения, арифметическое выражение. Эти элементы определяют типы ошибок в условиях.

Если условие некорректно, то некорректен по меньшей мере один из элементов условия. Следовательно, в условии возможны следующие типы ошибок:

q ошибка булева оператора (наличие некорректных / отсутствующих / избыточных булевых операторов);

q ошибка булевой переменной;

q ошибка булевой скобки;

q ошибка оператора отношения;

q ошибка арифметического выражения.

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

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

Существует несколько методик тестирования условий.

Простейшая методика — тестирование ветвей. Здесь для составного условия С проверяется:

q каждое простое условие (входящее в него);

q Тruе-ветвь;

q False-ветвь.

Другая методика — тестирование области определения. В ней для выражения отношения требуется генерация 3-4 тестов. Выражение вида

Е1 <оператор отношения> Е2

проверяется тремя тестами, которые формируют значение Е1 большим, чем Е2, равным Е2 и меньшим, чем Е2.

Если оператор отношения неправилен, а Е1 и Е2 корректны, то эти три теста гарантируют обнаружение ошибки оператора отношения.

Для определения ошибок в Е1 и Е2 тест должен сформировать значение Е1 большим или меньшим, чем Е2, причем обеспечить как можно меньшую разницу между этими значениями.

Для булевых выражений с п переменными требуется набор из 2n тестов. Этот набор позволяет обнаружить ошибки булевых операторов, переменных и скобок, но практичен только при малом п. Впрочем, если в булево выражение каждая булева переменная входит только один раз, то количество тестов легко уменьшается.

Обсудим способ тестирования условий, базирующийся на приведенных выше методиках.


Тестирование ветвей и операторов отношений

 

Способ тестирования ветвей и операторов отношений (автор К. Таи, 1989) обнаруживает ошибки ветвления и операторов отношения в условии, для которого выполняются следующие ограничения [72]:

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

q в условии нет общих переменных.

В данном способе используются естественные ограничения условий (ограничения на результат). Для составного условия С, включающего п простых условий, формируется ограничение условия:

ОУс = (d1,d2,d3.....dn),

где di — ограничение на результат i-го простого условия.

Ограничение на результат фиксирует возможные значения аргумента (переменной) простого условия (если он один) или соотношения между значениями аргументов (если их несколько).

Если i-e простое условие является булевой переменной, то его ограничение на результат состоит из двух значений и имеет вид

di = (true, false).

Если j-е простое условие является выражением отношения, то его ограничение на результат состоит из трех значений и имеет следующий вид:

dj= (>,<,=).

Говорят, что ограничение условия ОУc (для условия С) покрывается выполнением С, если в ходе этого выполнения результат каждого простого условия в С удовлетворяет соответствующему ограничению в ОУc.

На основе ограничения условия ОУ создается ограничивающее множество ОМ, элементы которого являются сочетаниями всех возможных значений d1, d2, d3, ..., dn.

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

b&(х>у)&а.

Условие принимает истинное значение, если все простые условия истинны. В терминах значений простых условий это соответствует записи

(true, true, true),

а в терминах ограничений на значения аргументов простых условий — записи

(true, >, true).

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

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

Пример 1. В качестве примера рассмотрим два типовых составных условия:

С& = а & Ь, Сor or b,

где а и b булевы переменные. Соответствующие ограничения условий принимают вид

ОУ&=( d1,d2), ОУor=( d1,d2),

где d1 = d2 = (true, false).

Ограничивающие множества удобно строить с помощью таблицы истинности (табл. 6.1).

Таблица 6.1. Таблица истинности логических операций

Вариант

а

b

a & b

a or b

1

false

false

false

false

2

false

true

false

true

3

true

false

false

true

4

true

true

true

true

 

Видим, что таблица задает в ОМ четыре элемента (и соответственно, четыре тестовых варианта). Зададим вопрос — каковы возможности минимизации? Можно ли уменьшить количество элементов в ОМ?

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

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

q для условия типа И (а & b) варианты 2 и 3 поглощают вариант 1. Поэтому ограничивающее множество имеет вид:

ОМ& = {(false, true), (true, false), (true, true)};

q для условия типа ИЛИ or b) варианты 2 и 3 поглощают вариант 4. Поэтому ограничивающее множество имеет вид:

ОМor = {(false, false), (false, true), (true, false)}.

Рассмотрим шаги способа тестирования ветвей и операторов отношений.

Для каждого условия в программе выполняются следующие действия:

1) строится ограничение условий ОУ;

2) выявляются ограничения результата по каждому простому условию;

3) строится ограничивающее множество ОМ. Построение выполняется путем подстановки в константные формулы ОМ& или OMOR выявленных ограничений результата;

4) для каждого элемента ОМ разрабатывается тестовый вариант.

Пример 2. Рассмотрим составное условие С1 вида:

В1 &(E1,E2),

где В1 — булево выражение, E1, Е2 — арифметические выражения.

Ограничение составного условия имеет вид

ОУ =( d1,d2),

где ограничения простых условий равны

d1 = (true, false), d2 = (=, <, >).

Проводя аналогию между С1 и С& (разница лишь в том, что в С1 второе простое условие — это выражение отношения), мы можем построить ограничивающее множество для С1 модификацией

ОМ& = {(false, true), (true, false), (true, true)}.

Заметим, что true для (E1= E2) означает =, a false для (E1 = E2) означает или <, или >. Заменяя (true, true) и (false, true), ограничениями (true, =) и (false, =) соответственно, a (true, false) — ограничениями (true, <) и (true, >), получаем ограничивающее множество для С1:

ОМ = {(false,=),(true,<),(true,>),(true,=)}.

Покрытие этого множества гарантирует обнаружение ошибок булевых операторов и операторов отношения в С1.

Пример 3. Рассмотрим составное условие С2 вида

(E3 >E4)&(E1=E2),

где E1, Е2, Е3, Е4 — арифметические выражения. Ограничение составного условия имеет вид

ОУ =( d1,d2),

где ограничения простых условий равны

d1=(=,<,>), d2 =(=,<,>).

Проводя аналогию между С2 и С1 (разница лишь в том, что в С2 первое простое условие — это выражение отношения), мы можем построить ограничивающее множество для С2 модификацией ОМ:

ОМ = {(=, =), (<, =), (>, <),(>, >),(>, =)}.

Покрытие этого ограничивающего множества гарантирует обнаружение ошибок операторов отношения в С2.

Способ тестирования потоков данных

 

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

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

Рассмотрим пример.

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

q в вершине 1 определяются значения переменных а, b;

q значение переменной а используется в вершине 4;

q значение переменной b используется в вершинах 3, 6;

q в вершине 4 определяется значение переменной с, которая используется в вершине 6.

 

Рис. 6.8. Граф программы с управляющими и информационными связями

 

В общем случае для каждой вершины графа можно записать:

q множество определений данных

DEF(i) = { х | i -я вершина содержит определение х};

q множество использований данных:

USE (i) = { х | i -я вершина использует х}.

Под определением данных понимают действия, изменяющие элемент данных. Признак определения — имя элемента стоит в левой части оператора присваивания:

x:=f(…).

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

#:=f(x).

Здесь место подстановки другого имени отмечено прямоугольником (прямоугольник играет роль метки-заполнителя).

Назовём DU-цепочкой (цепочкой определения-использования) конструкцию [х, i,j], где i,j имена вершин; х определена в i-й вершине DЕF(i)) и используется в j -й вершине USE(j)).

В нашем примере существуют следующие DU-цепочки:

[а,1,4],[b, 1,3], [b, 1,6], [с, 4, 6].

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

Очевидно, что для подготовки тестов требуется выделение маршрутов — путей выполнения программы на управляющем графе. Критерий для выбора пути — покрытие максимального количества DU-цепочек.

Шаги способа DU-тестирования:

1) построение управляющего графа (УГ) программы;

2) построение информационного графа (ИГ);

3) формирование полного набора DU-цепочек;

4) формирование полного набора отрезков путей в управляющем графе (отображением набора DU-цепочек информационного графа, рис. 6.9);

 

Рис. 6.9. Отображение DU-цепочки в отрезок пути

 

5) построение маршрутов — полных путей на управляющем графе, покрывающих набор отрезков путей управляющего графа;

6) подготовка тестовых вариантов.

Достоинства DU-тестирования:

q простота необходимого анализа операционно-управляющей структуры программы;

q простота автоматизации.

Недостаток DU-тестирования: трудности в выборе минимального количества максимально эффективных тестов.

Область использования DU-тестирования: программы с вложенными условными операторами и операторами цикла.


Тестирование циклов

 

Цикл — наиболее распространенная конструкция алгоритмов, реализуемых в ПО. Тестирование циклов производится по принципу «белого ящика», при проверке циклов основное внимание обращается на правильность конструкций циклов.

Различают 4 типа циклов: простые, вложенные, объединенные, неструктурированные. Структура циклов приведена на рис. 6.10.

 

Рис. 6.10. Типовые структуры циклов

 

Простые циклы

 

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

1) прогон всего цикла;

2) только один проход цикла;

3) два прохода цикла;

4) т проходов цикла, где т<п;

5) п - 1, п, п + 1 проходов цикла.

Вложенные циклы

 

С увеличением уровня вложенности циклов количество возможных путей резко возрастает. Это приводит к нереализуемому количеству тестов [13]. Для сокращения количества тестов применяется специальная методика, в которой используются такие понятия, как объемлющий и вложенный циклы (рис. 6.11).

 

Рис. 6.11. Объемлющий и вложенный циклы

 

Порядок тестирования вложенных циклов иллюстрирует рис. 6.12.

 

Рис. 6.12. Шаги тестирования вложенных циклов

 

Шаги тестирования.

1. Выбирается самый внутренний цикл. Устанавливаются минимальные значения параметров всех остальных циклов.

2. Для внутреннего цикла проводятся тесты простого цикла. Добавляются тесты для исключенных значений и значений, выходящих за пределы рабочего диапазона.

3. Переходят в следующий по порядку объемлющий цикл. Выполняют его тестирование. При этом сохраняются минимальные значения параметров для всех внешних (объемлющих) циклов и типовые значения для всех вложенных циклов.

4. Работа продолжается до тех пор, пока не будут протестированы все циклы.

Объединенные циклы

 

Если каждый из циклов независим от других, то используется техника тестирования простых циклов. При наличии зависимости (например, конечное значение счетчика первого цикла используется как начальное значение счетчика второго цикла) используется методика для вложенных циклов.


Неструктурированные циклы

 

Неструктурированные циклы тестированию не подлежат. Этот тип циклов должен быть переделан с помощью структурированных программных конструкций.

Контрольные вопросы

1. Определите понятие тестирования.

2. Что такое тест? Поясните содержание процесса тестирования.

3. Что такое исчерпывающее тестирование?

4. Какие задачи решает тестирование?

5. Каких задач не решает тестирование?

6. Какие принципы тестирования вы знаете? В чем их отличие друг от друга?

7. В чем состоит суть тестирования «черного ящика»?

8. В чем состоит суть тестирования «белого ящика»?

9. Каковы особенности тестирования «белого ящика»?

10. Какие недостатки имеет тестирование «белого ящика»?

11. Какие достоинства имеет тестирование «белого ящика»?

12. Дайте характеристику способа тестирования базового пути.

13. Какие особенности имеет потоковый граф?

14. Поясните понятие независимого пути.

15. Поясните понятие цикломатической сложности.

16. Что такое базовое множество?

17. Какие свойства имеет базовое множество?

18. Какие способы вычисления цикломатической сложности вы знаете?

19. Поясните шаги способа тестирования базового пути.

20. Поясните достоинства, недостатки и область применения способа тестирования базового пути.

21. Дайте общую характеристику способов тестирования условий.

22. Какие типы ошибок в условиях вы знаете?

23. Какие методики тестирования условий вы знаете?

24. Поясните суть способа тестирования ветвей и операторов отношений. Какие он имеет ограничения?

25. Что такое ограничение на результат?

26. Что такое ограничение условия?

27. Что такое ограничивающее множество? Чем удобно его применение?

28. Поясните шаги способа тестирования ветвей и операторов отношений.

29. Поясните достоинства, недостатки и область применения способа тестирования ветвей и операторов отношений.

30. Поясните суть способа тестирования потоков данных.

31. Что такое множество определений данных?

32. Что такое множество использований данных?

33. Что такое цепочка определения-использования?

34. Поясните шаги способа тестирования потоков данных.

35. Поясните достоинства, недостатки и область применения способа тестирования потоков данных.

36. Поясните особенности тестирования циклов.

37. Какие методики тестирования простых циклов вы знаете?

38. Каковы шаги тестирования вложенных циклов?


 

ГЛАВА 7. Функциональное тестирование программного обеспечения

 

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

Особенности тестирования «черного ящика»

 

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

q набор, образуемый такими входными данными, которые приводят к аномалиям поведения программы (назовем его IT);

q набор, образуемый такими выходными данными, которые демонстрируют дефекты программы (назовем его ОТ).

Как показано на рис. 7.1, любой способ тестирования «черного ящика» должен:

q выявить такие входные данные, которые с высокой вероятностью принадлежат набору IT;

q сформулировать такие ожидаемые результаты, которые с высокой вероятностью являются элементами набора ОТ.

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

 

Рис. 7.1. Тестирование «черного ящика»

 

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

Тестирование «черного ящика» обеспечивает поиск следующих категорий ошибок:

1) некорректных или отсутствующих функций;

2) ошибок интерфейса;

3) ошибок во внешних структурах данных или в доступе к внешней базе данных;

4) ошибок характеристик (необходимая емкость памяти и т. д.);

5) ошибок инициализации и завершения.

Подобные категории ошибок способами «белого ящика» не выявляются.

В отличие от тестирования «белого ящика», которое выполняется на ранней стадии процесса тестирования, тестирование «черного ящика» применяют на поздних стадиях тестирования. При тестировании «черного ящика» пренебрегают управляющей структурой программы. Здесь внимание концентрируется на информационной области определения программной системы.

Техника «черного ящика» ориентирована на решение следующих задач:

q сокращение необходимого количества тестовых вариантов (из-за проверки не статических, а динамических аспектов системы);

q выявление классов ошибок, а не отдельных ошибок.


 

Способ разбиения по эквивалентности

 

Разбиение по эквивалентности — самый популярный способ тестирования «черного ящика» [3], [14].

В этом способе входная область данных программы делится на классы эквивалентности. Для каждого класса эквивалентности разрабатывается один тестовый вариант.

Класс эквивалентности — набор данных с общими свойствами. Обрабатывая разные элементы класса, программа должна вести себя одинаково. Иначе говоря, при обработке любого набора из класса эквивалентности в программе задействуется один и тот же набор операторов (и связей между ними).

На рис. 7.2 каждый класс эквивалентности показан эллипсом. Здесь выделены входные классы эквивалентности допустимых и недопустимых исходных данных, а также классы результатов.

Классы эквивалентности могут быть определены по спецификации на программу.

 

Рис. 7.2. Разбиение по эквивалентности

 

Например, если спецификация задает в качестве допустимых входных величин 5-разрядные целые числа в диапазоне 15 000...70 000, то класс эквивалентности допустимых ИД (исходных данных) включает величины от 15 000 до 70 000, а два класса эквивалентности недопустимых ИД составляют:

q числа меньшие, чем 15 000;

q числа большие, чем 70 000.

Класс эквивалентности включает множество значений данных, допустимых или недопустимых по условиям ввода.

Условие ввода может задавать:

1) определенное значение;

2) диапазон значений;

3) множество конкретных величин;

4) булево условие.

Сформулируем правила формирования классов эквивалентности.

1. Если условие ввода задает диапазон п...т, то определяются один допустимый и два недопустимых класса эквивалентности:

q V_Class={n.. } допустимый класс эквивалентности;

q Inv_С1аss1={x|для любого х: х < п} первый недопустимый класс эквивалентности;

q Inv_С1аss2={y|для любого у: у > т} второй недопустимый класс эквивалентности.

2. Если условие ввода задает конкретное значение а, то определяется один допустимый и два недопустимых класса эквивалентности:

q V_Class={a};

q Inv_Class1 ={х|для любого х: х < а};

q Inv_С1аss2={y|для любого у: у > а}.

3. Если условие ввода задает множество значений {а, b, с}, то определяются один допустимый и один недопустимый класс эквивалентности:

q V_Class={a, b, с};

q Inv_С1аss={x|для любого х: (х а)&(х b)&(х с)}.

4. Если условие ввода задает булево значение, например true, то определяются один допустимый и один недопустимый класс эквивалентности:

q V_Class={true};

q Inv_Class={false}.

После построения классов эквивалентности разрабатываются тестовые варианты. Тестовый вариант выбирается так, чтобы проверить сразу наибольшее количество свойств класса эквивалентности.


 

Способ анализа граничных значений

 

Как правило, большая часть ошибок происходит на границах области ввода, а не в центре. Анализ граничных значений заключается в получении тестовых вариантов, которые анализируют граничные значения [3], [14], [69]. Данный способ тестирования дополняет способ разбиения по эквивалентности.

Основные отличия анализа граничных значений от разбиения по эквивалентности:

1) тестовые варианты создаются для проверки только ребер классов эквивалентности;

2) при создании тестовых вариантов учитывают не только условия ввода, но и область вывода.

Сформулируем правила анализа граничных значений.

1. Если условие ввода задает диапазон п...т, то тестовые варианты должны быть построены:

q для значений п и т;

q для значений чуть левее п и чуть правее т на числовой оси.

Например, если задан входной диапазон -1,0...+1,0, то создаются тесты для значений - 1,0, +1,0, - 1,001, +1,001.

2. Если условие ввода задает дискретное множество значений, то создаются тестовые варианты:

q для проверки минимального и максимального из значений;

q для значений чуть меньше минимума и чуть больше максимума.

Так, если входной файл может содержать от 1 до 255 записей, то создаются тесты для О, 1, 255, 256 записей.

3. Правила 1 и 2 применяются к условиям области вывода.

Рассмотрим пример, когда в программе требуется выводить таблицу значений. Количество строк и столбцов в таблице меняется. Задается тестовый вариант для минимального вывода (по объему таблицы), а также тестовый вариант для максимального вывода (по объему таблицы).

4. Если внутренние структуры данных программы имеют предписанные границы, то разрабатываются тестовые варианты, проверяющие эти структуры на их границах.

5. Если входные или выходные данные программы являются упорядоченными множествами (например, последовательным файлом, линейным списком, таблицей), то надо тестировать обработку первого и последнего элементов этих множеств.

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

Рассмотрим применение способов разбиения по эквивалентности и анализа граничных значений на конкретном примере. Положим, что нужно протестировать программу бинарного поиска. Нам известна спецификация этой программы. Поиск выполняется в массиве элементов М, возвращается индекс I элемента массива, значение которого соответствует ключу поиска Key.

Предусловия:

1) массив должен быть упорядочен;

2) массив должен иметь не менее одного элемента;

3) нижняя граница массива (индекс) должна быть меньше или равна его верхней границе.

Постусловия:

1) если элемент найден, то флаг Result=True, значение I — номер элемента;

2) если элемент не найден, то флаг Result=False, значение I не определено.

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

1) массив из одного элемента;

2) массив из четного количества элементов;

3) массив из нечетного количества элементов, большего единицы.

Наконец на последнем, 4-м уровне критерием разбиения может быть анализ ребер классов эквивалентности. Очевидно, возможны следующие варианты:

1) работа с первым элементом массива;

2) работа с последним элементом массива;

3) работа с промежуточным (ни с первым, ни с последним) элементом массива.

Структура дерева разбиений приведена на рис. 7.3.

 

Рис. 7.3. Дерево разбиений области исходных данных бинарного поиска

 

Это дерево имеет 11 листьев. Каждый лист задает отдельный тестовый вариант. Покажем тестовые варианты, основанные на проведенных разбиениях.

Тестовый вариант 1 (единичный массив, элемент найден) ТВ1:

ИД: М=15; Кеу=15.

ОЖ.РЕЗ.: Resutt=True; I=1.

Тестовый вариант 2 (четный массив, найден 1-й элемент) ТВ2:

ИД: М=15, 20, 25,30,35,40; Кеу=15.

ОЖ.РЕЗ.: Result=True; I=1.

Тестовый вариант 3 (четный массив, найден последний элемент) ТВЗ:

ИД: М=15, 20, 25, 30, 35, 40; Кеу=40.

ОЖ.РЕЗ:. Result=True; I=6.

Тестовый вариант 4 (четный массив, найден промежуточный элемент) ТВ4:

ИД: М=15,20,25,30,35,40; Кеу=25.

ОЖ.РЕЗ.: Result-True; I=3.

Тестовый вариант 5 (нечетный массив, найден 1-й элемент) ТВ5:

ИД: М=15, 20, 25, 30, 35,40, 45; Кеу=15.

ОЖ.РЕЗ.: Result=True; I=1.

Тестовый вариант 6 (нечетный массив, найден последний элемент) ТВ6:

ИД: М=15, 20, 25, 30,35, 40,45; Кеу=45.

ОЖ.РЕЗ.: Result=True; I=7.

Тестовый вариант 7 (нечетный массив, найден промежуточный элемент) ТВ7:

ИД: М=15, 20, 25, 30,35, 40, 45; Кеу=30.

ОЖ.РЕЗ.: Result=True; I=4.

Тестовый вариант 8 (четный массив, не найден элемент) ТВ8:

ИД: М=15, 20, 25, 30, 35,40; Кеу=23.

ОЖ.РЕЗ.: Result=False; I=?

Тестовый вариант 9 (нечетный массив, не найден элемент) ТВ9;

ИД: М=15, 20, 25, 30, 35, 40, 45; Кеу=24.

ОЖ.РЕЗ:. Result=False; I=?

Тестовый вариант 10 (единичный массив, не найден элемент) ТВ10:

ИД: М=15; Кеу=0.

ОЖ.РЕЗ.: Result=False; I=?

Тестовый вариант 11 (нарушены предусловия) ТВ11:

ИД: М=15, 10, 5, 25, 20, 40, 35; Кеу=35.

ОЖ.РЕЗ.: Аварийное донесение: Массив не упорядочен.


 

Способ диаграмм причин-следствий

 

Диаграммы причинно-следственных связей — способ проектирования тестовых вариантов, который обеспечивает формальную запись логических условий и соответствующих действий [3], [64]. Используется автоматный подход к решению задачи.

Шаги способа:

1) для каждого модуля перечисляются причины (условия ввода или классы эквивалентности условий ввода) и следствия (действия или условия вывода). Каждой причине и следствию присваивается свой идентификатор;

2) разрабатывается граф причинно-следственных связей;

3) граф преобразуется в таблицу решений;

4) столбцы таблицы решений преобразуются в тестовые варианты.

Изобразим базовые символы для записи графов причин и следствий (cause-effect graphs).

Сделаем предварительные замечания:

1) причины будем обозначать символами сi, а следствия — символами еi;

2) каждый узел графа может находиться в состоянии 0 или 1 (0 — состояние отсутствует, 1 — состояние присутствует).

Функция тождество (рис. 7.4) устанавливает, что если значение с1 есть 1, то и значение е1 есть 1; в противном случае значение е1 есть 0.

 

Рис. 7.4. Функция тождество

 

Функция не (рис. 7.5) устанавливает, что если значение с1 есть 1, то значение e1 есть 0; в противном случае значение е1 есть 1.

 

Рис. 7.5. Функция не

 

Функция или (рис. 7.6) устанавливает, что если с1 или с2 есть 1, то е1 есть 1, в противном случае e1 есть 0.

 

Рис. 7.6. Функция или

 

Функция и (рис. 7.7) устанавливает, что если и с1 и с2 есть 1, то е1 есть 1, в противном случае е1 есть 0.

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

 

Рис. 7.7. Функция и

 

Ограничение Е (исключает, Exclusive, рис. 7.8) устанавливает, что Е должно быть истинным, если хотя бы одна из причин — а или b принимает значение 1 и b не могут принимать значение 1 одновременно).

 

Рис. 7.8. Ограничение Е (исключает, Exclusive)

 

Ограничение I (включает, Inclusive, рис. 7.9) устанавливает, что по крайней мере одна из величин, а, b, или с, всегда должна быть равной 1 (а, b и с не могут принимать значение 0 одновременно).

 

Рис. 7.9. Ограничение I (включает, Inclusive)

 

Ограничение О (одно и только одно, Only one, рис. 7.10) устанавливает, что одна и только одна из величин а или b должна быть равна 1.

 

Рис. 7.10. Ограничение О (одно и только одно, Only one)

 

Ограничение R (требует, Requires, рис. 7.11) устанавливает, что если а принимает значение 1, то и b должна принимать значение 1 (нельзя, чтобы а было равно 1, a b - 0).

 

Рис. 7.11. Ограничение R (требует, Requires)

 

Часто возникает необходимость в ограничениях для следствий.

Ограничение М (скрывает, Masks, рис. 7.12) устанавливает, что если следствие а имеет значение 1, то следствие b должно принять значение 0.

 

Рис. 7.12. Ограничение М (скрывает, Masks)

 

Для иллюстрации использования способа рассмотрим пример, когда программа выполняет расчет оплаты за электричество по среднему или переменному тарифу.

При расчете по среднему тарифу:

q при месячном потреблении энергии меньшем, чем 100 кВт/ч, выставляется фиксированная сумма;

q при потреблении энергии большем или равном 100 кВт/ч применяется процедура А планирования расчета.

При расчете по переменному тарифу:

q при месячном потреблении энергии меньшем, чем 100 кВт/ч, применяется процедура А планирования расчета;

q при потреблении энергии большем или равном 100 кВт/ч применяется процедура В планирования расчета.

Шаг 1. Причинами являются:

1) расчет по среднему тарифу;

2) расчет по переменному тарифу;

3) месячное потребление электроэнергии меньшее, чем 100 кВт/ч;

4) месячное потребление электроэнергии большее или равное 100 кВт/ч.

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

q 101 — минимальная месячная стоимость;

q 102 — процедура А планирования расчета;

q 103 — процедура В планирования расчета.

Шаг 2. Разработка графа причинно-следственных связей (рис. 7.13).

Узлы причин перечислим по вертикали у левого края рисунка, а узлы следствий — у правого края рисунка. Для следствия 102 возникает необходимость введения вторичных причин — 11 и 12, — их размещаем в центральной части рисунка.

 

Рис. 7.13. Граф причинно-следственных связей

Шаг 3. Генерация таблицы решений. При генерации причины рассматриваются как условия, а следствия — как действия.

Порядок генерации.

1. Выбирается некоторое следствие, которое должно быть в состоянии «1».

2. Находятся все комбинации причин (с учетом ограничений), которые устанавливают это следствие в состояние «1». Для этого из следствия прокладывается обратная трасса через граф.

3. Для каждой комбинации причин, приводящих следствие в состояние «1», строится один столбец.

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

5. Действия 1-4 повторяются для всех следствий графа.

Таблица решений для нашего примера показана в табл. 7.1.

Шаг 4. Преобразование каждого столбца таблицы в тестовый вариант. В нашем примере таких вариантов четыре.

Тестовый вариант 1 (столбец 1) ТВ1:

ИД: расчет по среднему тарифу; месячное потребление электроэнергии 75 кВт/ч.

ОЖ.РЕЗ.: минимальная месячная стоимость.

Тестовый вариант 2 (столбец 2) ТВ2:

ИД: расчет по переменному тарифу; месячное потребление электроэнергии 90 кВт/ч.

ОЖ.РЕЗ.: процедура A планирования расчета.

Тестовый вариант 3 (столбец 3) ТВЗ:

ИД: расчет по среднему тарифу; месячное потребление электроэнергии 100 кВт/ч.

ОЖ.РЕЗ.: процедура А планирования расчета.

Тестовый вариант 4 (столбец 4) ТВ4:

ИД: расчет по переменному тарифу; месячное потребление электроэнергии 100 кВт/ч.

ОЖ.РЕЗ.: процедура В планирования расчета.

Таблица 7.1. Таблица решений для расчета оплаты за электричество

Номера столбцов — >

1

2

3

4

Условия

Причины

1

1

0

1

0

 

 

2

0

1

0

1

 

 

3

1

1

0

0

 

 

4

0

0

1

1

 

Вторичные причины

11

0

0

1

0

 

 

12

0

1

0

0

Действия

Следствия

101

1

0

0

0

 

 

102

0

1

1

0

 

 

103

0

0

0

1

Контрольные вопросы

 

1. Каковы особенности тестирования методом «черного ящика»?

2. Какие категории ошибок выявляет тестирование методом «черного ящика»?

3. Какие достоинства имеет тестирование методом «черного ящика»?

4. Поясните суть способа разбиения по эквивалентности.

5. Что такое класс эквивалентности?

6. Что может задавать условие ввода?

7. Какие правила формирования классов эквивалентности вы знаете?

8. Как выбирается тестовый вариант при тестировании по способу разбиения по эквивалентности?

9. Поясните суть способа анализа граничных значений.

10. Чем способ анализа граничных значений отличается от разбиения по эквивалентности?

11. Поясните правила анализа граничных значений.

12. Что такое дерево разбиений? Каковы его особенности?

13. В чем суть способа диаграмм причин-следствий?

14. Что такое причина?

15. Что такое следствие?

16. Дайте общую характеристику графа причинно-следственных связей.

17. Какие функции используются в графе причин и следствий?

18. Какие ограничения используются в графе причин и следствий?

19. Поясните шаги способа диаграмм причин-следствий.

20. Какую структуру имеет таблица решений в способе диаграмм причин-следствий?

21. Как таблица решений преобразуется в тестовые варианты?


 

ГЛАВА 8. Организация процесса тестирования программного обеспечения

 

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

Методика тестирования программных систем

 

Процесс тестирования объединяет различные способы тестирования в спланированную последовательность шагов, которые приводят к успешному построению программной системы (ПС) [3], [13], [64], [69]. Методика тестирования ПС может быть представлена в виде разворачивающейся спирали (рис. 8.1).

В начале осуществляется тестирование элементов (модулей), проверяющее результаты этапа кодирования ПС. На втором шаге выполняется тестирование интеграции, ориентированное на выявление ошибок этапа проектирования ПС. На третьем обороте спирали производится тестирование правильности, проверяющее корректность этапа анализа требований к ПС. На заключительном витке спирали проводится системное тестирование, выявляющее дефекты этапа системного анализа ПС.

Охарактеризуем каждый шаг процесса тестирования.

1. Тестирование элементов. Цель — индивидуальная проверка каждого модуля. Используются способы тестирования «белого ящика».

 

Рис. 8.1. Спираль процесса тестирования ПС

 

2. Тестирование интеграции. Цель — тестирование сборки модулей в программную систему. В основном применяют способы тестирования «черного ящика».

3. Тестирование правильности. Цель — проверить реализацию в программной системе всех функциональных и поведенческих требований, а также требования эффективности. Используются исключительно способы тестирования «черного ящика».

4. Системное тестирование. Цель — проверка правильности объединения и взаимодействия всех элементов компьютерной системы, реализации всех системных функций.

Организация процесса тестирования в виде эволюционной разворачивающейся спирали обеспечивает максимальную эффективность поиска ошибок. Однако возникает вопрос — когда заканчивать тестирование?

Ответ практика обычно основан на статистическом критерии: «Можно с 95%-ной уверенностью сказать, что провели достаточное тестирование, если вероятность безотказной работы ЦП с программным изделием в течение 1000 часов составляет по меньшей мере 0,995».

Научный подход при ответе на этот вопрос состоит в применении математической модели отказов. Например, для логарифмической модели Пуассона формула расчета текущей интенсивности отказов имеет вид:

, (8.1)

где — текущая интенсивность программных отказов (количество отказов в единицу времени); — начальная интенсивность отказов (в начале тестирования); р — экспоненциальное уменьшение интенсивности отказов за счет обнаруживаемых и устраняемых ошибок; t —время тестирования.

С помощью уравнения (8.1) можно предсказать снижение ошибок в ходе тестирования, а также время, требующееся для достижения допустимо низкой интенсивности отказов.

Тестирование элементов

 

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

Тестированию подвергаются:

q интерфейс модуля;

q внутренние структуры данных;

q независимые пути;

q пути обработки ошибок;

q граничные условия.

Интерфейс модуля тестируется для проверки правильности ввода-вывода тестовой информации. Если нет уверенности в правильном вводе-выводе данных, нет смысла проводить другие тесты.

Исследование внутренних структур данных гарантирует целостность сохраняемых данных.

Тестирование независимых путей гарантирует однократное выполнение всех операторов модуля. При тестировании путей выполнения обнаруживаются следующие категории ошибок: ошибочные вычисления, некорректные сравнения, неправильный поток управления [3].

Наиболее общими ошибками вычислений являются:

1) неправильный или непонятый приоритет арифметических операций;

2) смешанная форма операций;

3) некорректная инициализация;

4) несогласованность в представлении точности;

5) некорректное символическое представление выражений.

Источниками ошибок сравнения и неправильных потоков управления являются:

1) сравнение различных типов данных;

2) некорректные логические операции и приоритетность;

3) ожидание эквивалентности в условиях, когда ошибки точности делают эквивалентность невозможной;

4) некорректное сравнение переменных;

5) неправильное прекращение цикла;

6) отказ в выходе при отклонении итерации;

7) неправильное изменение переменных цикла.

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

1) донесение об ошибке невразумительно;

2) текст донесения не соответствует, обнаруженной ошибке;

3) вмешательство системных средств регистрации аварии произошло до обработки ошибки в модуле;

4) обработка исключительного условия некорректна;

5) описание ошибки не позволяет определить ее причину.

И, наконец, перейдем к граничному тестированию. Модули часто отказывают на «границах». Это означает, что ошибки часто происходят:

1) при обработке n-го элемента n-элементного массива;

2) при выполнении m-й итерации цикла с т проходами;

3) при появлении минимального (максимального) значения.

Тестовые варианты, ориентированные на данные ситуации, имеют высокую вероятность обнаружения ошибок.

Тестирование элементов обычно рассматривается как дополнение к этапу кодирования. Оно начинается после разработки текста модуля. Так как модуль не является автономной системой, то для реализации тестирования требуются дополнительные средства, представленные на рис. 8.2.

 

Рис. 8.2. Программная среда для тестирования модуля

 

Дополнительными средствами являются драйвер тестирования и заглушки. Драйвер — управляющая программа, которая принимает исходные данные (InData) и ожидаемые результаты (ExpRes) тестовых вариантов, запускает в работу тестируемый модуль, получает из модуля реальные результаты (OutData) и формирует донесения о тестировании. Алгоритм работы тестового драйвера приведен на рис. 8.3.

 

Рис. 8.3. Алгоритм работы драйвера тестирования

 

Заглушки замещают модули, которые вызываются тестируемым модулем. Заглушка, или «фиктивная подпрограмма», реализует интерфейс подчиненного модуля, может выполнять минимальную обработку данных, имитирует прием и возврат данных.

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

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

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


 

Тестирование интеграции

 

Тестирование интеграции поддерживает сборку цельной программной системы.

Цель сборки и тестирования интеграции: взять модули, протестированные как элементы, и построить программную структуру, требуемую проектом [3].

Тесты проводятся для обнаружения ошибок интерфейса. Перечислим некоторые категории ошибок интерфейса:

q потеря данных при прохождении через интерфейс;

q отсутствие в модуле необходимой ссылки;

q неблагоприятное влияние одного модуля на другой;

q подфункции при объединении не образуют требуемую главную функцию;

q отдельные (допустимые) неточности при интеграции выходят за допустимый уровень;

q проблемы при работе с глобальными структурами данных.

Существует два варианта тестирования, поддерживающих процесс интеграции: нисходящее тестирование и восходящее тестирование. Рассмотрим каждый из них.

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

 

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

Рассмотрим пример (рис. 8.4). Интеграция поиском в глубину будет подключать все модули, находящиеся на главном управляющем пути структуры (по вертикали). Выбор главного управляющего пути отчасти произволен и зависит от характеристик, определяемых приложением. Например, при выборе левого пути прежде всего будут подключены модули Ml, М2, М5. Следующим подключается модуль М8 или Мб (если это необходимо для правильного функционирования М2). Затем строится центральный или правый управляющий путь.

При интеграции поиском в ширину структура последовательно проходится по уровням-горизонталям. На каждом уровне подключаются модули, непосредственно подчиненные управляющему модулю — начальнику. В этом случае прежде всего подключаются модули М2, М3, М4. На следующем уровне — модули М5, Мб и т. д.

 

Рис. 8.4. Нисходящая интеграция системы

 

Опишем возможные шаги процесса нисходящей интеграции.

1. Главный управляющий модуль (находится на вершине иерархии) используется как тестовый драйвер. Все непосредственно подчиненные ему модули временно замещаются заглушками.

2. Одна из заглушек заменяется реальным модулем. Модуль выбирается поиском в ширину или в глубину.

3. После подключения каждого модуля (и установки на нем заглушек) проводится набор тестов, проверяющих полученную структуру.

4. Если в модуле-драйвере уже нет заглушек, производится смена модуля-драйвера (поиском в ширину или в глубину).

5. Выполняется возврат на шаг 2 (до тех пор, пока не будет построена целая структура).

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

Недостаток: трудности в ситуациях, когда для полного тестирования на верхних уровнях нужны результаты обработки с нижних уровней иерархии.

Существуют 3 возможности борьбы с этим недостатком:

1) откладывать некоторые тесты до замещения заглушек модулями;

2) разрабатывать заглушки, частично выполняющие функции модулей;

3) подключать модули движением снизу вверх.

Первая возможность вызывает сложности в оценке результатов тестирования.

Для реализации второй возможности выбирается одна из следующих категорий заглушек:

q заглушка А — отображает трассируемое сообщение;

q заглушка В — отображает проходящий параметр;

q заглушка С — возвращает величину из таблицы;

q заглушка D — выполняет табличный поиск по ключу (входному параметру) и возвращает связанный с ним выходной параметр.

 

Рис. 8.5. Категории заглушек

 

Категории заглушек представлены на рис. 8.5.

Очевидно, что заглушка А наиболее проста, а заглушка D наиболее сложна в реализации.

Этот подход работоспособен, но может привести к существенным затратам, так как заглушки становятся все более сложными.

Третью возможность обсудим отдельно.


 

Восходящее тестирование интеграции

 

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

Рассмотрим шаги методики восходящей интеграции.

1. Модули нижнего уровня объединяются в кластеры (группы, блоки), выполняющие определенную программную подфункцию.

2. Для координации вводов-выводов тестового варианта пишется драйвер, управляющий тестированием кластеров.

3. Тестируется кластер.

4. Драйверы удаляются, а кластеры объединяются в структуру движением вверх. Пример восходящей интеграции системы приведен на рис. 8.6.

Модули объединяются в кластеры 1,2,3. Каждый кластер тестируется драйвером. Модули в кластерах 1 и 2 подчинены модулю Ма, поэтому драйверы D1 и D2 удаляются и кластеры подключают прямо к Ма. Аналогично драйвер D3 удаляется перед подключением кластера 3 к модулю Mb. В последнюю очередь к модулю Мс подключаются модули Ма и Mb.

Рассмотрим различные типы драйверов:

q драйвер А — вызывает подчиненный модуль;

q драйвер В — посылает элемент данных (параметр) из внутренней таблицы;

q драйвер С —отображает параметр из подчиненного модуля;

q драйвер D — является комбинацией драйверов В и С.

Очевидно, что драйвер А наиболее прост, а драйвер D наиболее сложен в реализации. Различные типы драйверов представлены на рис. 8.7.

 

Рис. 8.7. Различные типы драйверов

 

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

Сравнение нисходящего и восходящего тестирования интеграции

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

1) основной недостаток— необходимость заглушек и связанные с ними трудности тестирования;

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

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

1) основной недостаток — система не существует как объект до тех пор, пока не будет добавлен последний модуль;

2) основное достоинство — упрощается разработка тестовых вариантов, отсутствуют заглушки.

Возможен комбинированный подход. В нем для верхних уровней иерархии применяют нисходящую стратегию, а для нижних уровней — восходящую стратегию тестирования [3], [13].

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

1) реализует несколько требований к программной системе;

2) имеет высокий уровень управления (находится достаточно высоко в программной структуре);

3) имеет высокую сложность или склонность к ошибкам (как индикатор может использоваться цикломатическая сложность — ее верхний разумный предел составляет 10);

4) имеет определенные требования к производительности обработки.

Критические модули должны тестироваться как можно раньше. Кроме того, к ним должно применяться регрессионное тестирование (повторение уже выполненных тестов в полном или частичном объеме).

Тестирование правильности

 

После окончания тестирования интеграции программная система собрана в единый корпус, интерфейсные ошибки обнаружены и откорректированы. Теперь начинается последний шаг программного тестирования — тестирование правильности. Цель — подтвердить, что функции, описанные в спецификации требований к ПС, соответствуют ожиданиям заказчика [64], [69].

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

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

1) системную спецификация;

2) план программного проекта;

3) спецификацию требований к ПС; работающий или бумажный макет;

4) предварительное руководство пользователя;

5) спецификация проектирования;

6) листинги исходных текстов программ;

7) план и методику тестирования; тестовые варианты и полученные результаты;

8) руководства по работе и инсталляции;

9) ехе-код выполняемой программы;

10) описание базы данных;

11) руководство пользователя по настройке;

12) документы сопровождения; отчеты о проблемах ПС; запросы сопровождения; отчеты о конструкторских изменениях;

13) стандарты и методики конструирования ПС.

Проверка конфигурации гарантирует, что все элементы конфигурации ПС правильно разработаны, учтены и достаточно детализированы для поддержки этапа сопровождения в жизненном цикле ПС.

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

Альфа-тестирование проводится заказчиком в организации разработчика. Разработчик фиксирует все выявленные заказчиком ошибки и проблемы использования.

Бета-тестирование проводится конечным пользователем в организации заказчика. Разработчик в этом процессе участия не принимает. Фактически, бета-тестирование — это реальное применение ПС в среде, которая не управляется разработчиком. Заказчик сам записывает все обнаруженные проблемы и сообщает о них разработчику. Бета-тестирование проводится в течение фиксированного срока (около года). По результатам выявленных проблем разработчик изменяет ПС и тем самым подготавливает продукт полностью на базе заказчика.


Системное тестирование

 

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

1) предусмотреть средства обработки ошибки, которые тестируют все вводы информации от других элементов системы;

2) провести тесты, моделирующие неудачные данные или другие потенциальные ошибки интерфейса ПС;

3) записать результаты тестов, чтобы использовать их как доказательство невиновности в случае «указания причины»;

4) принять участие в планировании и проектировании системных тестов, чтобы гарантировать адекватное тестирование ПС.

В конечном счете системные тесты должны проверять, что все системные элементы правильно объединены и выполняют назначенные функции. Рассмотрим основные типы системных тестов [13], [52].

Тестирование восстановления

 

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

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

Тестирование безопасности

 

Компьютерные системы очень часто являются мишенью незаконного проникновения. Под проникновением понимается широкий диапазон действий: попытки хакеров проникнуть в систему из спортивного интереса, месть рассерженных служащих, взлом мошенниками для незаконной наживы.

Тестирование безопасности проверяет фактическую реакцию защитных механизмов, встроенных в систему, на проникновение.

В ходе тестирования безопасности испытатель играет роль взломщика. Ему разрешено все:

q попытки узнать пароль с помощью внешних средств;

q атака системы с помощью специальных утилит, анализирующих защиты;

q подавление, ошеломление системы (в надежде, что она откажется обслуживать других клиентов);

q целенаправленное введение ошибок в надежде проникнуть в систему в ходе восстановления;

q просмотр несекретных данных в надежде найти ключ для входа в систему.

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


Стрессовое тестирование

 

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

Стрессовое тестирование производится при ненормальных запросах на ресурсы системы (по количеству, частоте, размеру-объему).

Примеры:

q генерируется 10 прерываний в секунду (при средней частоте 1,2 прерывания в секунду);

q скорость ввода данных увеличивается прямо пропорционально их важности (чтобы определить реакцию входных функций);

q формируются варианты, требующие максимума памяти и других ресурсов;

q генерируются варианты, вызывающие переполнение виртуальной памяти;

q проектируются варианты, вызывающие чрезмерный поиск данных на диске.

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

Тестирование производительности

 

В системах реального времени и встроенных системах недопустимо ПО, которое реализует требуемые функции, но не соответствует требованиям производительности.

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

Искусство отладки

 

Отладка — это локализация и устранение ошибок. Отладка является следствием успешного тестирования. Это значит, что если тестовый вариант обнаруживает ошибку, то процесс отладки уничтожает ее.

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

1) причина найдена, исправлена, уничтожена;

2) причина не найдена.

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

Возможные разные способы проявления ошибок:

1) программа завершается нормально, но выдает неверные результаты;

2) программа зависает;

3) программа завершается по прерыванию;

4) программа завершается, выдает ожидаемые результаты, но хранимые данные испорчены (это самый неприятный вариант).

Характер проявления ошибок также может меняться. Симптом ошибки может быть:

q постоянным;

q мерцающим;

q пороговым (проявляется при превышении некоторого порога в обработке — 200 самолетов на экране отслеживаются, а 201-й — нет);

q отложенным (проявляется только после исправления маскирующих ошибок).

В ходе отладки мы встречаем ошибки в широком диапазоне: от мелких неприятностей до катастроф. Следствием увеличения ошибок является усиление давления на отладчика — «найди ошибки быстрее!!!». Часто из-за этого давления разработчик устраняет одну ошибку и вносит две новые ошибки.

Английский термин debugging (отладка) дословно переводится как «ловля блох», который отражает специфику процесса — погоню за объектами отладки, «блохами». Рассмотрим, как может быть организован этот процесс «ловли блох» [3], [64].

Различают две группы методов отладки:

q аналитические;

q экспериментальные.

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

Общая стратегия отладки — обратное прохождение от замеченного симптома ошибки к исходной аномалии (месту в программе, где ошибка совершена).

В простейшем случае место проявления симптома и ошибочный фрагмент совпадают. Но чаще всего они далеко отстоят друг от друга.

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

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

Основное преимущество аналитических методов отладки состоит в том, что исходная программа остается без изменений.

В экспериментальных методах для прослеживания выполняется:

1. Выдача значений переменных в указанных точках.

2. Трассировка переменных (выдача их значений при каждом изменении).

3. Трассировка потоков управления (имен вызываемых процедур, меток, на которые передается управление, номеров операторов перехода).

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

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

Контрольные вопросы

 

1. Поясните суть методики тестирования программной системы.

2. Когда и зачем выполняется тестирование элементов? Какой этап конструирования оно проверяет?

3. Когда и зачем выполняется тестирование интеграции? Какой этап конструирования оно проверяет?

4. Когда и зачем выполняется тестирование правильности? Какой этап конструирования оно проверяет?

5. Когда и зачем выполняется системное тестирование? Какой этап конструирования оно проверяет?

6. Поясните суть тестирования элементов.

7. Перечислите наиболее общие ошибки вычислений.

8. Перечислите источники ошибок сравнения и неправильных потоков управления.

9. На какие ситуации ориентировано тестирование путей обработки ошибок?

10. Что такое драйвер тестирования?

11. Что такое заглушка?

12. Поясните порядок работы драйвера тестирования.

13. В чем цель тестирования интеграции?

14. Какие категории ошибок интерфейса вы знаете?

15. В чем суть нисходящего тестирования интеграции?

16. Поясните шаги процесса нисходящей интеграции.

17. Поясните достоинства и недостатки нисходящей интеграции.

18. Какие категории заглушек вы знаете?

19. В чем суть восходящего тестирования интеграции?

20. Поясните шаги процесса восходящей интеграции.

21. Поясните достоинства и недостатки восходящей интеграции.

22. Какие категории драйверов вы знаете?

23. Какова комбинированная стратегия интеграции?

24. Каковы признаки критического модуля?

25. Что такое регрессионное тестирование?

26. В чем суть тестирования правильности?

27. Какие элементы включает минимальная конфигурация программной системы?

28. Что такое альфа-тестирование?

29. Что такое бета-тестирование?

30. В чем суть системного тестирования?

31. Как защищаться от проблемы «указание причины»?

32. В чем суть тестирования восстановления?

33. В чем суть тестирования безопасности?

34. В чем суть стрессового тестирования?

35. В чем суть тестирования производительности?

36. Что такое отладка?

37. Какие способы проявления ошибок вы знаете?

38. Какие симптомы ошибки вы знаете?

39. В чем суть аналитических методов отладки?

40. Поясните достоинства и недостатки аналитических методов отладки.

41. В чем суть экспериментальных методов отладки?

42. Поясните достоинства и недостатки экспериментальных методов отладки.