ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ visual studio...

96
Федеральное агентство по образованию Государственное образовательное учреждение высшего профессионального образования Ульяновский государственный технический университет ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ В СРЕДЕ VISUAL STUDIO 2005 Учебное пособие для студентов, обучающихся по специальности 08080165 Составители О. Н. Евсеева А. Б. Шамшев Ульяновск 2008

Upload: others

Post on 03-Jun-2020

56 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

Федеральное агентство по образованию Государственное образовательное учреждение высшего

профессионального образования Ульяновский государственный технический университет

ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ В СРЕДЕ

VISUAL STUDIO 2005

Учебное пособие

для студентов, обучающихся по специальности 08080165

Составители О. Н. Евсеева А. Б. Шамшев

Ульяновск 2008

Page 2: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

2

УДК 681.3(075) ББК 32.81 О-11

Рецензенты: зав. кафедрой «Информационные технологии» к.ф-м.н, доцент М. А. Волков;

начальник отдела информационных технологий Филиала нацио-нального банка ОАО «ТРАСТ» г. Ульяновск И. В. Подгорный

Утверждено редакционно-издательским советом университета в качестве учебного пособия

ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ В СРЕДЕ VISUAL STUDIO 2005 : учебное пособие / сост. О. Н. Евсеева, А. Б. Шамшев. – Ульяновск : УлГТУ, 2008. – 96 с. ISBN 978-5-9795-0348-6

Пособие конспективно представляет возможности среды Visual Studio 2005

по организации отладки и тестирования приложений, написанных на С#, и со-держит краткое введение в дисциплину тестирования на примерах программ, разработанных на C#.

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

УДК 681.3(075)

ББК 32.81

Учебное издание

ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ В СРЕДЕ VISUAL STUDIO 2005

Учебное пособие

Составители ЕВСЕЕВА Ольга Николаевна ШАМШЕВ Анатолий Борисович

Редактор М. Штаева

Подписано в печать 29.12.2008. Формат 60×84/16. Усл. печ. л. 5,58. Тираж 100 экз.

Ульяновский государственный технический университет,

432027, г. Ульяновск, Сев. Венец, 32.

Типография УлГТУ, 432027, г. Ульяновск, Сев. Венец, 32.

© Евсеева О. Н., Шамшев А. Б., 2008 ISBN 978-5-9795-0348-6 © Оформление. УлГТУ, 2008

О-11

Page 3: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

3

ОГЛАВЛЕНИЕ Предисловие ............................................................................................................... 5 Введение ...................................................................................................................... 7

Корректность и устойчивость программных систем .................................... 7 Тестирование – способ обеспечения качества ............................................... 7 Правила и законы разработки программных продуктов .............................. 8

1. Надежность и корректность кода .................................................................... 10 1.1. Создание надежного кода .............................................................................. 10 1.2. Искусство отладки .......................................................................................... 11

1.2.1. Отладочная печать и условная компиляция .......................................... 11 1.2.2. Классы Debug и Trace .............................................................................. 15 1.2.3. Метод Флойда и утверждения Assert ..................................................... 17 1.2.4. Классы StackTrace и BooleanSwitch........................................................ 19

1.3. Обработка исключительных ситуаций ......................................................... 20 1.3.1. Обработка исключений в языках C/C++................................................ 20 1.3.2. Схема обработки исключений в C#........................................................ 21 Выбрасывание исключений. Создание объектов Exception ....................... 21 Захват исключения.......................................................................................... 22 Параллельная работа обработчиков исключений........................................ 23 Блок finally ....................................................................................................... 24

1.3.3. Схема Бертрана обработки исключительных ситуаций....................... 24 1.4. Контрольные вопросы и упражнения ........................................................... 29

2. Основные понятия тестирования .................................................................... 30 2.1. Терминология .................................................................................................. 30

Пример поиска и исправления ошибки. ....................................................... 31 2.2. Организация тестирования ............................................................................ 32

2.2.1. Пример сравнения словесного описания пункта спецификации с результатом выполнения фрагмента кода ....................................................... 33 2.2.2. Пример вставки операторов протоколирования промежуточных результатов .......................................................................................................... 33 2.2.3. Пример пошагового выполнения программы ....................................... 33 2.2.4. Пример выполнения программы с заказанными контрольными точками и анализом трасс и дампов ................................................................. 34 2.2.5. Пример обратного выполнения программы .......................................... 36 2.2.6. Сквозной пример тестирования .............................................................. 36 Спецификация программы ............................................................................. 38 Разработка тестов ............................................................................................ 38 Анализ тестовых случаев ............................................................................... 38 Выполнение тестовых случаев ...................................................................... 39 Оценка результатов выполнения программы на тестах.............................. 39

2.3. Основные проблемы тестирования ............................................................... 39 Простой пример............................................................................................... 39

2.4. Контрольные вопросы и упражнения ........................................................... 42

Page 4: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

4

3. Критерии выбора тестов.................................................................................... 44 3.1. Требования к идеальному критерию тестирования .................................... 44 3.2. Классы критериев ........................................................................................... 44

3.2.1. Структурные критерии (класс I) ............................................................. 44 3.2.2. Функциональные критерии (класс II)..................................................... 46 Частные виды функциональных критериев. ................................................ 46 Пример применения функциональных критериев....................................... 47

3.2.3. Стохастические критерии (класс III)...................................................... 49 Критерии стохастического тестирования..................................................... 50

3.2.4. Мутационный критерий (класс IV). ....................................................... 51 Пример применения мутационного критерия.............................................. 52

3.3. Оценка полноты тестирования программы по выбранному критерию .... 53 3.3.1. Плоская модель программы .................................................................... 54 3.3.2. Иерархическая модель программы......................................................... 56 3.3.3 Методика интегральной оценки тестированности................................. 59

3.4. Контрольные вопросы и упражнения ........................................................... 60 4. Разновидности тестирования............................................................................ 61

4.1. Модульное тестирование ............................................................................... 61 4.1.1. Тестирование на основе потока управления ......................................... 62 4.1.2. Тестирование на основе потока данных ................................................ 62 4.1.3. Методы проектирования тестовых путей для достижения заданной степени тестированности................................................................................... 63 4.1.4. Пример модульного тестирования ......................................................... 64

4.2. Интеграционное тестирование ...................................................................... 66 4.2.1. Особенности интеграционного тестирования для процедурного программирования.............................................................................................. 69 4.2.2. Особенности интеграционного тестирования для объектно-ориентированного программирования ............................................................. 71 4.2.3. Пример интеграционного тестирования ................................................ 78

4.3. Системное тестирование ................................................................................ 80 4.3.1. Категории тестов системного тестирования.......................................... 80 4.3.2. Пример системного тестирования приложения .................................... 81

4.4. Регрессионное тестирование ......................................................................... 83 4.4.1. Пример регрессионного тестирования ................................................... 84

4.5. Комбинирование уровней тестирования ...................................................... 85 4.6. Автоматизация тестирования ........................................................................ 86 4.7. Издержки тестирования ................................................................................. 90 4.8. Контрольные вопросы и упражнения ........................................................... 91

Заключение ............................................................................................................... 92 Библиографический список .................................................................................. 93 Предметный указатель........................................................................................... 94

Page 5: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

5

ПРЕДИСЛОВИЕ

Одной из базовых дисциплин в программе подготовки информатиков по специальности 080801 «Прикладная информатика» является курс «Высоко-уровневые методы информатики и программирования», содержание которого определяется выпиской из Государственного Образовательного Стандарта Высшего Профессионального Образования (ГОС ВПО).

Выписка из ГОС ВПО

Индекс Наименование дисциплины и ее основные разделы Всего часов

ОПД.Ф.01 Высокоуровневые методы информатики и программирования Новейшие направления в области создания технологий программи-рования. Законы эволюции программного обеспечения. Программирование в средах современных информационных систем: создание модульных программ, элементы теории модульного про-граммирования, объектно-ориентированное проектирование и про-граммирование. Объектно-ориентированный подход к проектированию и разработке программ: сущность объектно-ориентированного подхода; объект-ный тип данных; переменные объектного типа; инкапсуляция; насле-дование; полиморфизм; классы и объекты. Конструкторы и деструкторы. Особенности программирования в оконных операционных средах. Основные стандартные модули, обеспечивающие работу в оконной операционной среде. Среда раз-работки; система окон разработки; система меню. Отладка и тестиро-вание программ. Основы визуального программирования. Размеще-ние нового компонента. Реакция на события. Компоненты; использо-вание компонентов.

Настоящее пособие посвящено одной из важных частей третьего раздела

дисциплины «Объектно-ориентированный подход к проектированию и разра-ботке программ» – это «Отладка и тестирование программ», который иллюст-рируется примерами программ, написанных на языке С# под Windows на плат-форме .NET. Пособие раскрывает следующие дидактические единицы: • понятие надежности программного кода; • методы отладки программ; • обработка исключительных ситуаций; • основные понятия тестирования; • критерии выбора и виды тестирования.

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

Page 6: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

6

Задачей учебного пособия является раскрытие выделенных дидактиче-ских единиц на простых и конкретных примерах использования библиотек классов в Windows-приложениях на основе платформы .NET на языке C#, что позволит студентам освоить базовые принципы и методы технологии програм-мирования, отладки и тестирования программ на современном уровне и помо-жет применить их в курсовом и дипломном проектировании.

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

Первый раздел посвящен вопросам отладки программ, возможностям среды и языка разработки в решении задач отслеживания и поиска программ-ных ошибок с примерами, написанными на языке C#.

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

Третий раздел полностью посвящен критериям выбора тестов. Здесь представлены требования к идеальному тесту, четыре класса критериев и мето-дики оценки оттестированности ПО.

Четвертый раздел представляет различные виды тестирования. Для успешного изучения материала достаточно знание основ программи-

рования и желательны начальные навыки программирования на языке C#. Со-ветуем ознакомиться с материалом пособий [9, 15].

Среди учебников, посвященных подготовке тестировщиков, рекомендуем обратить внимание на книги [4, 5, 7, 10, 12], посвященные передаче опыта про-мышленного тестирования студентам и аспирантам, выбравшим своей специ-альностью профессиональное программирование.

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

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

Page 7: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

7

ВВЕДЕНИЕ

Корректность и устойчивость программных систем Корректность и устойчивость – два основных качества программной сис-

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

Корректность – это способность программной системы работать в стро-гом соответствии со своей спецификацией. Отладка – процесс, направленный на достижение корректности.

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

Почему так трудно создавать корректные и устойчивые программные системы? Все дело в сложности разрабатываемых систем. Когда в 60-х годах прошлого века фирмой IBM создавалась операционная система OS-360, то на ее создание потребовалось 5000 человеко-лет, и проект по сложности сравнивался с проектом высадки первого человека на Луну. Сложность нынешних сетевых операционных систем, систем управления хранилищами данных, прикладных систем программирования на порядки превосходит сложность OS-360, так что, несмотря на прогресс, достигнутый в области технологии программирования, проблемы, стоящие перед разработчиками, не стали проще.

Тестирование – способ обеспечения качества Качество программного продукта характеризуется набором свойств, оп-

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

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

Page 8: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

8

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

Классический подход проектирования программных продуктов выделяет три основные фазы процесса разработки программ, это такие фазы, как: 1. Дизайн приложения – 2. Разработка кода – 3. Тестирование. Как одна из ос-новных фаз процесса разработки программного продукта тестирование харак-теризуется достаточно большим вкладом в суммарную трудоемкость разработ-ки продукта. Широко известна оценка распределения трудоемкости между фа-зами создания программного продукта: 40% – 20% – 40% [4], из чего следует, что наибольший эффект в снижении трудоемкости может быть получен прежде всего на фазах 1 и 3. Поэтому основные вложения в автоматизацию или генера-цию кода следует осуществлять, прежде всего, на этих фазах. Хотя в современ-ном индустриальном программировании автоматизация тестирования является широко распространенной практикой, в то же время технология верификации требований и спецификаций пока делает только свои первые шаги.

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

Правила и законы разработки программных продуктов

Жизненный цикл программной системы Под «жизненным циклом» понимается период от замысла программного

продукта до его «кончины». Обычно рассматриваются следующие этапы этого процесса:

I. Проектирование II. Разработка

III. Развертывание IV. Сопровождение Все это называется циклом, поскольку на каждом этапе возможен возврат

к предыдущим этапам. В объектной технологии этот процесс является бесшов-ным, все этапы которого тесно переплетены. Не следует рассматривать его как однонаправленный – от проектирования к сопровождению. Чаще всего, ситуа-ция обратная: уже существующая реализация системы, прошедшая сопровож-дение, и существующие библиотеки компонентов оказывают решающее влия-ние на то, какой будет новая система, каковы будут ее спецификации.

Обратите внимание на следующие типовые правила, характерные для процесса разработки программного обеспечения [1,14]:

Page 9: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

9

• Этапу проектирования следует уделять самое пристальное внимание. Успех дела во многом определяется первым этапом. Нет смысла торопиться с пе-реходом на последующие этапы, пока не составлены ясные и четкие специ-фикации. Ошибки этого этапа – самые дорогие и трудно исправляемые.

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

• Разработка не начинается «с нуля». Только используя уже готовые компо-ненты, можно своевременно создать новую систему. Работая над проектом, нужно думать о будущем. Разрабатывать компоненты таким образом, чтобы их можно было использовать в последующих проектах.

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

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

Три закона программирования Первый закон (закон для разработчика). Корректность системы – недос-

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

Второй закон (закон для пользователя). Не бывает некорректных систем. Каждая появляющаяся ошибка при эксплуатации системы – это следствие не-знания спецификации системы.

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

Page 10: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

10

1. НАДЕЖНОСТЬ И КОРРЕКТНОСТЬ КОДА Что надо делать для создания корректного и устойчивого программного

продукта? Как минимум, необходимо:

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

• отладить этот код; • предусмотреть в нем обработку исключительных ситуаций.

1.1. Создание надежного кода Большинство вопросов, затрагиваемых в этом пособии, в том числе и

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

Для повышения надежности нужно уменьшить сложность системы, и главное в этом процессе – это повторное использование. В идеале большая часть системы должна быть собрана из уже готовых компонентов. Объектная технология проектирования вносит свой вклад в повышение надежности кода. Наследование и универсализация позволяют, не изменяя уже существующие классы, создать новые классы, новые типы данных, придающие проектируемой системе новые свойства при минимальных добавлениях нового кода. Статиче-ский контроль типов позволяет выявить многие ошибки еще на этапе компиля-ции. Динамическое связывание и полиморфизм позволяют автоматически включать объекты классов-потомков в уже существующие схемы работы – ме-тоды родителя могут вызывать методы потомков, ничего не зная о появлении этих новых потомков. Автоматическая сборка мусора позволяет снять с разра-ботчика обязанности управления освобождением памяти и предотвратить появ-ление крайне неприятных и опасных ошибок, связанных с некорректным уда-лением объектов.

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

Page 11: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

11

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

невозможно. Роль тестировщиков в современном процессе разработки про-граммного обеспечения велика.

Отладка – это некоторый «детективный» процесс. Программа, в которую внесены изменения, подозревается в том, что она работает некорректно. Пре-зумпция невиновности здесь не применима. Если удается предъявить тест, на котором программа дает неверный результат, то доказано, что подозрения вер-ны. Втайне мы всегда надеемся, что программа работает правильно. Но цель тестирования другая – попытаться опровергнуть это предположение. Отладка может доказать некорректность программы, но она не может доказать ее пра-вильность. Отладка не гарантирует корректности программы, даже если все тесты пройдены успешно. Искусное тестирование создает высокую степень уверенности в корректности программы.

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

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

Есть и другая классификация. Средства, используемые при отладке, мож-но разделить на инструментарий, предоставляемый средой разработки Visual Studio 2005, и программные средства, предоставляемые языком и специальны-ми классами библиотеки FCL (Framework Class Library). Начнем рассмотрение с программных средств.

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

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

Page 12: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

12

Хотелось бы иметь легкий механизм управления отладочными методами, позволяющий включать при необходимости те или иные инструменты. Для это-го можно воспользоваться механизмом условной компиляции, встроенным в язык C#. Этот механизм состоит из двух частей. К проекту, точнее, к конфигу-рации проекта, можно добавить специальные константы условной компиляции. Вызов отладочного метода может быть сделан условным. Если соответствую-щая константа компиляции определена, то происходит компиляция вызова ме-тода и он будет вызываться при выполнении проекта. Если же константа не оп-ределена (выключена), то вызов метода даже не будет компилироваться и ника-ких динамических проверок – вызывать метод или нет – делаться не будет.

Как задавать константы компиляции? Напомним, что проекты C# в Visual Studio существуют в нескольких конфигурациях. В ходе работы с проектом можно легко переключаться с одной конфигурации на другую, после чего она становится активной, можно изменять настройки конфигурации, можно создать собственные конфигурации проекта. По умолчанию проект создается в двух конфигурациях – Debug и Release, первая из которых предназначена для отлад-ки, вторая – для окончательных вычислений. Первая не предполагает оптими-зации и в ней определены две константы условной компиляции – DEBUG и TRACE, во второй – определена только константа TRACE. Отладочная версия может содержать вызовы, зависящие от константы DEBUG, которые будут от-сутствовать в финальной версии. Используя страницу свойств, к конфигурации проекта можно добавлять новые константы компиляции.

Можно также задавать константы условной компиляции в начале модуля проекта вперемешку с предложениями using. Предложение define позволяет оп-ределить новую константу: #define COMPLEX

Как используются константы условной компиляции? В языке С++, где имеется подобный механизм, определен специальный препроцессорный IF-оператор, анализирующий, задана константа или нет. В языке C# использу-ется вместо этого гораздо более мощный механизм. Как известно, методы C# обладают набором атрибутов, придающих методу разные свойства. Среди встроенных атрибутов языка есть атрибут Conditional, аргументом которого яв-ляется строка, задающая имя константы: [Conditional ("COMPLEX")] public void ComplexMethod () {...}

Если константа условной компиляции COMPLEX определена для актив-ной конфигурации проекта, то произойдет компиляция вызова метода ComplexMethod, когда он встретится в тексте программы. Если же такая кон-станта отсутствует в конфигурации, то вызов метода игнорируется.

На методы, для которых возможно задание атрибута Conditional, наклады-вается ряд ограничений. Метод не должен быть: • функцией, возвращающей значение; • методом интерфейса;

Page 13: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

13

• методом с модификатором override. Возможно его задание для virtual-метода. В этом случае атрибут наследуется методами потомков.

Атрибут Conditional, обычно с аргументом DEBUG, сопровождает моду-ли, написанные для целей отладки. Но использование этого атрибута не огра-ничивается интересами отладки. Зачастую проект может использоваться в не-скольких вариантах, например, в облегченном и более сложном. Методы, вы-зываемые в сложных ситуациях, например, ComplexMethod, имеющий атрибут условной компиляции, будут вызываться только в той конфигурации, где опре-делена константа COMPLEX.

Приведем пример работы с отладочными методами. Рассмотрим класс, в котором определены три метода, используемые при отладке: public class DebugPrint { [Conditional("DEBUG")] public static void PrintEntry(string name) { Console.WriteLine("Начал работать метод " + name); } [Conditional("DEBUG")] public static void PrintExit(string name) { Console.WriteLine("Закончил работать метод " + name); } [Conditional("DEBUG")] public static void PrintObject(object obj, string name) { Console.WriteLine("Объект {0}: {1}", name, obj.ToString()); } }

В классе Testing определено поле класса: int state = 1; и группа методов: public void TestDebugPrint() { DebugPrint.PrintEntry("Testing.TestDebugPrint"); PubMethod(); DebugPrint.PrintObject(state, "Testing.state"); DebugPrint.PrintExit("Testing.TestDebugPrint"); }

Page 14: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

14

private void InMethod1() { DebugPrint.PrintEntry("InMethod1"); // body DebugPrint.PrintExit("InMethod1"); } private void InMethod2() { DebugPrint.PrintEntry("InMethod2"); // body DebugPrint.PrintExit("InMethod2"); } public void PubMethod() { DebugPrint.PrintEntry("PubMethod"); InMethod1(); state++; InMethod2(); DebugPrint.PrintExit("PubMethod"); }

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

При переходе к конфигурации Release отладочная информация появлять-ся не будет.

Рис. 1. Трассировка вычислений в процессе отладки

Page 15: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

15

1.2.2. Классы Debug и Trace Атрибут условной компиляции Conditional характеризует метод, но не от-

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

Классы Debug и Trace – это классы-двойники. Оба они находятся в про-странстве имен Diagnostics, имеют идентичный набор статических свойств и методов с идентичной семантикой. В чем же разница? Методы класса Debug имеют атрибут условной компиляции с константой DEBUG, действуют только в Debug-конфигурации проекта и игнорируются в Release-конфигурации. Ме-тоды класса Trace включают два атрибута Conditional с константами DEBUG и TRACE и действуют в обеих конфигурациях.

Одна из основных групп методов этих классов – методы печати данных: Write, WriteIf, WriteLine, WriteLineIf. Методы перегружены, в простейшем случае позволяют выводить некоторое сообщение. Методы со словом If могут сделать печать условной, задавая условие печати в качестве первого аргумента метода, что иногда крайне полезно. Методы со словом Line дают возможность допол-нять сообщение символом перехода на новую строку.

По умолчанию методы обоих классов направляют вывод в окно Output. Однако это не всегда целесообразно, особенно для Release-конфигурации. За-мечательным свойством методов классов Debug и Trace является то, что они мо-гут иметь много «слушателей», направляя вывод каждому из них. Свойство Listeners этих классов возвращает разделяемую обоими классами коллекцию слушателей – TraceListenerCollection. Как и всякая коллекция, она имеет ряд ме-тодов для добавления новых слушателей: Add, AddRange, Insert – и возможность удаления слушателей: Clear, Remove, RemoveAt и другие методы. Объекты этой коллекции в качестве предка имеют абстрактный класс TraceListener.

Библиотека NFCL включает три неабстрактных потомка этого класса: DefaultTraceListener – слушатель этого класса добавляется в коллекцию по умолчанию, направляет вывод, поступающий при вызове методов классов Debug и Trace, в окно Output; EventLogTraceListener – посылает сообщения в журнал событий Windows; TextWriterTraceListener – направляет сообщения объектам класса TextWriter или Stream; обычно один из объектов этого класса направляет вывод на консоль, другой – в файл.

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

Page 16: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

16

Помимо свойства Listeners и методов печати, классы Debug и Trace имеют и другие важные методы и свойства:

Assert и Fail, проверяющие корректность хода вычислений – о них мы по-говорим особо;

Flush – метод, отправляющий содержание буфера слушателю (в файл, на консоль и так далее). Следует помнить, что данные буферизуются, поэтому применение метода Flush зачастую необходимо, иначе метод может завершить-ся, а данные останутся в буфере;

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

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

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

Рассмотрим пример работы, в котором отладочная информация направ-ляется в разные каналы – окно вывода, консоль, файл: public void Optima() { double x, y = 1; x = y – 2*Math.Sin(y); FileStream f = new FileStream("Debuginfo.txt", FileMode.Create, FileAccess.Write); TextWriterTraceListener writer1 = new TextWriterTraceListener(f); TextWriterTraceListener writer2 = new TextWriterTraceListener(System.Console.Out); Trace.Listeners.Add(writer1); Debug.Listeners.Add(writer2); Debug.WriteLine("Число слушателей:" + Debug.Listeners.Count); Debug.WriteLine("автоматический вывод из буфера:" + Trace.AutoFlush); Trace.WriteLineIf(x < 0, "Trace: " + "x= " + x.ToString() + " y = " + y); Debug.WriteLine("Debug: " + "x= " + x.ToString() + " y = " + y); Trace.Flush(); f.Close(); }

В коллекцию слушателей вывода к слушателю по умолчанию добавляют-ся еще два слушателя класса TextWriterTraceListener. Отметим, что хотя они до-бавляются методами разных классов Debug и Trace, попадают они в одну кол-лекцию. Как и обещано, один из этих слушателей направляет вывод в файл, другой на консоль. На рис. 2 на фоне окна кода показаны три канала вывода – окно Output, консоль, файл, содержащие одну и ту же информацию.

Page 17: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

17

Рис. 2. Три канала вывода

1.2.3. Метод Флойда и утверждения Assert Лет двадцать назад большие надежды возлагались на формальные методы

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

Одним из методов доказательства правильности программ был метод Флойда, при котором программа разбивалась на участки, окаймленные утвер-ждениями – булевскими выражениями (предикатами). Истинность начального предиката должна была следовать из входных данных программы. Затем для каждого участка доказывалось, что из истинности предиката, стоящего в начале

Page 18: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

18

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

Схема Флойда используется на практике, по крайней мере, программи-стами, имеющими вкус к строгим методам доказательства. Утверждения стано-вятся частью программного текста. Само доказательство может и не проводить-ся: чаще всего у программиста есть уверенность в справедливости расставлен-ных утверждений и убежденность, что при желании он мог бы провести и стро-гое доказательство. В C# эта схема поддерживается тем, что классы Debug и Trace имеют метод Assert, аргументом которого является утверждение. Что происходит, когда вычисление достигает соответствующей точки и вызывается метод Assert? Если истинно булево выражение в Assert, то вычисления продол-жаются, не оказывая никакого влияния на нормальный ход вычислений. Если оно ложно, то корректность вычислений под сомнением, их выполнение приос-танавливается и появляется окно с уведомлением о произошедшем событии, что показано на рис. 3.

Рис. 3. Нарушение утверждения Assert

В этой ситуации у программиста есть несколько возможностей: – прервать выполнение, нажав кнопку «Прервать» (Abort); – перейти в режим отладки «Повтор» (Retry); – продолжить вычисления, проигнорировав уведомление, «Пропустить».

В последнем случае сообщение о возникшей ошибке будет послано всем слушателям коллекции TraceListenerCollection.

Рассмотрим простой пример, демонстрирующий нарушение утвержде-ния: public void WriteToFile() { Stream myFile = new FileStream("TestFile.txt", FileMode.Create, FileAccess.Write); TextWriterTraceListener myTextListener = new TextWriterTraceListener(myFile); int y = Debug.Listeners.Add(myTextListener); TextWriterTraceListener myWriter = new TextWriterTraceListener(System.Console.Out);

Page 19: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

19

Trace.Listeners.Add(myWriter); Trace.AutoFlush = true; Trace.WriteLine("автоматический вывод из буфера:" + Trace.AutoFlush); int x = 22; Trace.Assert(x <= 21, "Перебор"); myWriter.WriteLine("Вывод только на консоль"); //Trace.Flush(); //Вывод только в файл byte[] buf = {(byte) 'B', (byte) 'y'}; myFile.Write(buf, 0, 2); myFile.Close(); }

Рис. 4. Файл с записью сообщения о нарушении утверждения Assert

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

Вариацией метода Assert является метод Fail, всегда приводящий к появ-лению окна с сообщением о нарушении утверждения, проверка которого осу-ществляется обычным программным путем.

1.2.4. Классы StackTrace и BooleanSwitch В библиотеке NFCL имеются и другие классы, полезные при отладке.

Класс StackTrace позволяет получить программный доступ к стеку вызовов. Класс BooleanSwitch предоставляет механизм, аналогичный константам услов-ной компиляции. Он разрешает определять константы, используемые позже в методе условной печати WriteIf классов Debug и Trace. Мощь этого механизма в

Page 20: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

20

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

1.3. Обработка исключительных ситуаций Какой бы надежный код ни был написан, сколь бы тщательной ни была

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

Что делать при возникновении исключительной ситуации? Конечно, все-гда есть стандартный способ – сообщить о возникшей ошибке и прервать вы-полнение программы. Понятно, что это приемлемо лишь для безобидных при-ложений; даже для компьютерных игр этот способ не годится, что уж говорить о критически важных приложениях!

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

1.3.1. Обработка исключений в языках C/C++ Для стиля программирования на языке C характерно описание методов

класса как булевых функций, возвращающих true в случае нормального завер-шения метода и false – при возникновении исключительной ситуации. Вызов метода встраивался в If-оператор, обрабатывающий ошибку в случае неуспеха завершения метода: bool MyMethod(...){...} if !MyMethod(){// обработка ошибки} {//нормальное выполнение}

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

Поэтому в C/C++ применяется схема try/catch блоков, суть которой в сле-дующем. Участок программы, в котором может возникнуть исключительная ситуация, оформляется в виде охраняемого try-блока. Если при его выполнении возникает исключительная ситуация, то происходит прерывание выполнения try-блока c классификацией исключения. Это исключение начинает обрабаты-

Page 21: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

21

вать один из catch-блоков, соответствующий типу исключения. В C/C++ при-меняются две такие схемы. Одна из них – схема с возобновлением – соответст-вует так называемым структурным, или С-исключениям. Вторая схема – без во-зобновления – соответствует С++ исключениям. В первой схеме обработчик исключения – catch-блок – возвращает управление в некоторую точку try-блока. Во второй схеме управление не возвращается в try-блок.

1.3.2. Схема обработки исключений в C# Язык C# наследовал схему исключений языка С++, внеся в нее свои кор-

рективы. Рассмотрим схему подробнее и начнем с синтаксиса конструкции try-catch-finally: try {...} catch (T1 e1) {...} ... catch(Tk ek) {...} finally {...}

Всюду в тексте модуля, где синтаксически допускается использование блока, этот блок можно сделать охраняемым, добавив ключевое слово try. Вслед за try-блоком могут следовать catch-блоки, называемые блоками-обработчиками исключительных ситуаций, их может быть несколько, они мо-гут и отсутствовать. Завершает эту последовательность finally-блок – блок за-вершения (финализации), который также может отсутствовать. Вся эта конст-рукция может быть вложенной – в состав try-блока может входить конструкция try-catch-finally.

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

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

Синтаксически оператор throw имеет вид: throw[выражение]

Выражение throw задает объект класса, являющегося наследником класса Exception. Обычно это выражение new, создающее новый объект. Если оно от-сутствует, то повторно выбрасывается текущее исключение. Если исключение выбрасывается операционной системой, то она сама классифицирует исключе-ние, создает объект соответствующего класса и автоматически заполняет его поля.

Page 22: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

22

В рассматриваемой нами модели исключения являются объектами, класс которых представляет собой наследника класса Exception. Этот класс и много-численные его наследники является частью библиотеки FCL, хотя и разбросаны по разным пространствам имен. Каждый класс задает определенный тип ис-ключения в соответствии с классификацией, принятой в Framework .Net. Вот лишь некоторые классы исключений из пространства имен System: ArgumentException, ArgumentOutOfRangeException, ArithmeticException, BadImage-FormatException, DivideByZeroException, OverflowException. В пространстве имен System.IO собраны классы исключений, связанных с проблемами ввода-вывода: DirectoryNotFoundException, FileNotFoundException и многие другие. Имена всех классов исключений заканчиваются словом Exception. Разрешается создавать собственные классы исключений, наследуя их от класса Exception.

При выполнении оператора throw создается объект te, класс TE которого характеризует текущее исключение, а поля содержат информацию о возникшей исключительной ситуации. Выполнение оператора throw приводит к тому, что нормальный процесс вычислений на этом прекращается. Если это происходит в охраняемом try-блоке, то начинается этап «захвата» исключения одним из об-работчиков исключений.

Захват исключения Блок catch – обработчик исключения имеет следующий синтаксис:

catch (T e) {...} Класс T, указанный в заголовке catch-блока, должен принадлежать клас-

сам исключений. Блок catch с формальным аргументом e класса T потенциально способен захватить текущее исключение te класса TE, если и только если объект te совместим по присваиванию с объектом e. Другими словами, потенциальная способность захвата означает допустимость присваивания e = te, что возможно, когда класс TE является потомком класса T. Обработчик, класс T которого явля-ется классом Exception, является универсальным обработчиком, потенциально он способен захватить любое исключение, поскольку все они являются его по-томками.

Потенциальных захватчиков может быть много, исключение захватывает лишь один – тот из них, кто стоит первым в списке проверки. Каков порядок проверки? Он довольно естественный. Вначале проверяются обработчики в по-рядке следования их за try-блоком, и первый потенциальный захватчик стано-вится активным, захватывая исключение и выполняя его обработку. Отсюда становится ясно, что порядок следования в списке catch-блоков крайне важен. Первыми идут наиболее специализированные обработчики, далее по мере воз-растания универсальности. Так, вначале должен идти обработчик исключения DivideByZeroException, а уже за ним – ArithmeticException. Универсальный обра-ботчик, если он есть, должен стоять последним. За этим наблюдает статический контроль типов. Если потенциальных захватчиков в списке catch-блоков нет (сам список может отсутствовать), то происходит переход к списку обработчи-

Page 23: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

23

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

Рис. 5. Цепочка вызовов, хранящаяся в стеке вызовов

Исключение возникло в последнем вызванном методе цепочки – на ри-сунке метод r5. Если у этого метода не нашлось обработчиков события, способ-ных обработать исключение, то это пытается сделать метод r4, вызвавший r5. Если вызов r5 находится в охраняемом блоке метода r4, то начнет проверяться список обработчиков в охраняемом блоке метода r4. Этот процесс подъема по списку вызовов будет продолжаться, пока не будет найден обработчик, способ-ный захватить исключение, или не будет достигнута начальная точка – проце-дура Main. Если и в ней нет потенциального захватчика исключения, то срабо-тает стандартный обработчик, прерывающий выполнение программы с выдачей соответствующего сообщения.

Параллельная работа обработчиков исключений Обработчику исключения – catch-блоку, захватившему исключение, – пе-

редается текущее исключение. Анализируя свойства этого объекта, обработчик может понять причину, приведшую к возникновению исключительной ситуа-ции, попытаться ее исправить, и в случае успеха, продолжить вычисления. От-метим, в принятой C# схеме без возобновления обработчик исключения не воз-вращает управление try-блоку, а сам пытается решить проблемы. После завер-шения catch-блока выполняются операторы текущего метода, следующие за конструкцией try-catch-finally.

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

Page 24: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

24

бросить текущее исключение или выбросить новое исключение, содержащее дополнительную информацию.

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

Блок finally До сих пор ничего не было сказано о важном участнике схемы обработки

исключений – блоке finally. Так как рассматриваемая схема является схемой без возобновления, то это означает, что управление вычислением неожиданно по-кидает try-блок. Просто так в большинстве случаев этого делать нельзя – нужно выполнить определенную чистку. Прежде всего, удаляются все локальные объ-екты, созданные в процессе работы блока. В языке С++ эта работа требовала вызова деструкторов объектов. В C#, благодаря автоматической сборке мусора, освобождением памяти можно не заниматься, достаточно освободить стек. Но в блоке try могли быть заняты другие ресурсы – открыты файлы, захвачены неко-торые устройства. Освобождение ресурсов, занятых try-блоком, выполняет finally-блок. Если он присутствует, он выполняется всегда, сразу же после за-вершения работы try-блока, как бы последний ни завершился. Блок try может завершиться вполне нормально без всяких происшествий, и управление достиг-нет конца блока, выполнение может быть прервано оператором throw, управле-ние может быть передано другому блоку из-за выполнения таких операторов как goto, return – во всех этих случаях, прежде чем управление будет передано по предписанному назначению (в том числе, прежде чем произойдет захват ис-ключения), предварительно будет выполнен finally-блок, который освобождает ресурсы, занятые try-блоком, а параллельно будет происходить освобождение стека от локальных переменных.

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

обладает одним существенным изъяном – ее можно применить некорректно. Она позволяет, в случае возникновения исключительной ситуации, уведомить о ее возникновении и спокойно продолжить работу, что, в конечном счете, при-ведет к неверным результатам. Из двух зол – прервать вычисление с уведомле-нием о невозможности продолжения работы или закончить вычисления с оши-бочным результатом вычисления – следует выбирать первое. Некорректно при-мененная схема C# приведет к ошибочным результатам.

Бертран Мейер [11] предложил следующую схему обработки исключи-тельных ситуаций. В основе ее лежит подход к проектированию программной системы на принципах «Проектирования по Контракту». Модули программной системы, вызывающие друг друга, заключают между собой контракты. Вызы-вающий модуль обязан обеспечить истинность предусловия, необходимого для корректной работы вызванного модуля. Вызванный модуль обязан гарантиро-

Page 25: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

25

вать истинность постусловия по завершении своей работы. Если в вызванном модуле возникает исключительная ситуация, то это означает, что он не может выполнить свою часть контракта. Что должен делать обработчик исключитель-ной ситуации? У него только две возможности – Retry и Rescue. Первая (Retry) – попытаться внести некоторые коррективы и вернуть управление охраняемому модулю, который может предпринять очередную попытку выполнить свой кон-тракт. Модуль может, например, в следующей попытке запустить другой алго-ритм, использовать другой файл, другие данные. Если все закончится успешно, и работа модуля будет соответствовать его постусловию, то появление исклю-чительной ситуации можно рассматривать как временные трудности, успешно преодоленные. Если же ситуация возникает вновь и вновь, тогда обработчик события применяет вторую стратегию (Rescue), выбрасывая исключение и пе-редавая управление вызывающему модулю, который и должен теперь попы-таться исправить ситуацию. Важная тонкость в схеме, предложенной Бертра-ном, состоит в том, что исключение, выбрасываемое обработчиком, следует рассматривать не как панику, не как бегство, а как отход на заранее подготов-ленные позиции. Обработчик исключения должен позаботиться о восстановле-нии состояния, предшествующего вызову модуля, который привел к исключи-тельной ситуации, и это гарантирует нахождение всей системы в корректном состоянии.

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

Приведем возможную реализацию такой схемы на C# и краткие коммен-тарии к этой процедуре, которую можно рассматривать как некоторый образец организации обработки исключительной ситуации: public void Pattern() { do { try { bool Danger = false; Success = true; MakeJob(); Danger = CheckDanger(); if (Danger) throw (new MyException()); MakeLastJob(); } catch (MyException me) {

Page 26: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

26

if (count > maxcount) throw(new MyException("Три попытки были безуспешны")); Success = false; count++; //корректировка ситуации Console.WriteLine("Попытка исправить ситуацию!"); level += 1; } } while (!Success); }

Конструкция try-catch блоков помещается в цикл do-while(!Success), за-вершаемый в случае успешной работы охраняемого блока, за чем следит булева переменная Success.

В данном образце предполагается, что в теле охраняемого блока анализи-руется возможность возникновения исключительной ситуации и, в случае об-наружения опасности, выбрасывается собственное исключение, класс которого задан программно. В соответствии с этим тело try-блока содержит вызов метода MakeJob, выполняющего некоторую часть работы, после чего вызывается метод CheckDanger, выясняющий, не возникла ли опасность нарушения специфика-ции, и может ли работа быть продолжена. Если все нормально, то выполняется метод MakeLastJob, выполняющий заключительную часть работы. Управление вычислением достигает конца try-блока, он успешно завершается и, поскольку остается истинной переменная Success, значение true которой установлено в начале try-блока, то цикл while, окаймляющий охраняемый блок и его обработ-чиков исключений, также успешно завершается.

Если в методе CheckDanger выясняется, что нормальное продолжение вы-числений невозможно, то выбрасывается исключение класса MyException. Оно перехватывается обработчиком исключения, стоящим за try-блоком, поскольку класс MyException указан как класс формального аргумента.

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

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

Как видите, эта схема реализует два корректных исхода обработки ис-ключительной ситуации – Retry и Rescue – повтор с надеждой выполнить обяза-тельства и передачу управления вызывающей программе, чтобы она предпри-

Page 27: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

27

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

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

Определим первым делом собственный класс исключений: public class MyException : Exception { public MyException() { }

public MyException(string message) : base(message) { }

public MyException(string message, Exception e) : base(message, e) { } }

Минимум того, что нужно сделать, определяя свои исключения, – это за-дать три конструктора класса, вызывающие соответствующие конструкторы ба-зового класса Exception.

В классе Excepts, методом которого является наш образец Pattern, опреде-лим следующие поля класса: private Random rnd = new Random(); private int level = -10; private bool Success; //true – нормальное завершение private int count = 1; // число попыток выполнения private const int maxcount = 3;

Определим теперь методы, вызываемые в теле охраняемого блока: private void MakeJob() { Console.WriteLine("Подготовительные работы завершены"); }

private bool CheckDanger() { //проверка качества и возможности продолжения работ int low = rnd.Next(level, 10); if (low > 6) return (false); return (true); }

Page 28: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

28

private void MakeLastJob() { Console.WriteLine("Все работы завершены успешно"); }

В классе Testing зададим метод, вызывающий метод Pattern: public void TestPattern() { Excepts ex1 = new Excepts(); try { ex1.Pattern(); } catch (Exception e) { Console.WriteLine("исключительная ситуация при вызове Pattern"); Console.WriteLine(e.ToString()); } }

Случай 1

Случай 2

Page 29: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

29

Случай 3

Рис. 6. Обработка исключительных ситуаций. Три случая

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

На рис. 6 показаны три варианта запуска метода TestPattern. В одном из них исключительной ситуации при вызове метода Pattern вообще не возникало, в другом – ситуация возникала, но коррекция обработчика исключения помогла и при повторе выполнения охраняемого блока в Pattern все прошло нормально. В третьем варианте метод Pattern не смог справиться с исключительной ситуа-цией, и она обрабатывалась в catch-блоке метода TestPattern.

1.4. Контрольные вопросы и упражнения 1. Что определяет задачу обеспечения качества программного продукта? 2. Определите жизненный цикл программного продукта, его фазы и этапы. 3. Что необходимо для создания надежного программного продукта? 4. Выделите основные виды средств отладки программных продуктов. 5. В чем различие конфигураций Debug и Release программного проекта? 6. В чем состоит механизм условной компиляции? 7. Как задавать константы компиляции? 8. Атрибутом чего является атрибут Conditional, и как он используется? 9. Каковы назначение и возможности классов Debug и Trace? 10. Каковы назначение и возможности классов-коллекций «слушателей»? 11. Как можно осуществить вывод результатов отладочной печати на консоль?

В файл? В окно вывода? 12. Сформулируйте суть метода Флойда, схемы Бертрана для обработки ис-

ключений.

Page 30: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

30

13. Что происходит при вызове метода Assert класса Debug или Trace? 14. Каково назначение классов StackTrace и BooleanSwitch? 15. Дайте определение понятия «исключительная ситуация». В чем заключает-

ся ее механизм работы? 16. Какие подходы к решению задачи обработки исключений вы знаете? 17. Какова схема обработки исключений в языке С, С++, С#? 18. Как и для чего создается объект класса Exception? 19. Что означает «выбрасывание» исключений? И как оно происходит? 20. Каковы назначение и использование оператора throw? 21. Как выполняется выбор обработчика ошибки при захвате исключения? 22. Какие виды охраняемых try-блоков вы знаете? В чем их различие? 23. Каковы назначение и использование catch-блока и finally-блока?

2. ОСНОВНЫЕ ПОНЯТИЯ ТЕСТИРОВАНИЯ

2.1. Терминология Отладка (debug, debugging) – процесс поиска, локализации и исправле-

ния ошибок в программе. Термин «отладка» в отечественной литературе используется двояко: для

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

Тестирование обеспечивает выявление (констатацию наличия) фактов расхождений с требованиями (ошибок).

Как правило, на фазе тестирования осуществляется и исправление иден-тифицированных ошибок, включающее локализацию ошибок, нахождение при-чин ошибок и соответствующую корректировку программы тестируемого приложения (Application (AUT) или Implementation Under Testing (IUT)).

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

Page 31: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

31

Пример поиска и исправления ошибки Отладка обеспечивает локализацию ошибок, поиск причин ошибок и со-

ответствующую корректировку программы. Рассмотрим простую функцию вычисления степени числа.

// Метод вычисляет неотрицательную степень n числа x static public double Power(double x, int n) { double z=1; for (int i=1;n>=i;i++) { z = z*x; } return z; }

Если вызвать метод Power с отрицательным значением степени n Power(2,-1), то получим некорректный результат – 2.

Исправим метод так, чтобы ошибочное значение параметра (недопусти-мое по спецификации значение) идентифицировалось специальным сообщени-ем, а возвращаемый результат был равен 1. // Метод вычисляет неотрицательную степень n числа x static public double PowerNonNeg(double x, int n) { double z=1; if (n>0) { for (int i=1;n>=i;i++) { z = z*x; } } else Console.WriteLine("Ошибка ! Степень числа n должна быть больше 0."); return z; }

Если вызвать скорректированный метод PowerNonNeg(2,-1) с отрицатель-ным значением параметра степени, то сообщение об ошибке будет выдано ав-томатически.

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

Тестирование разделяют на статическое и динамическое.

Page 32: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

32

Статическое тестирование выявляет формальными методами анализа без выполнения тестируемой программы неверные конструкции или неверные отношения объектов программы (ошибки формального задания) с помощью специальных инструментов контроля кода – CodeCheker.

Динамическое тестирование (собственно тестирование) осуществляет выявление ошибок только на выполняющейся программе с помощью специаль-ных инструментов автоматизации тестирования – Testbed или Testbench.

2.2. Организация тестирования Тестирование осуществляется на заданном заранее множестве входных

данных X и множестве предполагаемых результатов Y – (X,Y), которые задают пары соответствий (график) желаемой функции. Кроме того, должна быть за-фиксирована процедура Оракул (oracle), которая определяет, соответствуют ли выходные данные – YВ (вычисленные по входным данным – X) желаемым ре-зультатам – Y, т. е. принадлежит ли каждая вычисленная точка (x,yв) графику желаемой функции (X,Y).

Оракул дает заключение о факте появления неправильной пары (x,yв) и ничего не говорит о том, каким образом она была вычислена или каков пра-вильный алгоритм – он только сравнивает вычисленные и желаемые результа-ты. Оракулом может быть даже Заказчик или Программист, производящий со-ответствующие вычисления в уме, поскольку Оракулу нужен какой-либо аль-тернативный способ получения функции (X,Y) для вычисления эталонных зна-чений Y.

В процессе тестирования Оракул последовательно получает элементы множества (X,Y) и соответствующие им результаты вычислений (X,Yв) для идентификации фактов несовпадений (test incident).

При выявлении (x,yв)∉(X,Y) запускается процедура исправления ошибки, которая заключается во внимательном анализе (просмотре) протокола проме-жуточных вычислений, приведших к (x,yв), с помощью следующих методов: • «Выполнение программы в уме» (deskchecking). • Вставка операторов протоколирования (печати) промежуточных результатов

(logging). • Пошаговое выполнение программы (single-step running). • Выполнение с заказанными остановками (breakpoints), анализом трасс

(traces) или состояний памяти – дампов (dump). • реверсивное (обратное) выполнение (reversible execution)

Page 33: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

33

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

Пункт спецификации: «Метод Power должен принимать входные пара-метры: x – целое число, возводимое в степень, и n – неотрицательный порядок степени. Метод должен возвращать вычисленное значение xn».

Выполняем метод со следующими параметрами: Power(2,2) Проверка результата выполнения возможна, когда результат вычисления

заранее известен – 4. Если результат выполнения 22 = 4, то он соответствует спецификации.

2.2.2. Пример вставки операторов протоколирования промежу-точных результатов

Можно выводить промежуточные значения переменных при выполнении программы. Код, осуществляющий вывод, помечен комментариями. Этот метод относится к наиболее популярным средствам автоматизации отладки програм-мистов прошлых десятилетий. В настоящее время он известен как метод вне-дрения «агентов» в текст отлаживаемой программы. // Метод вычисляет неотрицательную степень n числа x static public double Power(double x, int n) { double z=1; for (int i=1;n>=i;i++) { z = z*x; Console.WriteLine("i = {0} z = {1}",i,z); \\ тестовый вывод } return z; }

2.2.3. Пример пошагового выполнения программы При пошаговом выполнении программы код выполняется строчка за

строчкой. В среде Microsoft Visual Studio возможны следующие команды поша-гового выполнения:

Step Into – если выполняемая строчка кода содержит вызов функции, процедуры или метода, то происходит вызов, и программа останавливается на первой строчке вызываемой функции, процедуры или метода.

Step Over – если выполняемая строчка кода содержит вызов функции, процедуры или метода, то происходит вызов и выполнение всей функции и программа останавливается на первой строчке после вызываемой функции.

Page 34: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

34

Step Out – предназначена для выхода из функции в вызывающую функ-цию. Эта команда продолжит выполнение функции и остановит выполнение на первой строчке после вызываемой функции.

Пошаговое выполнение до сих пор является мощным методом автоном-ного тестирования и отладки небольших программ.

2.2.4. Пример выполнения программы с заказанными контрольны-ми точками и анализом трасс и дампов

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

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

Когда в контрольной точке вызывается программа «агент», она тоже при-останавливает выполнение отлаживаемой программы, но только на время, не-обходимое для фиксации состояния выбранных переменных или структур дан-ных в специальном электронном журнале – Log-файле, после чего происходит автоматический возврат в режим исполнения.

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

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

Page 35: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

35

2

5

3

1

0x = 3 z = 1

4

x = 3 z = 1

x = 3 z = 1

x = 3 z = 3 n = 2

x = 3 z = 3 n = 2

n = 2

n = 2

n = 2

i = 1

i = 2

Рис. 7. Управляющий граф программы

Трасса, проходящая через вершины 0-1-3-4-5, зафиксирована в таблице (Таблица 1). Строки таблицы отображают вершины управляющего графа про-граммы, или breakpoints, в которых фиксировались текущие значения заказан-ных пользователем переменных.

Таблица 1 Трасса, проходящая через вершины 0-1-3-4-5

№ верши-ны-

оператора

Значение пе-ременной x

Значение пе-ременной z

Значение пе-ременной n

Значение пере-менной i

0 3 1 2 не зафиксировано 1 3 1 2 не зафиксировано 3 3 1 2 1 4 3 3 2 2 5 3 3 2 не зафиксировано Дамп – область памяти, состояние которой фиксируется в контрольной

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

Page 36: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

36

2.2.5. Пример обратного выполнения программы Обратное выполнение программы возможно при условии сохранения на

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

В следующей программе фиксируются значения всех переменных после выполнения каждого оператора. // Метод вычисляет неотрицательную степень n числа x static public double PowerNonNeg(double x, int n) { double z=1; Console.WriteLine("x={0} z={1} n={2}",x,z,n); if (n>0) { Console.WriteLine("x={0} z={1} n={2}",x,z,n); for (int i=1;n>=i;i++) { z = z*x; Console.WriteLine("x={0} z={1} n={2} i={3}",x,z,n,i); } } else Console.WriteLine("Ошибка ! Степень числа n должна быть больше 0."); return z; }

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

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

Тестирование заканчивается, когда выполнилось или «прошло» (pass) ус-пешно достаточное количество тестов в соответствии с выбранным критерием тестирования.

2.2.6. Сквозной пример тестирования Возьмем несколько отличающуюся от предыдущей (п. 2.2.5.) программу:

// Метод вычисляет степень n числа x static public double Power(int x, int n)

Page 37: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

37

{ int z=1; for (int i=1;n>=i;i++) { z = z*x; } return z; } [STAThread] static void Main(string[] args) { int x; int n; try { Console.WriteLine("Введите число x:"); x=Convert.ToInt32(Console.ReadLine()); if ((x>=0) & (x<=999)) { Console.WriteLine("Введите степень числа х – число n:"); n=Convert.ToInt32(Console.ReadLine()); if ((n>=1) & (n<=100)) { Console.WriteLine("Число х в степени n равно {0}", Power(x,n)); Console.ReadLine(); } else { Console.WriteLine("Ошибка : n должно быть из отрезка [1..100]"); Console.ReadLine(); } } else { Console.WriteLine("Ошибка : x должно быть из отрезка [0..999]"); Console.ReadLine(); } } \\ try-блок catch (Exception e) { Console.WriteLine("Ошибка : вводите числа."); Console.ReadLine(); }

Page 38: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

38

}

Для приведенной программы, вычисляющей степень числа, воспроизве-дем последовательность действий, необходимых для тестирования.

Спецификация программы На вход программа принимает два параметра: x – число, n – степень. Ре-

зультат вычисления выводится на консоль. Значения числа и степени должны быть целыми. Значения числа, возводимого в степень, должны лежать в диапазоне –

[0..999]. Значения степени должны лежать в диапазоне – [1..100]. Если числа, подаваемые на вход, лежат за пределами указанных диапазо-

нов, то должно выдаваться сообщение об ошибке.

Разработка тестов Определим области эквивалентности входных параметров.

Для x, числа, возводимого в степень, определим классы возможных значений: 1) x < 0 (ошибочное) 2) x > 999 (ошибочное) 3) x – не число (ошибочное) 4) 0 ≤ x ≤ 999 (корректное)

Для n – степени числа: 5) n < 1 (ошибочное) 6) n > 100 (ошибочное) 7) n – не число (ошибочное) 8) 1 ≤ n ≤100 (корректное)

Анализ тестовых случаев Входные значения: (x = 2, n = 3) (покрывают классы 4, 8). Ожидаемый результат: Число х в степени n равно 8. Входные значения: {(x = -1, n = 2),(x = 1000, n = 5 )} (покрывают классы 1, 2). Ожидаемый результат: Ошибка : x должно быть из отрезка [0..999]. Входные значения: {(x = 100, n = 0),(x = 100, n = 200)} (покрывают классы 5, 6). Ожидаемый результат: Ошибка : n должно быть из отрезка [1..100]. Входные значения:(x = ADS n = ASD) (покрывают классы 3, 7). Ожидаемый результат: Ошибка : вводите числа. Проверка на граничные значения:

1.1. Входные значения: (x = 999 n = 1). Ожидаемый результат: Число х в степени n равно 999.

Page 39: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

39

1.2. Входные значения: (x = 0 n = 100). Ожидаемый результат: Число х в степени n равно 0.

Выполнение тестовых случаев Запустим программу с заданными значениями аргументов.

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

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

2.3. Основные проблемы тестирования Реализация тестирования разделяется на три этапа:

1. Создание тестового набора (test suite) путем ручной разработки или авто-матической генерации для конкретной среды тестирования (testing envi-ronment).

2. Прогон программы на тестах, управляемый тестовым монитором (test monitor, test driver) с получением протокола результатов тестирования (test log).

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

Основная проблема тестирования – определение достаточности множе-

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

Простой пример Рассмотрим вопросы тестирования на

примере нашей простой программы Power. Запись текста этой программы видоизменена с целью сделать иллюстрацию описываемых фактов более прозрачной.

// Метод вычисляет // неотрицательную степень n числа x 1 static public double Power(double x, int n) { 2 double z=1;

1

2

3

4

5

6

7

Рис. 8. Управляющий граф программы

Page 40: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

40

3 for (int i=1; 4 n>=i; 5 i++) 6 {z = z*x;} //Возврат в п.4 7 return z; }

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

Существуют реализуемые и нереализуемые пути в программе, в нереали-зуемые пути в обычных условиях попасть нельзя.

Например, для функции Н путь (1,3,4) реализуем, путь (1,2,4) нереализу-ем в условиях нормальной работы. Но при сбоях даже нереализуемый путь мо-жет реализоваться. public static float H(float x,float y) { float H; 1 if (x*x+y*y+2<=0) 2 H = 17; 3 else H = 64; 4 return H*H+x*x; }

Рассмотрим следующие два примера тестирования. Пусть программа H(x:int, y:int) реализована в машине с 64 разрядным сло-

вами, тогда мощность множества тестов ||(X,Y)||=264. Это означает, что компьютеру, работающему на частоте 1 ГГц, для про-

гона этого полного набора тестов (при условии, что один тест выполняется за 100 команд) потребуется ~ 3K лет.

Или рассмотрим фрагмент схемы программы управления рукой робота, где интервал между моментами срабатывания руки не определен. // Фрагмент программы срабатывания руки робота. // Прочитать значения датчика static public bool ReadSensor(bool Sensor) { //...чтение значения датчика Console.WriteLine("...reading sensor value"); return Sensor; }

// Открыть схват

Page 41: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

41

static public void OpenHand() { //...открываем схват Console.WriteLine("...opening hand"); } // Закрыть схват static public void CloseHand() { //...закрываем схват Console.WriteLine("...closing hand"); }

[STAThread] static void Main(string[] args) { while (true) { Console.WriteLine("Enter Sensor value (true/false)"); if (ReadSensor(Convert.ToBoolean(Console.ReadLine()))) { OpenHand(); CloseHand(); } } }

Этот тривиальный пример требует прогона бесконечного множества по-следовательностей входных значений с разными интервалами срабатывания руки робота (Рис. 9).

Отсюда выводы:

• Тестирование программы на всех входных значениях невозможно. • Невозможно тестирование и на всех путях.

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

Время

Рис. 9. Тестовая последовательность сигналов датчика руки робота

Page 42: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

42

Требование к тестам – программа на любом из них должна останавли-ваться, т. е. не зацикливаться. Можно ли заранее гарантировать останов на лю-бом тесте?

В теории алгоритмов доказано, что не существует общего метода для ре-шения этого вопроса, а также вопроса, достигнет ли программа на данном тесте заранее фиксированного оператора.

Задача о выборе конечного набора тестов (X,Y) для проверки программы в общем случае неразрешима.

Поэтому для решения практических задач остается искать частные случаи решения этой задачи.

2.4. Контрольные вопросы и упражнения 1. Является ли программа аналогом математической формулы? 2. В чем различие отладки и тестирования программ? 3. Какие подходы используются для обоснования истинности программ? 4. В чем различие статического и динамического тестирования? 5. Каковы основные функции Оракула (Oracle)? 6. Какие методы поиска причины возникновения test incident вы знаете? 7. Какие фазы имеет процесс тестирования? 8. Каковы особенности разработки тестового набора? 9. Какие существуют способы получения эталонных значений теста? 10. В примере из п. 2.2.2 используйте для тестового вывода один из способов

условной компиляции, рассмотренных в разделе 1. 11. Расширьте возможности примера (задание 10) выводом отладочной ин-

формации в разные каналы (см. п. 1.2.2). 12. Перепишите пример программы п. 2.2.2, создайте для него подходящие

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

13. Примените к примерам п.п. 2.2.2., 2.2.6 схему Бертрана для обработки ис-ключений.

14. Что собой представляют контрольная точка, трасса и дамп программы? 15. Выполните задания 10 и 11 для примера п. 2.2.5. 16. Для сквозного примера п. 2.2.6 примените схему Флойда. 17. Для сквозного примера п. 2.2.6 создайте подходящие классы исключитель-

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

раметров? 20. Возможно ли тестирование программы на всех возможных путях УГП?

Page 43: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

43

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

вания операции в машине с 32-разрядным машинным словом? 23. Сколько тестов потребуется для проверки программы управления рукой

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

ла. Используйте для своей программы рассмотренные в разделах 1 и 2 подходы к отладке.

26. Выполните все этапы тестирования для задачи решения квадратного урав-нения, которая формулируется следующим образом: найти корни квадрат-ного уравнения, заданного своими коэффициентами, с положительным дискриминантом; подстановкой в уравнение, убедиться в погрешности вы-числений. Используйте для своей программы рассмотренные в разделах 1 и 2 подходы к отладке.

27. Выполните все этапы тестирования для задачи преобразования комплекс-ного числа z = х + iy, заданного своими действительной (x) и мнимой (y) частями, в тригонометрическую форму и вывода его на экран в виде выра-жения: z = r(cosϕ +i sinϕ). Для справки: .;22

xyarctgyxr =+= ϕ Используйте

для своей программы рассмотренные в разделах 1 и 2 подходы к отладке.

Page 44: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

44

3. КРИТЕРИИ ВЫБОРА ТЕСТОВ

3.1. Требования к идеальному критерию тестирования Требования к идеальному критерию формулируются следующим обра-

зом: 1. достаточность, т. е. критерий должен показывать, когда некоторое ко-

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

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

одновременно должны раскрывать или не раскрывать ошибки программы; 4. проверяемость, т. е. критерий должен быть легко проверяемым, например,

вычисляемым на тестах. Для нетривиальных классов программ в общем случае не существует

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

ные.

3.2. Классы критериев Выделяют следующие классы критериев.

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

3.2.1. Структурные критерии (класс I) Структурные критерии используют модель программы в виде «белого

ящика», что предполагает знание исходного текста программы или специфика-ции программы в виде потокового графа управления. Структурная информация понятна и доступна разработчикам подсистем и модулей приложения, поэтому данный класс критериев часто используется на этапах модульного и интеграци-онного тестирования (Unit testing, Integration testing).

Структурные критерии базируются на основных элементах УГП, опера-торах, ветвях и путях.

Page 45: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

45

Условие критерия тестирования команд (критерий С0) – набор тестов в совокупности должен обеспечить прохождение каждой команды не менее од-ного раза. Это слабый критерий, он, как правило, используется в больших про-граммных системах, где другие критерии применить невозможно.

Условие критерия тестирования ветвей (критерий С1) – набор тестов в совокупности должен обеспечить прохождение каждой ветви не менее одного раза. Это достаточно сильный и при этом экономичный критерий, поскольку множество ветвей в тестируемом приложении конечно и не так уж велико. Данный критерий часто используется в системах автоматизации тестирования.

Условие критерия тестирования путей (критерий С2) – набор тестов в со-вокупности должен обеспечить прохождение каждого пути не менее 1 раз. Если программа содержит цикл (в особенности с неявно заданным числом итераций), то число итераций ограничивается константой (часто равной 2, или числом классов выходных путей).

Для следующего примера простой программы рассмотрим условия ее тестирования в соответствии со структурными критериями. 1 public void Method (ref int x) { 2 if (x>17) 3 x = 17-x; 4 if (x==-13) 5 x = 0; 6 }

Тестовый набор из одного теста, удовлетворяет критерию команд (C0): (X,Y)={(xвх=30, xвых=0)} покрывает все операторы трассы 1-2-3-4-5-6.

Тестовый набор из двух тестов удовлетворяет критерию ветвей (C1): (X,Y)={(30,0), (17,17)}, добавляет 1 тест к множеству тестов для С0 и трассу 1-2-4-6. Трасса 1-2-3-4-5-6 проходит через все ветви достижимые в операторах if при условии true, а трасса 1-2-4-6 через все ветви, достижимые в операторах if при условии false.

Тестовый набор из четырех тестов удовлетворяет критерию путей (C2): (X,Y)={(30,0), (17,17), (-13,0), (21,-4)}.

Набор условий для двух операторов if c метками 2 и 4 приведен ниже (Таблица 2).

Таблица 2 Условия операторов if

(30,0) (17,17) (-13,0) (21,-4)

2 if (x>17) > ≤ ≤ >

4 if (x==-13) = ≠ = ≠

Page 46: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

46

Критерий ветвей С2 проверяет программу более тщательно, чем критерии – C1, однако даже если он удовлетворен, нет оснований утверждать, что про-грамма реализована в соответствии со спецификацией.

Например, если спецификация задает условие, что |x| ≤ 100, невыполни-мость которого можно подтвердить на тесте (-177,-177). Действительно, опера-торы 3 и 4 на тесте (-177,-177) не изменят величину х = – 177 и результат не будет соответствовать спецификации.

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

3.2.2. Функциональные критерии (класс II) Функциональный критерий – важнейший для программной индустрии

критерий тестирования. Он обеспечивает, прежде всего, контроль степени вы-полнения требований заказчика в программном продукте. Поскольку требова-ния формулируются к продукту в целом, они отражают взаимодействие тести-руемого приложения с окружением. При функциональном тестировании пре-имущественно используется модель «черного ящика». Проблема функциональ-ного тестирования – это, прежде всего, трудоемкость; дело в том, что докумен-ты, фиксирующие требования к программному изделию (Software requirement specification, Functional specification и т.п.), как правило, достаточно объемны, тем не менее, соответствующая проверка должна быть всеобъемлющей.

Частные виды функциональных критериев 1. Тестирование пунктов спецификации – набор тестов в совокупности дол-

жен обеспечить проверку каждого тестируемого пункта не менее одного раза. Спецификация требований может содержать сотни и тысячи пунктов тре-

бований к программному продукту и каждое из этих требований при тестиро-вании должно быть проверено в соответствии с критерием не менее чем одним тестом. 2. Тестирование классов входных данных – набор тестов в совокупности

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

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

Page 47: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

47

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

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

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

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

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

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

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

Критерий тестирования функций объединяет отчасти особенности струк-турных и функциональных критериев. Он базируется на модели «полупрозрач-ного ящика», где явно указаны не только входы и выходы тестируемого компо-нента, но также состав и структура используемых методов (функций, процедур) и классов. 6. Комбинированные критерии для программ и спецификаций – набор тестов

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

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

Пример применения функциональных критериев Рассмотрим пример разработки набора тестов по критерию классов вход-

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

Page 48: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

48

Произвести опрос статуса склада (вызвать функцию GetStoreStat). Доба-вить в журнал сообщений запись «СИСТЕМА: Запрошен статус СКЛАДА».

В зависимости от полученного значения произвести следующие действия: Полученный статус склада = 32. В приемную ячейку склада поступил подшип-ник. Система должна: a. Добавить в журнал сообщений запись «СКЛАД : Статус СКЛАДА = 32». b. Получить параметры поступившего подшипника с терминала подшипника

(должна быть вызвана функция GetRollerPar). c. Добавить в журнал сообщений запись «СИСТЕМА: Запрошены параметры

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

быть выполнены следующие действия (Таблица 3):

Таблица 3 Действия по результатам функции GetRollerPar

Значение, возвращенное функцией GetRollerPar Действия системы

... ... 0 Добавить на первое место команду GetR – «ПОЛУЧИТЬ

ИЗ ПРИЕМНИКА В ЯЧЕЙКУ» Добавить в журнал сообщений запись «ТЕРМИНАЛ ПОДШИПНИКА: 0 – параметры возвращены <Но-мер_группы>»

1 Добавить в журнал сообщений запись «ТЕРМИНАЛ ПОДШИПНИКА: 1 – нет данных»

... ...

e. Произвести опрос терминала оси (вызвать функцию получения сообщения от терминала – GetAxlePar). В журнал сообщений должно быть добавлено сообщение «СИСТЕМА : Запрошены параметры оси». В зависимости от возвращенного функцией GetAxlePar значения должны быть выполнены следующие действия (Таблица 4).

Таблица 4 Действия по результатам функции GetAxlePar

Значение, возвращенное функцией GetAxlePar Действия системы

... ... 1 Добавить в журнал сообщений запись «ТЕРМИНАЛ

ОСИ: 1 – нет данных» ... ...

Page 49: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

49

Определим классы входных данных для параметра – статус склада:

Статус склада = 0 (правильный). Статус склада = 4 (правильный). Статус склада = 16 (правильный). Статус склада = 32 (правильный). Статус склада = любое другое значение (ошибочный).

Теперь рассмотрим тестовые случаи: Тестовый случай 1 (покрывает класс 4): Состояние окружения (входные данные – X ): Статус склада – 32. ... Ожидаемая последовательность событий (выходные данные – Y): Система запрашивает статус склада (вызов функции GetStoreStat) и полу-чает 32. ... Тестовый случай 2 (покрывает класс 5): Состояние окружения (входные данные – X): Статус склада – 12dfga. ... Ожидаемая последовательность событий (выходные данные – Y): Система запрашивает статус склада (вызов функции GetStoreStat) и со-гласно пункту спецификации при ошибочном значении статуса склада в журнал добавляется сообщение «СКЛАД: ОШИБКА: Неопределенный статус». ...

3.2.3. Стохастические критерии (класс III) Стохастическое тестирование применяется при тестировании сложных

программных комплексов – когда набор детерминированных тестов (X,Y) име-ет громадную мощность. В случаях, когда подобный набор невозможно разра-ботать и исполнить на фазе тестирования, можно применить следующую мето-дику. 1. Разработать программы – имитаторы случайных последовательностей

входных сигналов {x}. 2. Вычислить независимым способом значения {y} для соответствующих

входных сигналов {x} и получить тестовый набор (X,Y). 3. Протестировать приложение на тестовом наборе (X,Y), используя два спо-

соба контроля результатов:

Page 50: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

50

Детерминированный контроль – проверка соответствия вычисленного зна-чения yв∈{y} значению y, полученному в результате прогона теста на набо-ре {x} – случайной последовательности входных сигналов, сгенерированной имитатором. Стохастический контроль – проверка соответствия множества значений

{yв}, полученного в результате прогона тестов на наборе входных значений {x}, заранее известному распределению результатов F(Y).

В этом случае множество Y неизвестно (его вычисление невозможно), но известен закон распределения данного множества.

Критерии стохастического тестирования Статистические методы окончания тестирования представляют собой

стохастические методы принятия решений о совпадении гипотез о распределе-нии случайных величин. К ним принадлежат такие широко известные методы, как метод Стьюдента (St), метод Хи-квадрат (χ2) и т. п.

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

При формализации модели скорости выявления ошибок (Рис. 10) исполь-зуют следующие обозначения: N – исходное число ошибок в программном комплексе перед тестированием, С – константа снижения скорости выявления ошибок за счет нахождения оче-редной ошибки, t1,t2,…tn – кортеж возрастающих интервалов обнаружения последовательности из n ошибок, T – время выявления n ошибок.

Времена выявления последовательности ошибок

Скорость выявления ошибки

NC

(N-1)C

t1 t2 tn . . .

Рис. 10. Зависимость скорости выявления ошибок от времени выявления

Page 51: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

51

Если допустить, что за время T выявлено n ошибок, то справедливо соот-ношение (3.2-1), утверждающее, что произведение скорости выявления одной i-той ошибки и времени выявления этой i-той ошибки есть 1 по определению:

(3.2-1) (N–i+1)*C*ti = 1 Отсюда для n ошибок справедливо соотно-шение (3.2-2):

(3.2-2) N*C*t1+(N–1)*C*t2+…+(N–(n–1))*C*tn=n N*C*(t1+t2+…+tn) – C*∑i [(i–1)*ti] = n, i=1..n N*C*T – C*∑i [(i–1)*ti] = n Если из (3.2-1) определить ti и просуммировать от 1 до n, то придем к со-

отношению (3.2-3) для времени T выявления n ошибок (3.2-3) ∑i [1/(N–i+1)] = T*C, i=1..n Если из (3.2-2) выразить С, приходим к соотношению (3.2-4): (3.2-4) C = n/(N*T – ∑i [(i–1)*ti)], i=1..n Наконец, подставляя С в (3.2-3), получаем окончательное соотношение

(3.2-5), удобное для оценок: (3.2-5) ∑i [1/(N–i+1)] = n/(N – (1/T)*∑i [(i–1)*ti]) Если оценить величину N приблизительно, используя известные методы

оценки числа ошибок в программе [4] или данные о плотности ошибок для про-ектов рассматриваемого класса из исторической базы данных проектов. И, кро-ме того, использовать текущие данные об интервалах между ошибками t1,t2…tn, полученные на фазе тестирования, то, подставляя эти данные в (3.2-5), можно получить оценку tn+1 временного интервала необходимого для нахожде-ния и исправления очередной ошибки (будущей ошибки).

Если tn+1>Td – допустимого времени тестирования проекта, то тестирова-ние заканчиваем, в противном случае продолжаем поиск ошибок.

Наблюдая последовательность интервалов ошибок t1,t2…tn, и время, по-траченное на выявление n ошибок T= ∑i ti, можно прогнозировать интервал времени до следующей ошибки и уточнять в соответствии с (3.2-4) величину С.

3.2.4. Мутационный критерий (класс IV) Постулируется, что профессиональные программисты пишут сразу почти

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

Подход базируется на следующих понятиях: Мутации – мелкие ошибки в программе.

Page 52: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

52

Мутанты – программы, отличающиеся друг от друга мутациями. Содержание метода мутационного тестирования состоит в следующем.

В разрабатываемую программу P вносят мутации, т.е. искусственно создают программы-мутанты P1, P2... Затем программа P и ее мутанты тестируются на одном и том же наборе тестов (X,Y).

Если на наборе (X,Y) подтверждается правильность программы P, и, кро-ме того, выявляются все внесенные в программы-мутанты ошибки, то набор тестов (X,Y) соответствует мутационному критерию, а тестируемая программа объявляется правильной.

Если некоторые мутанты не проявили всех своих мутаций, то надо рас-ширять набор тестов (X,Y) и продолжать тестирование.

Пример применения мутационного критерия Рассмотрим следующую программу P.

// Метод вычисляет неотрицательную степень n числа x static public double PowerNonNeg(double x, int n) // Р { double z=1; if (n>0) { for (int i=1;n-1>=i;i++) { z = z*x; } // for } //if else Console.WriteLine("Ошибка! Степень числа n должна быть больше 0"); return z; }

Для нее создается две программы-мутанта P1 и P2. В P1 изменено начальное значение переменной z с 1 на 2.

// Метод вычисляет неотрицательную степень n числа x static public double PowerMutant1(double x, int n) // Р1 { double z=2; if (n>0) { for (int i=1;n>=i;i++) { z = z*x; } } else Console.WriteLine("Ошибка ! Степень числа n должна быть больше 0."); return z; }

Page 53: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

53

Измененное начальное значение переменной z в мутанте Р1 выделено в тексте программы светлым тоном.

В P2 изменено начальное значение переменной i с 1 на 0 и граничное зна-чение индекса цикла с n на n-1. // Метод вычисляет неотрицательную степень n числа x static public double PowerMutant2(double x, int n) // Р2 { double z=1; if (n>0) { for (int i=0;n-1>=i;i++) { z = z*x; } } else Console.WriteLine("Ошибка ! Степень числа n должна быть больше 0"); return z; }

Измененное начальное значение переменной i и границы цикла в мутанте P2 также выделены в тексте программы.

При запуске тестов (X,Y) = { (x=2, n=3, y=8), (x=999, n=1, y=999), (x=0, n=100, y=0) } выявляются все ошибки в программах-мутантах и ошибка в ос-новной программе, где в условии цикла вместо n стоит n-1.

3.3. Оценка полноты тестирования программы по выбранному критерию

Тестирование программы Р по некоторому критерию C означает покры-тие множества компонентов программы P М = {m1...mk} по элементам или по связям. Оценка уровня оттестированности программы опирается на множества неизбыточных тестов.

T={t1...tn} – кортеж неизбыточных тестов ti. Тест ti неизбыточен, если существует покрытый им компонент mi из

M(P,C), не покрытый ни одним из предыдущих тестов t1...ti-1. Каждому ti соот-ветствует неизбыточный путь pi – последовательность вершин от входа до вы-хода.

V(P,С) – сложность тестирования Р по критерию С – измеряется мак-симальным числом неизбыточных тестов, покрывающих все элементы множе-ства M(P,С).

DV(P,С,Т) – остаточная сложность тестирования Р по критерию С – измеряется максимальным числом неизбыточных тестов, покрывающих эле-менты множества M(P,С), оставшиеся непокрытыми, после прогона набора тес-тов Т. Величина DV строго и монотонно убывает от V до 0.

Page 54: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

54

TV(P,С,Т) = (V-DV)/V – оценка степени тестированности (уровня пол-ноты тестирования) Р по критерию С. Критерий окончания тестирования TV(P,С,Т) ≥ L, где 0 ≤ L ≤ 1 (Рис. 11). L – уровень оттестированности, задан-ный в требованиях к программному продукту.

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

3.3.1. Плоская модель программы Для оценки степени оттестированности часто используется УГП – управ-

ляющий граф программы. Рассмотрим следующий пример компонента про-граммы G, для которого построен УГП (Рис. 12) в виде многокомпонентного объекта G. Он содержит внутри себя два компонента G1 и G2, УГП которых раскрыты. // Пример плоской модели проекта public void G() { int TerminalStatus=0, CommandStatus=0; bool IsPresent=true, CommandFound=true; 1 Init(); 2 switch (TerminalStatus) { case 11 : 11 AddCommand(); 16 switch (CommandStatus) { case 12 : 12 GetMessage(); 13 ClearQueue(); break; case 17 : 17 ClearQueue(); break; case 18 : 18 DumpQueue(); break; } // switch CommandStatus

Время

TV L

Рис. 11. Метрика оттестированности приложения

Page 55: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

55

14 ProcessCommand(); 15 Commit(); break; case 3 : 3 AskTerminal(); 4 if (IsPresent) { 5 Connect(); } 6 RebuildQueue(); break; case 19 : 19 SearchValidCommand(); 20 if (CommandFound) { 21 AnalyzeCommand(); } else { 23 LogError(); } 22 MoveNextCommand(); break; } // switch TerminalStatus 7 LogResults(); 10 DisposeAll(); }

В результате УГП компонента G имеет такой вид, как если бы компонен-

ты G1 и G2 в его структуре специально не выделялись, а УГП компонентов G1 и G2 были вставлены в УГП G. Для тестирования компонента G в соответствии с критерием путей потребуется прогнать тестовый набор, покрывающий сле-дующий набор трасс графа G:

P1(G) = 1-2-3-4-5-6-7-10; P2(G) = 1-2-3-4-6-7-10; P3(G) = 1-2-11-16-18-14-15-7-10; P4(G) = 1-2-11-16-17-14-15-7-10; P5(G) = 1-2-11-16-12-13-14-15-7-10; P6(G) = 1-2-19-20-23-22-7-10; P7(G) = 1-2-19-20-21-22-7-10. Оценка степени тестированности плоской модели определяется долей

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

(3.3-1) TV(G,С) = (V–DV)/V = ∑PTi(G) / (∑Pi(G)), где PTi(G) – тестовый путь (ti) в графе G плоской модели равен 1, если он

протестирован (прогнан), или 0, если нет.

Рис. 12. Плоская модель УГП компонента G

G 2 G 1

G 1

5

2 3

4

6

7

10

11

12 13

14 15

16

17 18

19 20

21 22

23

8 9

Page 56: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

56

Например, если в УГП (Рис. 12) тесты t6 и t7, которым соответствуют трассы P6 и P7, не прогнаны, то в соответствии с соотношением (3.3-1) для TV(G,С) степень тестированности будет оценена в (7–2)/7 = 0.71.

3.3.2. Иерархическая модель программы Представим тот же пример компонента программы в виде иерархической

модели. // Пример иерархической модели проекта public void G1() { int CommandStatus=0; AddCommand(); switch (CommandStatus) { case 12 : GetMessage(); ClearQueue(); break; case 17 : ClearQueue(); break; case 18 : DumpQueue(); break; } ProcessCommand(); Commit(); } public void G2() { bool CommandFound=true; SearchValidCommand(); if (CommandFound) { AnalyzeCommand(); } else { LogError(); } MoveNextCommand(); }

Page 57: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

57

public void G() { int TerminalStatus=0; bool IsPresent=true; 1 Init(); 2 switch (TerminalStatus) { case 11 : 8 G1(); break; case 3 : 3 AskTerminal(); 4 if (IsPresent) { 5 Connect(); } 6 RebuildQueue(); break; case 19 : 9 G2(); break; } 7 LogResults(); 10 DisposeAll(); }

GG1

G1

2

3

4

56

7

8 9

10

G2 G1

G1

2

3

4

56

7

8 9

10

Рис. 13. Иерархическая модель УГП компонента G

Page 58: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

58

УГП компонента G, представленный в виде иерархической модели, при-веден на рисунке (Рис. 13). В иерархическом УГП G входящие в его состав ком-поненты представлены ссылками на свои УГП G1 и G2 (Рис. 14).

Для исчерпывающего тестирования иерархической модели компонента G в соответствии с критерием путей требуется прогнать следующий набор трасс:

P1(G) = 1-2-3-4-5-6-7-10; P2(G) = 1-2-3-4-6-7-10; P3(G) = 1-2-8-7-10; P4(G) = 1-2-9-7-10. Приведенный набор трасс достаточен при условии, что компоненты G1 и

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

P11(G1)=11-16-12-13-14-15; P21(G2)=19-20-21-22. P12(G1)=11-16-17-14-15. P22(G2)=11-16-18-17-14-15 P13(G1)=19-20-23-22.

Рис. 14. Иерархическая модель: УГП компонент G1 и G2

Оценка тестированности иерархической модели определяется на основе

учета оценок тестированности компонентов. Если трасса некоторого теста tj УГП G включает узлы, представляющие компоненты Gj1,..Gjm, оценка TV сте-пени тестированности которых известна, то оценка тестированности PTi(G) при реализации этой трассы определяется не как 1, а как минимальная из оценок TV для компонентов.

19 20

21 22

23

G 2 G 1

11

12

13 14 15

16

17 18

Page 59: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

59

Интегральная оценка определяется соотношением (3.3-2): (3.3-2) TV(G,С) = (V-DV)/V = (∑PTi(G) * ∑(TV(Gij,С))) / (∑Pi(G)),

где PTi(G) – тестовый путь (ti) в графе G равен 1, если протестирован, или 0, если нет. В путь PTi графа G может входить j узлов модулей Gij со своей степе-нью тестированности TV(Gij,С) из которых берется минимальный, что дает худшую оценку степени тестированности пути.

Например, для УГП (Рис. 13) предположим, что результаты оценки тести-рованности компонент дали для G1 величину 0.66 (трассы Р11 и Р12 были ус-пешно прогнаны, а трасса Р13 не прошла из-за ошибки), а для G2 величину 0.5 (трасса Р22 прошла, а Р21 нет) (Рис. 14). Тогда интегральная оценка тестирован-ности графа G при успешном прогоне трасс P1, P2, P3, P4 в соответствии с (3.3-2) составит:

TV(G,С) = (1+1+1*0.66+1*0.5)/(1+1+1+1) = 0.79.

Интегральная оценка оттестированности программного проекта позволя-ет отслеживать качество или отлаженность программной компоненты или про-екта. Это важная метрика качества (безошибочности) программного продукта.

3.3.3 Методика интегральной оценки тестированности Получение интегральной оценки тестированности сводится к выполне-

нию следующих шагов. 1. Выбор критерия С и приемочной оценки тестированности программного

проекта – L. 2. Построение дерева классов проекта и построение УГП для каждого моду-

ля. 3. Модульное тестирование и оценка TV на модульном уровне. 4. Построение УГП, интегрирующего модули в единую иерархическую

(классовую) модель проекта. 5. Выбор тестовых путей для проведения интеграционного или системного

тестирования. 6. Генерация тестов, покрывающих тестовые пути шага 5. 7. Интегральная оценка тестированности проекта с учетом оценок тестиро-

ванности модулей-компонентов. 8. Повторение шагов 5-7 до достижения заданного уровня тестированности L.

Page 60: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

60

3.4. Контрольные вопросы и упражнения 1. Какие предъявляются требования к идеальному критерию тестирования? 2. Какие классы частных критериев тестируемости известны? 3. Какая модель программы лежит в основе структурных критериев тести-

руемости? 4. Что задают условия тестирования программы в соответствии со структур-

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

ветствии со структурными критериями. 6. Какими недостатками обладают структурные критерии? 7. Какая модель программы лежит в основе функциональных критериев тес-

тируемости? 8. Какие существуют разновидности функциональных критериев? 9. Назовите недостатки функциональных критериев. 10. Какие существуют критерии стохастического тестирования? 11. Какая информация должна собираться при тестировании для применения

метода оценки скорости выявления ошибок? 12. Какой подход используется в методе мутационного тестирования? 13. Составьте множество мутаций для программ из заданий к разделам 1 и 2. 14. Постройте на основе полученных мутаций (задание 13) программы-

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

го обеспечения? 17. Что является метрикой для оценки полноты тестирования программного

проекта? 18. В чем состоят особенности плоской модели УГП? 19. Какие существуют особенности иерархической модели УГП? 20. Постройте УГП для программ из заданий к разделам 1 и 2 и перечислите

для каждой из них все трассы. 21. Приведите пример неизбыточного теста для программы вычисления фак-

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

рассмотрев при этом 2 способа ее реализации – итерационный и рекурсив-ный. Оцените сложность тестирования обоих вариантов реализации.

Page 61: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

61

4. РАЗНОВИДНОСТИ ТЕСТИРОВАНИЯ

4.1. Модульное тестирование Модульное тестирование – это тестирование программы на уровне от-

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

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

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

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

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

Page 62: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

62

модули (а именно, спецификацию интерфейса), в то время как ошибки в алго-ритме обработки параметров довольно легко обнаруживаются.

Являясь по способу исполнения структурным тестированием или тести-рованием «белого ящика», модульное тестирование характеризуется степенью, в которой тесты выполняют или покрывают логику программы (исходный текст). Тесты, связанные со структурным тестированием, строятся по следую-щим принципам:

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

4.1.1. Тестирование на основе потока управления Особенности использования структурных критериев тестирования С0,

С1, С2 были рассмотрены в разделе 3. К ним следует добавить критерий по-крытия условий, заключающийся в покрытии всех логических (булевских) ус-ловий в программе. Критерии покрытия решений (ветвей – С1) и условий не заменяют друг друга, поэтому на практике используется комбинированный критерий покрытия условий/решений, совмещающий требования по покрытию и решений, и условий.

К популярным критериям относятся критерий покрытия функций про-граммы, согласно которому каждая функция программы должна быть вызвана хотя бы один раз, и критерий покрытия вызовов, согласно которому каждый вызов каждой функции в программе должен быть осуществлен хотя бы один раз. Критерий покрытия вызовов известен также как критерий покрытия пар вызовов (call pair coverage).

4.1.2. Тестирование на основе потока данных Этот вид тестирования направлен на выявление ссылок на неинициализи-

рованные переменные и избыточные присваивания (аномалий потока данных). Как основа для стратегии тестирования поток данных впервые был описан в 1976 году [14]. Предложенная еще тогда стратегия требовала тестирования всех взаимосвязей, включающих в себя ссылку (использование) и определение пе-ременной, на которую указывает ссылка (т. е. требуется покрытие дуг инфор-мационного графа программы). Недостаток стратегии в том, что она не включа-ет критерий С1 и не гарантирует покрытия решений.

Стратегия требуемых пар [4] также тестирует упомянутые взаимосвязи. Использование переменной в предикате дублируется в соответствии с числом

Page 63: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

63

выходов решения, и каждая из таких требуемых взаимосвязей должна быть протестирована. К популярным критериям принадлежит критерий СР, заклю-чающийся в покрытии всех таких пар дуг v и w, что из дуги v достижима дуга w, поскольку именно на дуге может произойти потеря значения переменной, которая в дальнейшем уже не должна использоваться. Для «покрытия» еще од-ного популярного критерия Cdu достаточно тестировать пары (вершина, дуга), поскольку определение переменной происходит в вершине УГП, а ее использо-вание – на дугах, исходящих из решений или в вычислительных вершинах.

4.1.3. Методы проектирования тестовых путей для достижения заданной степени тестированности

Процесс построения набора тестов при структурном тестировании приня-то делить на три фазы:

1. Конструирование УГП. 2. Выбор тестовых путей. 3. Генерация тестов, соответствующих тестовым путям. Первая фаза соответствует статическому анализу программы, задача ко-

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

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

подходящих тестов, реализующих прохождение этих путей. Для реализации второй фазы выделяют три подхода к построению тесто-

вых путей: 1. Статические методы. 2. Динамические методы. 3. Методы реализуемых путей. Достоинство статических методов состоит в сравнительно небольшом ко-

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

Статические методы. Статические методы представляют самое простое и легко реализуемое решение – построение каждого пути посредством посте-

Page 64: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

64

пенного его удлинения за счет добавления дуг, пока не будет достигнута вы-ходная вершина управляющего графа программы. Эта идея может быть усиле-на в так называемых адаптивных методах, которые каждый раз добавляют только один тестовый путь (входной тест), используя предыдущие пути (тесты) как руководство для выбора последующих путей в соответствии с некоторой стратегией. Чаще всего адаптивные стратегии применяются по отношению к критерию С1. Основной недостаток статических методов заключается в том, что не учитывается возможная нереализуемость построенных путей тестирова-ния.

Динамические методы. Такие методы предполагают построение полной системы тестов, удовлетворяющих заданному критерию, путем одновременно-го решения задачи построения покрывающего множества путей и тестовых данных. При этом можно автоматически учитывать реализуемость или нереа-лизуемость ранее рассмотренных путей или их частей. Основной идеей дина-мических методов является подсоединение к начальным реализуемым отрезкам путей дальнейших их частей так, чтобы: 1) не терять при этом реализуемости вновь полученных путей; 2) покрыть требуемые элементы структуры програм-мы.

Методы реализуемых путей. Данная методика заключается в выделении из множества путей подмножества всех реализуемых путей. После чего покры-вающее множество путей строится из полученного подмножества реализуемых путей.

4.1.4. Пример модульного тестирования Пусть требуется протестировать класс TCommand, который реализует ко-

манду для склада. Этот класс содержит единственный метод TCommand.GetFullName(), спецификация которого описывается следующим об-разом: ... Операция GetFullName() возвращает полное имя команды, соответствующее ее допус-тимому коду, указанному в поле NameCommand. В противном случае возвращается со-общение «ОШИБКА: Неверный код команды». Операция может быть применена в любой момент. ...

Разработаем спецификацию тестового случая для тестирования метода GetFullName на основе приведенной спецификации класса (

Таблица 5).

Page 65: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

65

Таблица 5

Спецификация теста

Название класса: TСommand Название тестового случая: TСommandTest1

Описание тестового случая. Тест проверяет правильность работы метода GetFullName – получения полного названия команды на основе кода команды. В тесте подаются следующие значения кодов команд (входные значения): -1, 1, 2, 4, 6, 20, (причем, 1 – запрещенное значение).

Начальные условия. Нет.

Ожидаемый результат Перечисленным входным значениям должны соответствовать следующие вы-ходные:

Коду команды -1 должно соответствовать сообщение «ОШИБКА: Неверный код команды»

Коду команды 1 должно соответствовать полное название команды «ПОЛУЧИТЬ ИЗ ВХОДНОЙ ЯЧЕЙКИ»

Коду команды 2 должно соответствовать полное название команды «ОТПРАВИТЬ ИЗ ЯЧЕЙКИ В ВЫХОДНУЮ ЯЧЕЙКУ»

Коду команды 4 должно соответствовать полное название команды «ПОЛОЖИТЬ В РЕЗЕРВ»

Коду команды 6 должно соответствовать полное название команды «ПРОИЗВЕСТИ ЗАНУЛЕНИЕ»

Коду команды 20 должно соответствовать полное название команды «ЗАВЕРШЕНИЕ КОМАНД ВЫДАЧИ»

Для тестирования метода класса TCommand.GetFullName() был создан тес-

товый драйвер – класс TCommandTester. Класс TCommandTester содержит метод TCommandTest1(), в котором реализована вся функциональность теста. class TCommandTester:Tester // Тестовый драйвер { ... TCommand OUT; public TCommandTester() { OUT=new TCommand(); Run(); }

private void Run() { TCommandTest1();

Page 66: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

66

} private void TCommandTest1() { int[] commands = {-1, 1, 2, 4, 6, 20}; for(int i=0;i<=5;i++) { OUT.NameCommand=commands[i]; LogMessage(commands[i].ToString()+" : "+OUT.GetFullName()); } } ... }

В данном случае для покрытия спецификации достаточно перебрать сле-дующие значения кодов команд: -1, 1, 2, 4, 6, 20, (-1 – запрещенное значение) и получить соответствующее им полное название команды с помощью метода GetFullName().

Пары значений (X, Yв) при исполнении теста заносятся в log-файл для последующей проверки на соответствие спецификации.

После завершения теста следует просмотреть журнал теста, чтобы срав-нить полученные результаты с ожидаемыми, заданными в спецификации тесто-вого случая TСommandTest1. -1 : ОШИБКА : Неверный код команды 1 : ПОЛУЧИТЬ ИЗ ВХОДНОЙ ЯЧЕЙКИ 2 : ОТПРАВИТЬ ИЗ ЯЧЕЙКИ В ВЫХОДНУЮ ЯЧЕЙКУ 4 : ПОЛОЖИТЬ В РЕЗЕРВ 6 : ПРОИЗВЕСТИ ЗАНУЛЕНИЕ 20 : ЗАВЕРШЕНИЕ КОМАНД ВЫДАЧИ

4.2. Интеграционное тестирование Интеграционное тестирование – это тестирование части системы, со-

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

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

Page 67: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

67

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

На Рис. 15 приведена структура комплекса программ K, состоящего из от-тестированных на этапе модульного тестирования модулей M1, M2, M11, M12, M21, M22. Задача, решаемая методом интеграционного тестирования, – тести-рование межмодульных связей, реализующихся при исполнении программного обеспечения комплекса K.

Интеграционное тестирование использует модель «белого ящика» на мо-

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

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

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

1) «Сверху вниз» и соответствующее ему восходящее тестирование. 2) «Снизу вверх» и соответственно нисходящее тестирование.

Особенности монолитного тестирования заключаются в следующем: для

замены неразработанных к моменту тестирования модулей, кроме самого верх-него (модуль К на Рис. 15), необходимо дополнительно разрабатывать драйверы (test driver) и/или заглушки (stub), замещающие отсутствующие на момент се-анса тестирования модули нижних уровней.

К

M1 M2

M11 M12 M21 M22

Рис. 15. Пример структуры комплекса программ

Page 68: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

68

Сравнение монолитного и интегрального подхода дает следующее. • Монолитное тестирование требует больших трудозатрат, связанных с до-

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

• Пошаговое тестирование связано с меньшей трудоемкостью идентификации ошибок за счет постепенного наращивания объема тестируемого кода и со-ответственно локализации добавленной области тестируемого кода.

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

Например, порядок тестирования комплекса K (Рис. 15) при нисходящем тестировании может быть таким, как показано ниже, где тестовый набор, раз-работанный для модуля Mi, обозначен как XYi = (X, Y)i. K->XYK M1->XY1 M11->XY11 M2->XY2 M22->XY22 M21->XY21 M12->XY12

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

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

• сложность организации и разработки среды для реализации исполнения мо-дулей в нужной последовательности;

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

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

Например, порядок тестирования комплекса K (Рис. 15) при нисходящем тестировании может быть следующим: M11->XY11 M12->XY12 M1->XY1 M21->XY21

Page 69: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

69

M2(M21, Stub(M22))->XY2 K(M1, M2(M21, Stub(M22)) ->XYK M22->XY22 M2->XY2 K->XYK

А к недостаткам восходящего тестирования можно отнести: • запаздывание проверки концептуальных особенностей тестируемого ком-

плекса; • необходимость в разработке и использовании драйверов.

4.2.1. Особенности интеграционного тестирования для процедур-ного программирования

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

Первым подходом к разработке программного обеспечения является про-цедурное (модульное) программирование. Традиционное процедурное про-граммирование предполагает написание исходного кода в императивном (пове-лительном) стиле, предписывающем определенную последовательность выпол-нения команд, а также описание программного проекта с помощью функцио-нальной декомпозиции. Такие языки, как Pascal и C, являются императивными. В них порядок исходных строк кода определяет порядок передачи управления, включая последовательное исполнение, выбор условий и повторное исполнение участков программы. Каждый модуль имеет несколько точек входа (при стро-гом написании кода – одну) и несколько точек выхода (при строгом написании кода – одну). Сложные программные проекты имеют модульно-иерархическое построение [10], и тестирование модулей является начальным шагом процесса тестирования программного обеспечения (ПО). Построение графовой модели модуля является тривиальной задачей, а тестирование практически всегда про-водится по критерию покрытия ветвей C1, т. е. каждая дуга и каждая вершина графа модуля должны содержаться, по крайней мере, в одном из путей тести-рования.

Таким образом, M(P,C1) = E∪Nij, где Е – множество дуг, а Nij – входные вершины ГМП.

Сложность тестирования модуля по критерию С1 выражается уточненной формулой для оценки топологической сложности МакКейба [16]:

V(P,C1) = q + kin , где q – число бинарных выборов для условий ветвле-ния, а kin – число входов графа.

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

Page 70: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

70

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

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

Если программа P состоит из p модулей, то при интеграции модулей в комплекс фактически получается громоздкая плоская (Рис. 12), или более про-стая – иерархическая (Рис. 13) – модель программного проекта. В качестве кри-терия тестирования на интеграционном уровне обычно используется критерий покрытия ветвей C1. Введем также следующие обозначения:

n – число узлов в графе; e – число дуг в графе; q – число бинарных выборов из условий ветвления в графе; kin – число входов в граф; kout – число выходов из графа; kext – число точек входа, которые могут быть вызваны извне. Тогда сложность интеграционного тестирования всей программы P по

критерию C1 может быть выражена формулой: V(P,C1) =∑V(Modi, C1) – kin +kext = e – n – kext + kout = q + kext, (∀Modi∈P) Однако при подобном подходе к построению ГМП разработчик тестового

набора неизбежно сталкивается с неприемлемо высокой сложностью тестиро-вания V(P,C) для проектов среднего и большого объема (размером в 105 – 107 строк) [14], что следует из роста топологической сложности управляющего графа по МакКейбу. Таким образом, используя плоскую или иерархическую модель, трудно дать оценку тестированности TV(P,C,T) для всего проекта и оценку зависимости тестированности проекта от тестированности отдельного модуля TV(Modi,C), включенного в этот проект.

Рассмотрим вторую модель сборки модулей в процедурном программи-ровании – граф вызовов. В этой модели в случае интеграционного тестирования учитываются только вызовы модулей в программе. Поэтому из множества M(Modi,C) тестируемых элементов можно исключить те элементы, которые не подвержены влиянию интеграции, т. е. узлы и дуги, не соединенные с вызовами модулей: M(Modi,C') = E' ∪ Nin, где E' = {(ni, nj)∈E | ni или nj содержит вызовы модулей}, т.е. E' – подмножество ребер графа модуля, а Nin – «входные» узлы графа. Эта модификация ГМП приводит к получению нового графа – графа вы-

Page 71: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

71

1

2

9 8

7

10

G1 G2

G

зовов, каждый узел в этом графе представляет модуль (процедуру), а каждая дуга – вызов модуля (процедуры). Для процедурного программирования подоб-ный шаг упрощает графовую модель программного проекта до приемлемого уровня сложности. Таким образом, может быть определена цикломатическая сложность упрощенного графа модуля Modi как V'(Modi,C'), а громоздкая фор-мула, выражающая сложность интеграционного тестирования программного проекта, принимает следующий вид:

V'(P,C1') = ∑V'(Modi, C1') – kin +kext Так, для программы ГМП, которая приведена на Рис. 12, для получения

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

1) Дуги 1-2, содержащей входной узел 1 графа G. 2) Дуг 2-8, 8-7, 7-10, содержащих вызов модуля G1. 3) Дуг 2-9, 9-7, 7-10, содержащих вызов модуля G2. В результате граф вызовов примет вид, показанный на Рис. 16, а слож-

ность данного графа по критерию C1' равна: V'(G,C1') = q + kext =1+1=2. V'(Modi,C') также называется в литературе сложностью модульного ди-

зайна (complexity of module design).

Сумма сложностей модульного дизайна для всех модулей по критерию

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

4.2.2. Особенности интеграционного тестирования для объектно-ориентированного программирования

Программный проект, написанный в соответствии с объектно-ориентированным подходом, будет иметь ГМП, существенно отличающийся от ГМП традиционной «процедурной» программы. Сама разработка проекта стро-ится по другому принципу – от определения классов, используемых в програм-

Рис. 16. Граф вызовов модулей

Page 72: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

72

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

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

Перед тем как приступить к описанию графовой модели объектно-ориентированной программы, остановимся отдельно на одном существенном аспекте разработки программного обеспечения на языке объектно-ориентированного программирования (ООП), например, C++ или С#. Разработ-ка программного обеспечения высокого качества для MS Windows или любой другой операционной системы, использующей стандарт «look and feel», с при-менением только вновь созданных классов практически невозможна. Програм-мист должен будет затратить массу времени на решение стандартных задач по созданию пользовательского интерфейса. Чтобы избежать работы над давно решенными вопросами, во всех современных компиляторах предусмотрены специальные библиотеки классов. Такие библиотеки включают в себя практи-чески весь программный интерфейс операционной системы и позволяют задей-ствовать при программировании средства более высокого уровня, чем просто вызовы функций. Базовые конструкции и классы могут быть использованы по-вторно при разработке нового программного проекта. За счет этого значительно сокращается время разработки приложений. В качестве примера подобной сис-темы можно привести библиотеку базовых классов .NET – Framework Class Li-brary (NFCL) [1].

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

Page 73: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

73

объекту, когда сообщение обработано. Поэтому каждый метод (функция – член класса) должен пройти традиционное модульное тестирование по выбранному критерию C (как правило, С1). В соответствии с введенными выше обозначе-ниями, назовем метод Modi, а сложность тестирования – V(Modi,C). Все резуль-таты, полученные в пункте 4.2.1 для тестирования модулей, безусловно, подхо-дят для тестирования методов классов. Каждый класс должен быть рассмотрен и как субъект интеграционного тестирования. Интеграция для всех методов класса проводится с использованием инкрементальной стратегии снизу вверх. При этом мы можем использовать повторно тесты для классов-родителей тес-тируемого класса, что следует из принципа наследования – от базовых классов, не имеющих родителей (корней), к самым верхним уровням классов (листьям).

Графовая модель класса, как и объектно-ориентированной программы, на интеграционном уровне в качестве узлов использует методы. Дуги данной ГМП (вызовы методов) могут быть образованы двумя способами: Прямым вызовом одного метода из кода другого, в случае, если вызываемый метод виден (не закрыт для доступа средствами языка программирования) из класса, содержащего вызывающий метод, присвоим такой конструкции назва-ние Р-путь (P-path, Procedure path, процедурный путь(Р-путь)). Обработкой сообщения, когда явного вызова метода нет, но в результате рабо-ты «вызывающего» метода порождается сообщение, которое должно быть об-работано «вызываемым» методом.

Для второго случая «вызываемый» метод может породить другое сооб-щение, что приводит к возникновению цепочки исполнения последовательно-сти методов, связанных сообщениями. Подобная цепочка носит название ММ-путь (MM-path, Method/Message path, путь метод/сообщение). ММ-путь закан-чивается, когда достигается метод, который при отработке не вырабатывает но-вых сообщений (т. е. вырабатывает «сообщение покоя»).

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

На Рис. 17 можно выделить четыре ММ-пути (1-4) и один P-путь (5): 1. msg a → метод 3 → msg 3 → метод 4 → msg d 2. msg b → метод 1 → msg 1 → метод 4 → msg d 3. msg b → метод 1 → msg 2 → метод 5 4. msg c → метод 2 5. call → метод 5 Здесь класс изображен как объединенное множество методов. Введем следующие обозначения:

Kmsg – число методов класса, обрабатывающих различные сообщения;

Page 74: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

74

Kem – число методов класса, которые не закрыты от прямого вызова из других классов программы.

Рис. 17. Пример MM-путей и P-путей в графовой модели класса

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

ределяется как сумма методов – обработчиков сообщений Kmsg и тех мето-дов, которые могут быть вызваны из других классов программы Kem. Это определяется самим разработчиком путем разграничения доступа к методам класса (с помощью ключевых слов разграничения доступа public, private, protect) при написании методов. Таким образом, Kext = Kmsg + Kem, и имеет новый по сравнению с процедурным программированием физический смысл.

• Принцип соединения узлов в ГМП, отражающий два возможных типа вызо-вов методов класса (через ММ-пути и Р-пути), что приводит к новому на-полнению для множества М требуемых элементов.

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

С учетом приведенных замечаний, информационные связи между моду-лями программного проекта получают новый физический смысл, а формула оценки сложности интеграционного тестирования класса Cls принимает вид: V(Cls, C) = f (Kmsg, Kem).

Page 75: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

75

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

Значение числа ММ-путей зависит от схемы обработки сообщений дан-ным классом, что должно быть определено в спецификации класса. Например, для класса, изображенного на Рис. 17, сложность интеграционного тестирования V(Cls,C) = 5 (множество неизбыточных тестов Т для класса составляют 4 ММ-пути плюс внешний вызов метода 5, т. е. Р-путь).

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

Когда класс программы P протестирован, объект данного класса может быть включен в общий граф G программного проекта, содержащий все ММ-пути и все вызовы методов классов и процедур, возможные в программе (Рис. 18).

Программа P, содержащая n классов, имеет сложность интеграционного

тестирования классов: V(P, C) = ∑iV(Clsi,C), i = 1..n. Формальным представлением описанного выше подхода к тестированию

программного проекта служит модель классов программного проекта, состоя-щая из дерева классов проекта (Рис. 19) и модели каждого класса, входящего в программный проект (

Рис. 20).

method 3

method 1

Class N Object

method 22

method 4

method 5

msg b

msg c

msg 1

msg 2

msg 3

msg a

msg d method X

Class M Object

.............

..............

Class 1 Object

method 1

Generating massages Events method Y

call method 5

msg e

Рис. 18. Пример включения объекта в модель программного проекта, построенного с использованием MM-путей и P-путей

Page 76: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

76

Рис. 20. Модель класса, входящего в программный проект

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

Методика проведения тестирования программы, представленной в виде модели классов программного проекта, включает в себя несколько этапов, со-ответствующих уровням тестирования (Рис. 21):

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

Base class 1

.....

.....

.....

.....

.....

.....

.....

Base class N

Generated class 11

Generated class 1NM

Generated class N1

Generated class 1J

Generated class NJ

Generated class X

Generated class Y

Рис. 19. Дерево классов проекта

Page 77: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

77

2. На втором уровне тестируются методы класса, которые образуют кон-текст интеграционного тестирования каждого класса.

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

Рис. 21. Уровни тестирования модели классов программного проекта

Второй и третий уровни рассматриваемой модели соответствуют этапу интеграционного тестирования.

Для третьего уровня важным оказывается понятие атомарной системной функции (АСФ). АСФ – это множество, состоящее из внешнего события на входе системы, реакции системы на это событие в виде одного или более ММ-путей и внешнего события на выходе системы. В общем случае внешнее выходное событие может быть нулевым, т. е. неаккуратно написанное программное обеспечение может не обеспечивать внешней реакции на действия пользователя. АСФ, состоящая из входного внешнего события, одного ММ-пути и выходного внешнего события, может быть взята в качестве модели для нити (thread). Тестирование подобной АСФ в рамках модели классов ГМП реа-лизуется довольно сложно, так как, хотя динамическое взаимодействие нитей (потоков) в процессе исполнения естественно фиксируется в log-файлах, запо-минающих результаты трассировки исполнения программ, оно же достаточно сложно отображается на классовой ГМП. Причина в том, что модель классов ориентирована на отображение статических характеристик проекта, а в данном случае требуется отображение поведенческих характеристик. Как правило, тес-тирование взаимодействия нитей в ходе исполнения программного комплекса выносится на уровень системного тестирования и использует другие более при-способленные для описания поведения модели. Например, описание поведения программного комплекса средствами языков спецификаций MSC, SDL, UML.

Явный учет границ между интеграционным и системным уровнями тес-тирования дает преимущество при планировании работ на фазе тестирования, а

Page 78: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

78

возможность сочетать различные методы и критерии тестирования в ходе рабо-ты над программным проектом дает наилучшие результаты [11].

Объектно-ориентированный подход, ставший в настоящее время неявным стандартом разработки программных комплексов, позволяет широко использо-вать иерархическую модель программного проекта, способ применения кото-рой иллюстрирует приведенная на Рис. 21 схема. Каждый класс рассматривается как объект модульного и интеграционного тестирования. Сначала каждый ме-тод класса тестируется как модуль по выбранному критерию C. Затем класс становится объектом интеграционного тестирования. Далее осуществляется ин-теграция всех методов всех классов в единую структуру – классовую модель проекта, где в общую ГМП протестированные модули входят в виде узлов (ин-терфейсов вызова) без учета их внутренней структуры, а их детальные описа-ния образуют контекст всего программного проекта.

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

4.2.3. Пример интеграционного тестирования Продемонстрируем тестирование взаимодействий на примере взаимодей-

ствия класса TCommandQueue и класса TСommand. Как и при модульном тести-ровании разработаем спецификацию тестового случая (Таблица 6).

Таблица 6 Спецификация тестового случая для интеграционного тестирования

Названия взаимодействующих классов: TСommandQueue, TCommand

Название теста: TCommandQueueTest1

Описание теста. Тест проверяет возможность создания объекта типа TCommand и добавления его в очередь при вызове метода AddCommand

Начальные условия: очередь команд пуста

Ожидаемый результат: в очередь будет добавлена одна команда

На основе этой спецификации разработан тестовый драйвер – класс

TCommandQueueTester, который наследуется от класса Tester (п. 4.1.4). Класс содержит следующий конструктор, в котором создаются объекты

классов TStore, TterminalBearing и объект типа TcommandQueue;

Page 79: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

79

public TCommandQueueTester() { TB = new TTerminalBearing(); S = new TStore(); CommandQueue=new TCommandQueue(S,TB); S.CommandQueue=CommandQueue; ... }

И методы, реализующие тесты. Каждый тест реализован в отдельном ме-тоде.

1) Метод Run, в котором вызываются методы тестов. 2) Метод dump, который сохраняет в log-файле теста информацию обо

всех командах, находящихся в очереди в формате <N : CommandName> – <но-мер позиции в очереди: полное название команды>.

3) Точка входа в программу – метод Main, в котором происходит создание экземпляра класса TСommandQueueTester.

Теперь создадим тест, который проверяет, создается ли объект типа TСommand, и добавляется ли команда в конец очереди. private void TCommandQueueTest1() { LogMessage("//////////// TCommandQueue Test1 /////////////"); LogMessage("Проверяем, создается ли объект типа TCommand"); // В очереди нет команд dump(); // Добавляем команду // параметр = -1 означает, что команда должна быть добавлена в конец очереди CommandQueue.AddCommand(TCommand.GetR, 0, 0, 0, new TBearingParam(), new TAxleParam(),-1); LogMessage("Command added"); // В очереди одна команда dump(); }

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

чтобы сравнить полученные результаты с ожидаемыми результатами, заданны-ми в спецификации тестового случая TCommandQueueTest1. //////////////////// TCommandQueue Test1 ////////////////// Проверяем, создается ли объект типа TCommand 0 commands in command queue Command added 1 commands in command queue 0: ПОЛУЧИТЬ ИЗ ВХОДНОЙ ЯЧЕЙКИ

Page 80: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

80

4.3. Системное тестирование Системное тестирование качественно отличается от интеграционного и

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

Системное тестирование производится над проектом в целом с помощью метода «черного ящика». Структура программы не имеет никакого значения, для проверки доступны только входы и выходы, видимые пользователю. Тести-рованию подлежат коды и пользовательская документация.

4.3.1. Категории тестов системного тестирования Выделяют следующие виды системных тестов: 1. Полнота решения функциональных задач. 2. Стрессовое тестирование – на предельных объемах нагрузки входного по-

тока. 3. Корректность использования ресурсов (утечка памяти, возврат ресурсов). 4. Оценка производительности. 5. Эффективность защиты от искажения данных и некорректных действий. 6. Проверка инсталляции и конфигурации на разных платформах. 7. Корректность документации

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

Page 81: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

81

4.3.2. Пример системного тестирования приложения Рассмотрим для системы «Система управления автоматизированным

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

Описание варианта использования системы представлено на рисунке (Рис. 22).

CистемаТерминал оси Терминал

подшипника Склад

1. Опрос статуса склада

2. Возврат статуса

3. При поступлении подшипниказапрос характеристик подшипника

4. Возврат характеристикподшипника

5. Опрос терминала оси

6. Возврат характеристикоси

7. Посылка складу команды -“положить подшипник”

8. Возврат сообщенияо приеме команды

10. Возврат сообщения орезультатах выполнения

команды

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

11. КОНЕЦ

Рис. 22. Краткое описание тестируемой системы «Поступление подшипника на склад»

Спецификация тестового случая №1:

Состояние окружения (входные данные – X ): Статус склада – 32. Пришел подшипник. Статус обмена с терминалом подшипника (0 – есть подшипник) и его парамет-ры – «Статус=0 Диаметр=12». Статус обмена с терминалом оси (1 – нет оси) и ее параметры – «Статус=1 Диаметр=12».

Page 82: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

82

Статус команды – 0. Команда успешно принята. Сообщение от склада – 1. Команда успешно выполнена. Ожидаемая последовательность событий (выходные данные – Y): a. Система запрашивает статус склада (вызов функции GetStoreStat) и получает

32. b. Система запрашивает параметры подшипника (вызов функции GetRollerPar)

и получает Статус = 0 Диаметр=12. c. Система запрашивает параметры оси (вызов функции GetAxlePar) и получает

Статус = 1 Диаметр=0. d. Система добавляет в очередь команд склада на последнее место команду

SendR (получить из приемника в ячейку) (вызов функции SendStoreCom) и получает сообщение о том, что команда успешно принята – статус = 0.

e. Система запрашивает склад о результатах выполнения команды (вызов функции GetStoreMessage) и получает сообщение о том, что команда успеш-но выполнена – статус = 1.

Выходные данные (результаты выполнения Yв) зафиксированы в журнале тес-та: ВЫЗОВ: GetStoreStat РЕЗУЛЬТАТ: 32 ВЫЗОВ: GetRollerPar РЕЗУЛЬТАТ: Статус = 0 Диаметр = 12 ВЫЗОВ: GetAxlePar РЕЗУЛЬТАТ: Статус = 1 Диаметр = 0 ВЫЗОВ: SendStoreCom РЕЗУЛЬТАТ: 0 ВЫЗОВ: GetStoreMessage РЕЗУЛЬТАТ: 1

Приведенный ниже тест был разработан в соответствии со спецификаци-

ей тестового случая №1. class Test1:Test { override public void start() { //Задаем состояние окружения (входные данные) StoreStat="32"; //Поступил подшипник RollerPar="0 NewUser Depot1 123456 1 12 1 1"; //статус обмена с терминалом подшипника (0 – есть подшипник) и его параметры AxlePar="1 NewUser Depot1 123456 1 0 12 12"; //статус обмена с терминалом оси (1 – нет оси) и ее параметры CommandStatus="0"; //команда успешно принята StoreMessage="1"; //успешно выполнена

Page 83: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

83

//Получаем информацию о функционировании системы wait(“GetStoreStat”); //опрос статуса склада wait(“GetRollerPar”); //Получение информации о подшипнике с терминала подшипника wait(“GetAxlePar”); //Получение информации об оси с терминала оси wait(“SendStoreCom”); //добавление в очередь команд склада // на первое место //команды GetR (получить из приемника в ячейку) wait(“GetStoreMessage”); //Получение сообщения от склада о результатах выполнения команды //В результате первый подшипник должен быть принят } }

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

Пример журнала теста: Test started CALL:GetStoreStat 0 RETURN:32 CALL:GetRollerPar RETURN:0 NewUser Depot1 123456 1 12 1 1 CALL:GetAxlePar RETURN:1 NewUser Depot1 123456 1 0 12 12 CALL:SendStoreCom 1 0 0 1 0 0 0 RETURN:0 CALL:GetStoreMessage RETURN:1

4.4. Регрессионное тестирование Регрессионное тестирование – цикл тестирования, который производит-

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

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

Page 84: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

84

4.4.1. Пример регрессионного тестирования Получив отчет об ошибке, программист анализирует исходный код, на-

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

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

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

правления привнесли ошибку (наведенную ошибку) в код, который до этого исправно работал.

Например, при тестировании класса TСommandQueue запускают следую-

щий набор тестов: // Тест проверяет, создается ли объект типа TCommand и //добавляется ли он в конец очереди. private void TCommandQueueTest1() // Тест проверяет добавление команд в очередь на указанную позицию. // Также проверяется правильность удаления команд из очереди. private void TCommandQueueTest2()

При этом первый тест выполняется успешно, а второй нет, т. е. команда добавляется в конец очереди команд успешно, а на указанную позицию – нет. Разработчик анализирует код, который реализует тестируемую функциональ-ность: ... if ((Position < -1)&&(Position<=this.Items.Count)) { this.Items.Insert(Position, Command); } else { if (Position==-1)

{ this.Items.Add(Command); } } ...

Page 85: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

85

Анализ показывает, что ошибка заключается в использовании неверного знака сравнения в первой строке фрагмента (выделено другим тоном). Далее программист исправляет ошибку, например, следующим образом: ... if ((Position >= -1)&&(Position<=this.Items.Count)) { this.Items.Insert(Position, Command); } else { if (Position==-1)

{ this.Items.Add(Command); } } ...

Для проверки скорректированного кода хочется пропустить только тест TCommandQueueTest2. Можно убедиться, что тест TCommandQueueTest2 будет выполняться успешно. Однако одной этой проверки недостаточно. Если мы по-вторим пропуск двух тестов, то при запуске первого теста, TCommandQueueTest1, будет обнаружен новый дефект. Повторный анализ кода показывает, что ветка else не выполняется. Таким образом, исправление в од-ном месте привело к ошибке в другом, что демонстрирует необходимость про-ведения полного перетестирования. Однако повторное перетестирование требу-ет значительных усилий и времени. Возникает задача – отобрать сокращенный набор тестов из исходного набора (может быть, пополнив его рядом дополни-тельных, вновь разработанных тестов), которого, тем не менее, будет достаточ-но для исчерпывающей проверки функциональности в соответствии с выбран-ным критерием. Организация повторного тестирования в условиях сокращения ресурсов, необходимых для обеспечения заданного уровня качества продукта, обеспечивается регрессионным тестированием.

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

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

Page 86: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

86

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

Таблица 7 Характеристики модульного, интеграционного и системного тестирования

Модульное Интеграционное Системное Типы дефектов Локальные дефек-

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

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

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

Необходимость в системе тес-тирования

Да Да Нет*

Цена разработ-ки системы тес-

тирования

Низкая Низкая до уме-ренной

Умеренная до высокой или неприемлемой

Цена всего процесса тести-

рования

Низкая Низкая Высокая

4.6. Автоматизация тестирования Использование различных подходов к тестированию определяется их эф-

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

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

Продолжение табл. 7

Page 87: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

87

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

На рисунке (Рис. 23) приведены структура теста, структура тестируемого комплекса и структура тестирующего модуля.

Особенностью структуры каждого из тестирующих модулей Mi является

запуск тестирующей программы Pi после того как каждый из модулей Mij, вхо-дящих в контекст модуля Mi, оттестирован. В этом случае запуск тестирующе-

Структура программы P теста Загрузка теста (X,Y*) Запуск тестируемого модуля Cравнение полученных результатов Y c эталонными Y* Структура тестируемого комплекса ModF ← МоdF1 МоdF2 МоdF3 ← МоdF31 МоdF32 Структура тестирующего модуля Мод TestModF: Mod TestМодF1 Mоd TestМодF2 Mоd TestМодF3 P TestМодF Мод TestModF1: P TestМодF1 Мод TestModF2: P TestМодF2 Мод TestModF3: Mod TestМодF31 Mоd TestМодF32 P TestМодF3

Рис. 23. Структура тестового набора в системе автоматизации тестирования

Page 88: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

88

го модуля обеспечивает рекурсивный спуск к программам тестирования моду-лей нижнего уровня, а затем исполняет тестирование вышележащих уровней в условиях оттестированности нижележащих. Тестовые наборы подобной струк-туры ориентированы на автоматическое управление пропуском тестового набо-ра в тестовом цикле. Важным преимуществом подобной организации является возможность регулирования нижнего уровня, до которого следует доходить в цикле тестирования. В этом случае контекст редуцированных в конкретном тестовом цикле модулей помечается как базовый, не подлежащий тестирова-нию. Например, если контекст модуля ModF3: (ModF31, ModF32) – помечен как базовый, то в результате рекурсивный спуск затронет лишь модули ModF1, ModF2, ModF3 и вышележащий модуль ModF. Описанный способ организации тестовых наборов применяется в системах автоматизации тестирования.

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

На Рис. 24 представлена обобщенная структура системы автоматизации тестирования, в которой создается и сохраняется следующая информация: • Набор тестов, достаточный для покрытия тестируемого приложения в соот-

ветствии с выбранным критерием тестирования – как результат ручной или автоматической разработки (генерации) тестовых наборов и драй-вер/монитор пропуска тестового набора.

• Результаты прогона тестового набора, зафиксированные в Log-файле. Log-файл содержит трассы («протоколы»), представляющие собой реализован-ные при тестовом прогоне последовательности некоторых событий (значе-ний отдельных переменных или их совокупностей) и точки реализации этих событий на графе программы. В составе трасс могут присутствовать после-довательности явно и неявно заданных меток, задающих пути реализации трасс на управляющем графе программы, совокупности значений перемен-ных на этих метках, величины промежуточных результатов, достигнутых на некоторых метках и т.п.

Page 89: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

89

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

Рис. 24. Структура инструментальной системы автоматизации тестирования

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

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

Page 90: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

90

4.7. Издержки тестирования Интенсивность обнаружения ошибок на единицу затрат и надежность

тесно связаны со временем тестирования и, соответственно, с гарантией качест-ва продукта (Рис. 25A). Чем больше трудозатрат вкладывается в процесс тести-рования, тем меньше ошибок в продукте остается незамеченными. Однако со-вершенство в индустриальном программировании имеет пределы, которые прежде всего связаны с затратами на получение программного продукта, а так-же с избытком качества, которое не востребовано заказчиком приложения. На-хождение оптимума – очень ответственная задача тестировщика и менеджера проекта.

Рис. 25. Издержки тестирования

Движение к уменьшению числа оставшихся ошибок или к качеству про-

дукта приводит к применению различных методов отладки и тестирования в процессе создания продукта. На Рис. 25В приведен затратный компонент тес-тирования в зависимости от совершенствования применяемого инструментария и методов тестирования.

На практике популярны следующие методы тестирования и отладки, упорядоченные по связанным с их применением затратам:

1. Статические методы тестирования 2. Модульное тестирование

Page 91: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

91

3. Интеграционное тестирование 4. Системное тестирование 5. Тестирование реального окружения и реального времени Зависимость эффективности применения перечисленных методов или их

способности к обнаружению соответствующих классов ошибок (С) сопоставле-на на Рис. 25 С с затратами (B). График показывает, что со временем, по мере обнаружения более сложных ошибок и дефектов, эффективность низкозатрат-ных методов падает вместе с количеством обнаруживаемых ошибок.

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

4.8. Контрольные вопросы и упражнения 1. Какие существуют разновидности тестирования? 2. Каковы задачи модульного тестирования? 3. Какие принципы положены в основу модульного тестирования? 4. Какие критерии используются при тестировании на основе потока управ-

ления? 5. Какие критерии используются при тестировании на основе потока данных? 6. Каковы фазы процесса построения тестовых путей? 7. Какие существуют методы построения тестовых путей? Назовите их дос-

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

методом интеграционного тестирования? 11. В чем различие монолитного и инкрементального методов сборки моду-

лей? 12. Каковы особенности нисходящего тестирования? 13. Каковы особенности нисходящего тестирования? 14. Представьте порядок тестирования модулей комплекса К (Рис. 15) в виде

графа для нисходящего (восходящего) тестирования. 15. Какие существуют особенности интеграционного тестирования для проце-

дурного программирования? 16. Каково выражение для оценки сложности интеграционного тестирования?

Page 92: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

92

17. Для графовых моделей программ, построенных вами по соответствующим заданиям из разделов 1 и 2, определите сложность интеграционного тести-рования.

18. Каково выражение для оценки сложности графа вызовов? 19. Какие этапы методики тестирования используются в объектно-

ориентированном программировании? 20. Какие возможности повторного использования тестов класса представляет

объектно-ориентированное программирование? 21. Каковы особенности тестовой модели объектно-ориентированной про-

граммы? 22. Что используется для построения дуг графовой модели в ООП? 23. Какова формула оценки сложности интеграционного тестирования для

ООП? 24. Каковы особенности системного тестирования? 25. Какие задачи решаются на этапе системного тестирования? 26. Какие категории тестов разрабатываются для системного тестирования? 27. Каковы особенности регрессионного тестирования? 28. Какие задачи решает тестировщик, проверяя изменения, внесенные разра-

ботчиком в код? 29. Какие типы дефектов выявляются при системном или регрессионном тес-

тировании? 30. Каковы особенности тестовых наборов, используемых в промышленных

проектах? 31. Какие этапы процесса тестирования поддаются автоматизации? 32. Какие этапы процесса тестирования выполняются вручную? 33. Какую информацию использует и производит система автоматизации тес-

тирования в цикле тестирования? 34. Какие из рассмотренных методов тестирования наиболее затратны? 35. Какие из рассмотренных методов тестирования наиболее результативны?

ЗАКЛЮЧЕНИЕ Вы познакомились с основными проблемами, технологиями и разновид-

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

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

Page 93: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

93

БИБЛИОГРАФИЧЕСКИЙ СПИСОК 1. C# 2005 для профессионалов / К. Нейгел, Б. Ивьен, Д. Глинн, К. Уотсон,

М. Скиннер, А. Джонс. – Москва ; Санкт-Петербург ; Киев.: «Диалектика», 2007.

2. IEEE Software Engineering Standards Collection 1997 Edition 3. IEEE Std 610.12-1990, IEEE Standard Glossary of Software Engineering Tech-

nology 4. Боэм, Б. У. Инженерное проектирование программного обеспечения /

Б. У. Боэм. ; Пер. с англ. – М. : Радио и связь, 1985. 5. Брукс, Ф. Мифический человеко-месяц или как создаются программные

системы / Ф. Брукс. – СПб. : Символ-Плюс, 1999. 6. Воройский, Ф. С. Информатика. Новый систематизированный толковый

словарь-справочник / Ф. С. Воройский. – М. : ФИЗМАТЛИТ, 2003. 7. Канер, С. Тестирование программного обеспечения / С. Канер, Дж. Фолк,

Енг. Нгуен. – К : ДиаСофт, 2000. 8. Кариев, Ч. А. Разработка Windows-приложений на основе Visual C# /

Ч. А. Кариев. – М. : БИНОМ. Лаборатория знаний, Интернет-университет информационных технологий – ИНТУИТ.ру, 2007.

9. Классы, интерфейсы и делегаты в С# 2005 : учебное пособие / сост. О. Н. Евсеева, А. Б. Шамшев. – Ульяновск : УлГТУ, 2008.

10. Липаев, В. В. Тестирование программ / В. В. Липаев. – М. : Радио и связь, 1986.

11. Майерс, Г. Искусство тестирования программ / Г. Майерс – М. : Финансы и статистика, 1982.

12. Макгрегор, Дж. Тестирование объектно-ориентированного программного обеспечения / Дж. Макгрегор, Д. Сайкс. – К : Диасофт, 2002.

13. Марченко, А. Л. Основы программирования на C# 2.0 / А. Л. Марченко. – М. : БИНОМ. Лаборатория знаний, Интернет-университет информацион-ных технологий – ИНТУИТ.ру, 2007

14. Основы современного тестирования программного обеспечения, разрабо-танного на С# : учебное пособие / В. П. Котляров, Т. В. Коликова ; под ред. В. П. Котлярова. – СПб., 2004.

15. Основы языка С# 2005 : учебное пособие / сост. О. Н. Евсеева, А. Б. Шамшев. – Ульяновск : УлГТУ, 2008.

16. Шимаров, В. А. Тестирование программ: цели и особенности инструмен-тальной поддержки // Программное обеспечение ЭВМ / АН БССР. Инсти-тут математики. – Минск, 1994. – Вып. 100 – с. 19-43

Page 94: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

94

ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ

# #define, 12

A Abort, 18 Add, 15 AddRange, 15 Assert, 16, 17, 18 AUT, 30 AutoFlush, 16

B BooleanSwitch, 19 break mode, 33 breakpoint, 33 breakpoints, 32 build, 60, 87

C call pair coverage, 61 Clear, 15 Close, 16 CodeCheker, 31 Conditional, 12

D debug, 30 Debug, 12, 15 debugging, 30 DefaultTraceListener, 15 deskchecking, 32 Diagnostics, 15 dump, 32

E EventLogTraceListener, 15 Exception, 21

F Fail, 16 false, 20 Flush, 16

I Insert, 15 Integration testing, 43 IUT, 30

L Listeners, 15 logging, 32 log-файл, 35

M Method/Message path, 72 MM-path, 72

O oracle, 31 Output, 15

P P-path, 72 Procedure path, 72

R Release, 12 Remove, 15 RemoveAt, 15 Repository, 60 Rescue, 24 Retry, 18, 24 reversible execution, 32

S single-step running, 32 StackTrace, 19 Step Into, 33 Step Out, 33 Step Over, 33 Stream, 15 stub, 66 Stub, 65

Page 95: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

95

T test driver, 38, 66 test incident, 32 test log, 38 test monitor, 38 test suite, 38 Testbed, 31 Testbench, 31 testing environment, 38 TextWriter, 15 TextWriterTraceListener, 15 throw, 21 Trace, 15 TraceListener, 15 TraceListenerCollection, 15 traces, 32 true, 20

U Unit testing, 43

W Write, 15 WriteIf, 15, 19 WriteLine, 15 WriteLineIf, 15

А анализ потока данных, 61 потока управления, 61

анализ тестовых случаев, 37 АСФ, 76

Б блок

catch, 20, 21, 22, 23 finally, 21, 24 try, 20, 21, 23

В выполнение тестовых случаев, 38

Г ГМП, 72 граф программы, управляющий, 39

Д Дамп, 35 достаточность, 43

Ж жизненный цикл, 8

И исключение выбрасывание, 21 захват, 22

исключительная ситуация обработка, 7, 20

К качество, 7 классы критериев, 43 компиляция, условная, 12 константы условной компиляции, 12

DEBUG, 12 TRACE, 12

контроль, детерминированный, 49 контроль, стохастический, 49 конфигурация проекта, 12 корректность, 7 критерий

Cdu, 62 мутационный, 43, 50 покрытия ветвей С1, 44, 69 покрытия вызовов, 61 покрытия команд С0, 44 покрытия путей С2, 44 покрытия условий, 61 покрытия функций программы, 61 СР, 62 стохастического тестирования, 43, 49 структурный, 43 функциональный, 43, 45

М метод Бертрана, 24 внедрения «агентов», 32 динамический, 63 реализуемых путей, 63 статический, 62 Флойда, 17

метод сборки инкрементальный, 66

Page 96: ОТЛАДКА И ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЙ VISUAL STUDIO …venec.ulstu.ru/lib/disk/2008/Evseeva.pdf · А. Б. Шамшев. – Ульяновск : УлГТУ,

96

монолитный, 66 модель

«белого ящика», 43, 66 «черного ящика», 45 классов, 74 скорости выявления ошибок, 49

модель программы иерархическая, 55 плоская, 53

монитор, тестовый, 38 мутанты, 51 мутации, 50

Н набор, тестовый, 38 надежность, 43

О обратное выполнение программы, 35 Оракул, 31 отладка, 11, 30 отладочная печать, 11 оценка результатов тестирования, 38

П полнота, 43 проверяемость, 43 протокол результатов тестирования,

38 путь метод/сообщение (ММ-путь), 72 процедурный (Р-путь), 72

Р разработка тестов, 37 режим останова, 33

С сборка, 87 сложность, 52, 68 интеграционного тестирования, 69 класса, 73

остаточная, 52 спецификация программы, 37 среда тестирования, 38

стратегия «сверху вниз», 66 «снизу вверх», 66

структура теста, 86 тестируемого комплекса, 86 тестирующего модуля, 86

Т тест неизбыточный, 52

тестирование, 8, 30 восходящее, 67 динамическое, 31 издержки, 89 интеграционное, 65, 68 классов входных данных, 45 классов выходных данных, 46 комбинированное, 46 критерий окончания, 53 модульное, 60 монолитное, 66 нисходящее, 67 оценка, интегральная, 58 полнота, 53 правил, 46 пунктов спецификации, 45 регрессионное, 82 системное, 79 статическое, 31 стохастическое, 48 функций, 46

точка, контрольная, 33 трасса, 34

У УГП, 39 устойчивость, 7

Ф функция, атомарная системная, 76

Я язык, спецификаций, 76