Главная страница / 29. Структурное программирование: 29.5. Проектирование и те...

29.5. Проектирование и тестирование программы

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

  • нисходящая разработка;
  • структурное программирование;
  • нисходящее тестирование.

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

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

Постановка задачи завершается созданием технического задания, а затем – внешней спецификации программы, включающей в себя:

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

Таким образом, программа рассматривается как «черный ящик», для которого определена функция и входные и выходные данные.

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

Для каждой подзадачи составляется внешняя спецификация, аналогичная приведенной выше (см. этап Постановка задачи). Для каждой подзадачи (модуля) описывается ее функция, например, с помощью HIPO-диаграммы. Одна задача может реализовываться с помощью нескольких модулей и, наоборот, в одном модуле может решаться несколько задач. На более низкий уровень проектирования переходят только после окончания проектирования верхнего уровня. Для каждого модуля разрабатывается алгоритм обработки данных с помощью базовых конструкций структурного программирования и записывается в обобщенной форме – например, словесной, в виде обобщенных блок-схем или другими способами. Ниже приведен пример структурного проекта, описанный с помощью блок-схемы для логического модуля «1. 1. Перевод десятичного целого в р-ичное целое, представленное строкой» (рис. 29.4).

img294

Рис. 29.4. Блок-схема логического модуля «1. 1. Перевод десятичного целого в р-ичное целое, представленное строкой»

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

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

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

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

Таким образом, сначала создается логический скелет программы, который затем обрастает плотью кода.

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

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

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

Этапы проектирования и программирования совмещены во времени: сначала проектируется и кодируется верхний уровень, затем – следующий, и т. д. Такая стратегия применяется потому, что в процессе кодирования может возникнуть необходимость внести изменения, отражающиеся на модулях нижнего уровня. Приведенный далее пример структурного кодирования проекта описан с помощью блок-схемы рис. 29.4. Модуль реализован в форме функции Object Pascal.

img295

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

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

Тестирование – это процесс выполнения программ с целью обнаружения логических ошибок [23].

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

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

Будем тестировать функцию методом тестирования путей. Среди возможных путей выполнения функции можно выделить два пути выполнения, которые представлены на Е-схеме выполнения функции (рис. 29.5), полученной из блок-схемы.

img295b

Рис. 29.5. Е-схема выполнения функции, реализующей логический модуль
«1. 1. Перевод десятичного целого в  р-ичное целое, представленное строкой»

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

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

Таблица 29.1. Набор тестов для тестирования разработанной функции

Номер теста
Исходные данные
Ожидаемый результат
Путь прохождения
1
n = 0, р = 2 0
Цикл не выполняется
2
n = 1, р = 2
1
Цикл не выполняется
3
n = 8, р = 2
1000
Цикл выполняется три раза
4
n = 0, р = 16
0
Цикл не выполняется
5
n = 10, р =16
A
Цикл не выполняется
6
n = 15, р =16
F
Цикл выполняется три раза
7
n = 256, р = 16
100
Цикл выполняется два раза
Отладка программы – это процесс, осуществляемый после удачного выполнения теста. Процесс отладки начинается при обнаружении ошибки (например, при удачном завершении теста) и проводится в два этапа:
  • определяется природа и местонахождение подозреваемой ошибки в программе;
  • фиксируется или исправляется ошибка.

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

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

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