Обложка
Глава 1. Введение
Что представляет собой тестирование программных продуктов и чем оно 
							не является
Какими особенностями обладает тестирование объектно-ориентированного 
							программного обеспечения?
Обзор принятого подхода к тестированию
Тестировать часто
Тестировать в полном объеме
Перспектива тестирования
Структура книги
Соглашения, принятые в данной книге
Переходящий пример - игра Bricles
Физические основы игры
Среда, в которой реализована игра
Упражнения
Глава 2. Перспектива тестирования
Понятия объектно-ориентированного программирования
Сообщение
Интерфейс
Класс
Наследование
Полиморфизм
Продукты разработки
Проектные модели
Исходные коды
Резюме
Упражнения
Глава 3. Планирование работ по тестированию
Обзор процесса тестирования
Анализ рисков как средство тестирования
Анализ рисков
Процесс тестирования
Размерности тестирования программного обеспечения
Кто выполняет тестирование?
Какие компоненты подлежат тестированию?
Как часто следует выполнять тестирование?
Как выполняется тестирование?
Какие объемы тестирования следует считать адекватными?
Роли в процессе тестирования
Специалист по комплексным испытаниям
Системный тестировщик
Руководитель испытаний
Подробное описание набора видов деятельности в тестировании
Планирование как вид деятельности
Оценка
Процесс тестирования игры \\
Шаблоны документов
Система показателей тестирования
Резюме
Упражнения
Глава 4. Тестирование аналитических и проектных моделей
Место в процессе разработки
Основные понятия целенаправленной проверки
Организация целенаправленной инспекционной деятельности
Индивидуальная проверка
Подготовка проверки
Реалистичные модели
Отбор тестовых случаев для целей проверки
Построение тестовых случаев
Завершение построения контрольной таблицы
Сеанс интерактивной проверки
Тестирование специальных типов моделей
Модели анализа
Проектные модели
Повторное тестирование
Модели для тестирования дополнительных свойств
Резюме
Упражнения
Приложение: Определение процесса для целенаправленной проверки
Подробное описание шагов
Роли в процессе тестирования
Глава 5. Основы тестирования классов
Оцениваемые факторы тестирования классов
Построение тестовых случаев
Построение тестового драйвера
Структура класса Tester
Резюме
Упражнения
Глава 6. Тестирование взаимодействия и функционирования компонентов
Определение взаимодействий
Тестирование взаимодействий объекта
Тестирование взаимодействующих классов
Связь между тестированием и подходом к проектированию
Выбор тестовых случаев
Критерий адекватности системы OATS
Еще один пример
Еще одно применение системы OATS
Тестирование серийно выпускаемых компонентов
Тестирование протоколов
Тестовые шаблоны
Конкретный пример
Тестирование взаимодействий на системном уровне
Резюме
Упражнения
Глава 7. Тестирование иерархий классов
Требования, предъявляемые к тестированию подклассов
Иерархическое инкрементальное тестирование
Организация тестового программного обеспечения
Тестирование абстрактных классов
Резюме
Упражнения
Глава 8. Тестирование распределенных объектов
Вычислительные модели
Параллельная модель
Сетевая модель
Распределенная модель
Основные различия
Дополнительная инфраструктура
Частичные отказы
Тайм-ауты
Динамическая природа структуры
Потоки
Тестирование путей в распределенных системах
Тестирование жизненного цикла
Модель распределения
Стандартные модели распределения
Сравнение и выводы
Обобщенная модель с распределенными компонентами
Локальные и удаленные интерфейсы
Описание распределенных объектов
Традиционные предусловия, постусловия и инварианты
Временная логика
Условия испытаний
Тестирование взаимодействий
Тестовые случаи
Тестирование каждого предположения
Тестирование инфратруктуры
Тестовые случаи для проверки логических ошибок
Максимальный вариант распределенной системы - Internet
Тестирование Internet-приложений на протяжении их жизненного цикла
Что мы еще не успели сказать?
Резюме
Упражнения
Глава 9. Тестирование систем
Критерии приостановки тестирования и требования возобновления 
								тестирования
Дополнительные стратегии, используемые при отборе тестовых случаев
Классификация ODC
Случаи использования как источники тестовых случаев
Использование сценариев для построения тестовых случаев
Раздел ожидаемых результатов в тестовом случае
Тестирование инкрементальных проектов
Тестирование множественных представлений
Что необходимо подвергнуть тестированию
Проверка качественных системных атрибутов
Тестирование механизма развертывания системы
Тестирование после развертывания системы
Тестирование взаимодействий окружения
Тестирование системы безопасности
Типы тестирования
Тестирование на протяжении жизненного цикла
Тестирование характеристик производительности
Тестирование систем различных типов
Встроенные системы
Многоуровневые системы
Распределенные системы
Измерение тестового покрытия
Когда покрытие измеряется?
Когда используется покрытие?
Классификация ODC
Дополнительные случаи
Резюме
Упражнения
Глава 10. Компоненты, каркасы и линии продуктов
Модель компонента Enterprise JavaBeans
Тестирование компонентов и объектов
Процессы тестирования компонентов
Процесс тестирования компонентов на стадии приемочных испытаний
Тестовые случаи, основанные на интерфейсах
Реальный пример - компонент GameBoard
Структуры
Процесс тестирования структур
Проверка структуры
Структурирование тестовых случаев с целью поддержки структуры
Линии продуктов
Тестирование на уровне технического управления
Тестирование на уровне проектирования программного обеспечения
Тестирование в рамках проекта линии продуктов
Дальнейшие перспективы
Резюме
Упражнения
Глава 11. Заключение
Данные
Стандарты
Инфраструктура программного обеспечения
Технологии
Риски
Игра
Резюме
Библиография
Предметный указатель
Обложка
Текст
                    Джон Макгрегнр, Девнд Сайкс
1---------------------------------
Тестирование
объектно-ориентированного
программного обеспечения
---------:-----------. -
Разработчикам
Менеджерам проекгоь
Программистам
Тестирование - это прежде всего оценка
промежуточных продуктов, созданных в
процессе разработки программного
обеспечения. Тестирование представляет
собой более широкое поле деятельности,
нежели простая проверка на предмет
соответствия техническим требованиям
некоторых частей или программной
OBJECT TECHNOLOGY
BOOTH
JflCOBSOH
HUMBflUGH
DiaSoft
Оотр«*пм вкивпа» 
издательство
системы в целом.

A Practical Guide to Testing Object-Oriented Software John D. McGregor David A. Sykes Addison-Wesley Boston*San Francisco*New York*Toronto*Montreal London*Munich*Paris*Madrid Capetown*Sydney*Tokyo*Singapore*Mexico City
Тестирование объектно-ориентированного программного обеспечения Практическое пособие Джон Макгрегор, Девид Сайкс грРЯЩ юргово-иадателкский дом Io DiaSoft
УДК 004.415 ББК 32.973.26-018.2 М 15 Макгрегор Джон, Сайкс Девид М 15 Тестирование объектно-ориентированного программного обеспечения. Практическое пособие: Пер. с англ./Джон Макгрегор, Девид Сайкс. - К.: ООО «ТИД «ДС», 2002. - 432 с. ISBN 966-7992-12-8 Книга Тестирование объектно-ориентированного программного обеспечения. Практи- ческое пособие основное внимание уделяет реальному планированию и эффективной реа- лизации процесса тестирования объектно-ориентированного и компонентного программ- ного обеспечения. Подробно рассматриваются концептуальные отличия технологий тестирования объектно-ориентированного программного обеспечения от таковых для тра- диционного процедурного программного обеспечения. Благодаря огромному опыту имени- тых авторов, книга может послужить эффективным практическим и учебным руковод- ством для профессиональных разработчиков, предлагая готовые технологии построения надежного, предсказуемого и высокоэффективного программного обеспечения с тестиро- ванием на всех этапах - анализа, проектирования и реализации. Среди прочих, внимание уделяется таким вопросам, как: тестирование аналитических и проектных моделей, тести- рование иерархии наследования, тестирование классов, тестирование взаимодействий между объектами, тестирование распределенных объектов, эффективный выбор тестовых набо- ров. Предлагаются уникальные методики подбора тестовых случаев, обеспечивающих максимальное покрытие и адекватность тестирования. Несмотря на то что многие до сего момента воспринимают сам процесс тестирования как неизбежное зло, после тщательного изучения этой книги их взгляды гарантированно изменятся на противоположные. Особенную пользу книга окажет разработчикам объект- но-ориентированного программного обеспечения для западных компаний. ББК 2.973.26-018.2 Authorized translation from the English language edition, entitled Practical Guide to Testing Object- Oriented Software, First Edition by John McGregor, published by Pearson Education, Inc, publishing as ADDISON WESLEY LONGMAN, Copyright © 2001 All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc. Russian language edition published by DIASOFT PUBLISHING LTD., Copyright © 2002 Лицензия предоставлена издательством ADDISON WESLEY LONGMAN. Все права зарезервированы, включая право на полное или частичное воспроизведение в какой бы то ни было форме. ISBN 966-7992-12-8 (рус.) © ООО «ТИД «ДС», 2002 ISBN 0201325640 (англ.) © ADDISON WESLEY LONGMAN, 2001 © Оформление. ООО «ТИД «ДС», 2002 Гигиеническое заключение №77.99.6.953.11.438.2.99 от 04.02.1999
Оглавление Глава 1. Введение................................................15 Для кого предназначена эта книга?................................17 Что представляет собой тестирование программных продуктов и чем оно не является........................................... 18 Какими особенностями обладает тестирование объектно-ориентированного программного обеспечения?..............20 Обзор принятого подхода к тестированию...........................22 Тестировать как можно раньше...................................23 Тестировать часто..............................................23 Тестировать в полном объеме....................................23 Перспектива тестирования.........................................24 Структура книги..................................................24 Соглашения, принятые в данной книге..............................26 Переходящий пример — игра Brides.................................27 Базовые компоненты игры «Кирпичики»............................27 Физические основы игры.........................................28 Среда, в которой реализована игра............................ 30 Упражнения .......................................................30 Глава 2. Перспектива тестирования................................32 Перспектива тестирования.........................................32 Понятия объектно-ориентированного программирования...............34 Объекты........................................................35 Сообщение......................................................37 Интерфейс......................................................38 Класс..........................................................39 Наследование...................................................50 Полиморфизм....................................................51 Продукты разработки..............................................59 Аналитические модели...........................................60 5
6 Оглавление Проектные модели..............................................76 Исходные коды.................................................79 Резюме...........................................................83 Упражнения.......................................................83 Глава 3. Планирование работ по тестированию......................84 Обзор процесса разработки........................................85 Обзор процесса тестирования......................................89 Анализ рисков как средство тестирования..........................94 Риски.........................................................94 Анализ рисков.................................................95 Процесс тестирования.............................................99 Проблемы планирования.........................................99 Размерности тестирования программного обеспечения............100 Кто выполняет тестирование?................................. 101 Какие компоненты подлежат тестированию?......................102 Как часто следует выполнять тестирование?................... 103 Как выполняется тестирование?............................... 104 Какие объемы тестирования следует считать адекватными?...... 107 Роли в процессе тестирования................................... 109 Тестировщик классов......................................... 109 Специалист по комплексным испытаниям........................ 109 Системный тестировщик........................................ ПО Руководитель испытаний...................................... 110 Подробное описание набора видов деятельности в тестировании..... ПО Планирование как вид деятельности............................... НО Планирование работ по тестированию.......................... 114 Оценка...................................................... 114 Процесс тестирования игры «Кирпичики»....................... 117 Шаблоны документов.......................................... 118 Система показателей тестирования............................ 130 Резюме..........................................................132 Упражнения......................................................132
Оглавление 7 Глава 4. Тестирование аналитических и проектных моделей.........133 Обзор.......................................................... 134 Место в процессе разработки.................................... 138 Основные понятия целенаправленной проверки..................... 139 Критерии оценки...............................................142 Организация целенаправленной инспекционной деятельности........ 144 Основные роли................................................ 144 Индивидуальная проверка...................................... 145 Подготовка проверки............................................ 145 Описание проверки............................................ 145 Реалистичные модели.......................................... 146 Отбор тестовых случаев для целей проверки....;............... 149 Построение тестовых случаев.................................. 152 Завершение построения контрольной таблицы.................... 152 Сеанс интерактивной проверки................................. 154 Тестирование специальных типов моделей......................... 156 Модели требований............................................ 157 Модели анализа................................................163 Проектные модели..............................................166 Повторноетестирование........................................ 176 Модели для тестирования дополнительных свойств................. 177 Резюме......................................................... 180 Контрольный список действий, выполняемых в процессе тестирования моделей.............................. 180 Упражнения..................................................... 182 Приложение: Определение процесса для целенаправленной проверки.. 182 Этапы процесса................................................182 Подробное описание шагов......................................182 Роли в процессе тестирования................................. 187 Глава 5. Основы тестирования классов............................188 Тестирование классов........................................... 188 Способы тестирования классов................................. 189 Оцениваемые факторы тестирования классов..................... 190
8 Оглавление Построение тестовых случаев................................... 194 Адекватность тестовых наборов, предназначенных для тестирования класса......................................205 Построение тестового драйвера..................................209 Требования, предъявляемые к тестовым классам.................212 Структура класса Tester......................................214 Резюме.........................................................236 Упражнения.....................................................237 Глава 6. Тестирование взаимодействия и функционирования компонентов....................................................238 Взаимодействия объектов........................................239 Идентификация взаимодействий.................................241 Определение взаимодействий...................................246 Тестирование взаимодействий объекта............................247 Тестирование коллекций.......................................247 Тестирование взаимодействующих классов.......................248 Связь между тестированием и подходом к проектированию........249 Выбор тестовых случаев.........................................251 Ортогональная матрица тестирования...........................255 Критерий адекватности системы OATS...........................259 Еще один пример..............................................262 Еще одно применение системы OATS.............................264 Тестирование серийно выпускаемых компонентов...................264 Изучение конкретных примеров использования в рамках приемочных испытаний................................265 Тестирование протоколов........................................269 Тестовые шаблоны...............................................269 Тестовый шаблон Listener.....................................270 Конкретный пример............................................272 Тестирование взаимодействий на системном уровне..............275 Резюме.........................................................276 Упражнения.....................................................276
Оглавление 9 Глава 7. Тестирование иерархий классов.........................277 Наследование в объектно-ориентированных разработках............278 Требования, предъявляемые к тестированию подклассов............279 Возможности совершенствования................................279 Иерархическое инкрементальное тестирование...................281 Организация тестового программного обеспечения.................291 Тестирование абстрактных классов...............................293 Резюме.........................................................296 Упражнения.....................................................297 Глава 8. Тестирование распределенных объектов..................298 Базовые концепции..............................................299 Вычислительные модели..........................................300 Совмещенная модель...........................................300 Параллельная модель..........................................301 Сетевая модель...............................................301 Распределенная модель........................................301 Основные различия..............................................302 Недетерминированность........................................302 Дополнительная инфраструктура................................303 Частичные отказы.............................................303 Тайм-ауты....................................................303 Динамическая природа структуры...............................304 Потоки.........................................................304 Синхронизация................................................304 Тестирование путей в распределенных системах...................305 Модели потоков...............................................309 Тестирование жизненного цикла..................................310 Модель распределения...........................................311 Базовая модель клиент/сервер.................................311 Стандартные модели распределения.............................311 Сравнение и выводы...........................................314 Обобщенная модель с распределенными компонентами...............315 Базовая архитектура..........................................315 Локальные и удаленные интерфейсы.............................317
10 Оглавление Описание распределенных объектов.................................318 Язык описания интерфейсов.....................................318 Традиционные предусловия, постусловия и инварианты............318 Временная логика.................................................319 Временные тестовые модели.....................................322 Оператор Eventually(p)........................................322 Оператор until(p,q)...........................................323 Оператор always(p)............................................323 Условия испытаний................................................325 Тестирование классов..........................................325 Тестирование взаимодействий...................................326 Тестовые случаи..................................................327 Тестирование, отражающее специфику конкретных моделей.........327 Тестирование каждого предположения............................328 Тестирование инфратруктуры....................................332 Тестовые случаи для проверки логических ошибок................333 Максимальный вариант распределенной системы — Internet........334 Web-серверы...................................................336 Тестирование Internet-приложений на протяжении их жизненного цикла...........................................337 Что мы еще не успели сказать?.................................338 Резюме...........................................................338 Упражнения.......................................................339 Глава 9. Тестирование систем.....................................340 Составление плана тестирования системы...........................342 Свойства, подвергающиеся и не подвергающиеся тестированию.....342 Критерии приостановки тестирования и требования возобновления тестирования.........................344 Дополнительные стратегии, используемые при отборе тестовых случаев......................................344 Профиль использования.........................................345 Классификация ODC.............................................345 Случаи использования как источники тестовых случаев..............347 Построение профилей использования.............................347
Оглавление 11 Использование сценариев для построения тестовых случаев......349 Раздел ожидаемых результатов в тестовом случае...............351 Тестирование инкрементальных проектов..........................355 Особенности тестирования существующего программного обеспечения после его обновления.............................356 Тестирование множественных представлений.......................357 Что необходимо подвергнуть тестированию........................358 Тестирование на соответствие функциональным требованиям......358 Проверка качественных системных атрибутов....................358 Тестирование механизма развертывания системы.................360 Тестирование после развертывания системы.....................361 Тестирование взаимодействий окружения........................361 Тестирование системы безопасности............................363 Типы тестирования .............................................364 Тестирование в предельных режимах............................364 Тестирование на протяжении жизненного цикла..................365 Тестирование характеристик производительности ...............366 Тестирование систем различных типов............................368 Реагирующая система..........................................368 Встроенные системы...........................................369 Многоуровневые системы.......................................371 Распределенные системы.......................................372 Измерение тестового покрытия...................................372 Что подлежит покрытию?.......................................372 Когда покрытие измеряется?...................................373 Когда используется покрытие?.................................373 Классификация ODC............................................374 Дополнительные случаи........................................374 Резюме.........................................................376 Упражнения.....................................................376 Глава 10. Компоненты, каркасы и линии продуктов................377 Модели компонентов.............................................378 Распределенные компоненты....................................379 Модель компонента Enterprise JavaBeans.......................380
12 Оглавление Тестирование компонентов и объектов...........................381 Процессы тестирования компонентов.............................382 Процесс тестирования компонентов на стадии приемочных испытаний.383 Тестовые случаи, основанные на интерфейсах....................384 Реальный пример — компонент GameBoard.........................386 Структуры.......................................................388 Основные проблемы.............................................394 Процесс тестирования структур.................................394 Проверка структуры............................................395 Структурирование тестовых случаев с целью поддержки структуры.396 Линии продуктов.................................................397 Тестирование на уровне организационного управления............398 Тестирование на уровне технического управления................398 Тестирование на уровне проектирования программного обеспечения..398 Тестирование в рамках проекта линии продуктов.................399 Дальнейшие перспективы........................................400 Резюме..........................................................400 Упражнения......................................................400 Глава 11. Заключение............................................401 Предложения.....................................................401 Организация и процесс.........................................401 Данные........................................................402 Стандарты.....................................................402 Инфраструктура программного обеспечения.......................404 Технологии....................................................404 Риски.........................................................405 Игра............................................................406 Резюме..........................................................408 Библиография....................................................409 Предметный указатель............................................412
Предисловие Тестирование программного обеспечения является очень важным и в то же время трудоемким видом деятельности. Это книга предназначена для специалистов, которые проводят тестирование программ в процессе их разработки. В центре вни- мания находится объектно-ориентированное и компонентное программное обеспече- ние, однако многие из методов, рассматриваемых в данной книге, можно применять независимо от реализуемой парадигмы разработки. Мы полагаем, что читатели зна- комы с методами тестирования процедурного программного обеспечения, т.е. про- грамм, во время разработки которых соблюдался процедурный принцип и для напи- сания которых использовались языки, подобные С, Ada, Fortran или COBOL. Мы также полагаем, что читатель имеет некоторый опыт разработки программного обес- печения с использованием объектно-ориентированных и компонентных технологий. Основное внимание будет сосредоточено на том, что в первую очередь подлежит тестированию в процессе разработки объектно-ориентированного программного обес- печения, равно как и на описании того, как надлежит выполнять тестирование объек- тно-ориентированных программ в процессе их разработки, а также насколько про- граммные средства тестирования, построенные с помощью новейших технологий, отличаются от процедурно-ориентированных средств тестирования. Что собой представляет тестирование программного обеспечения? Для нас тести- рование — это прежде всего оценка промежуточных продуктов, созданных в процес- се разработки программного обеспечения. Это гораздо более широкое поле деятель- ности, нежели просто проверка некоторых частей или программной системы в целом с целью определения степени ее соотвествия техническим требованиям. В общем случае, тестирование программных продуктов — это доволько сложный процесс, при этом редко бывает так, чтобы разработчики располагали всеми необходимыми для этого средствами. С нашей точки зрения, тестирование — это род деятельности, которая должна проводиться на всем протяжении создания программного продукта, а не только на конечной его стадии, чтобы определить, насколько успешно разработ- чики справились со своей задачей. В силу упомянутого обстоятельства, мы обраща- емся к тестированию разрабатываемых продуктов (моделей) еще до написания пер- вых программных кодов. Мы отнюдь не расчитываем на то, что вы будете применять в своей практике все то, что описано в настоящей книге. Довольно редко случается так, чтобы в наличии были все виды ресурсов, необходимые для всех уровней и видов тестирования, какие хотелось бы выполнить. Мы надеемся, что некоторые из предлагаемых подходов и методов окажутся полезными и что в рамках вашего проекта для их применения отыщется достаточный объем ресурсов. В данной книге мы рассматриваем целый ряд методов тестирования. Все описан- ные здесь методы прошли проверку на практике. Многие из них применялись в самых различных отраслях промышленности при разработке проектов различных масшта- 13
14 Предисловие бов. В главе 3 мы проанализируем влияние некоторых из этих переменных факторов на выбор типов тестирования, выполняемых в обязательном порядке. При описании методов зачастую приводятся один или большее число примеров с целью демонстрации применения того или иного метода. Мы надеемся, что эти при- меры и пояснения упростят применение этих методов при самостоятельной разработке программного обеспечения. Полные исходные и тестовые коды и другие программ- ные ресурсы для примеров можно получить по адресу http://cseng.aw.com/book/ 0.3828.0201325640.00.html, Для максимального увеличения степени полезности книги, мы будем придержи- ваться двух организационных направлений. Физическая компоновка книги отражает обычную последовательность событий, происходящих в процессе разработки проек- та. Например, тестирование модели будет проводиться раньше, чем тестирование компонент или программ. Мы включили также набор вопросов, которые может за- дать лицо, проводящее тестирование, когда оно столкнется со специфическими зада- чами в проекте. Эти FAQ (Frequently asked Questions, Часто задаваемые вопросы) помещены в основной текст книги со специальными пометками. В книге рассматриваются и альтернативные методы и способы адаптации методов к изменению объемов тестирования. Тестирование программного обеспечения, от правильного функционирования которого зависят жизни людей или успех ответствен- ной миссии, естественно, требует больших усилий, чем, скажем, игр для игровых автоматов. В каждой главе имеются разделы резюме, чтобы можно было сделать однозначный выбор. Эта книга является плодом многолетних исследований, преподавательской и кон- сультационной деятельности как в университете, так и в различных компаниях. Мы хотели бы поблагодарить спонсоров проводимых нами исследований, в частности, компании COMSOFT, IBM и AT&T за их неизменную поддержку наших научных исследований. Благодарим наших студентов, тех, кто участвовал в исследовательс- ких работах, и тех, кто в течение долгих часов высиживал в аудиториях и создавал так необходимую нам обратную связь во время создания первых версий текста дан- ной книги. Консультанты, работавшие на компанию Korson-McGregor, ранее изве- стную как Software Architects, сделали много предложений и работали над ранними версиями методов, продолжая удовлетворять запросы клиентов. Служащие многочис- ленных клиентов, обращавшихся за консультациями, помогали нам совершенство- вать методы, вынуждая решать реальные проблемы и тем самым обеспечивая неоце- нимую обратную связь. Особую благодарность выражаем Мелиссе Расс (Melissa I. Russ, в девичестве Мейджор (Major)), которая помогла организовать несколько кон- сультаций и сделала ряд проницательных замечаний, позволивших улучшить матери- ал книги. Но больше всего мы хотели бы поблагодарить наши семьи за то, что они стоичес- ки перенесли наше физическое и ментальное отсуствие, без чего появление этой книги было бы попросту невозможным. Джон Д. Макгрегор (John D. McGregor) Девид А. Сайкс (David A. Sykes)
Глава I Введение Тестирование программного обеспечения всегда было делом нелегким, однако понимание самого процесса тестирования не вызывало особых трудностей. Соответствующая комбинация тестирования программных модулей, проверки взаимодействия и функционирования компонентов системы, системного тести- рования, регрессионного тестирования и приемочных испытаний позволяет по- лучить систему, вполне пригодную к практическому применению. Намерение написать эту книгу возникло, когда мы окончательно убедились в том, что большинство людей убеждено, что тестирование объектно-ориентирован- ного программного обеспечения мало чем отличается от тестирования процедурного программного обеспечения. В то время как многие общие подходы и методы те- стирования являются одними и теми же или, по крайней мере, могут быть поза- имствованы из традиционных методов тестирования и соответствующим обра- зом адаптированы, тем не менее, накопленный нами опыт и проведенные исследования показали, в что некоторых аспектах имеются существенные отли- чия, которые являются источником трудно решаемых проблем. В то же время удачно спроектированное объектно-ориентированное программное обеспечение, как часть инкрементального процесса, предоставляет гораздо большие возможно- сти по его совершенствованию, нежели традиционные процессы тестирования. Такие возможности языков объектно-ориентированного программирования, как наследование и полиморфизм, ставят перед специалистами, выполняющими тестирование, новые и довольно сложные проблемы. Далее мы покажем, как ре- шаются многие из этих проблем. В этой книге будут рассматриваться процессы и методы эффективного тестирования объектно-ориентированного программного обеспечения на всех стадиях его разработки. Наш подход к программному обес- печению тестирования является всесторонним и достаточно полным, и мы наде- емся, что все организации, занимающиеся разработкой программного обеспече- ния, будут ориентироваться именно на предлагаемый подход. В то же время мы понимаем, что ресурсы, необходимые для полноценного тестирования, ограниче- ны, и что существует множество эффективных путей разработки программ, по- этому мы считаем, что целесообразно остановить свой выбор на каком-то одном методе из числа тех, которые представляются в книге. 15
16 Глава 1. Введение Выбор объектно-ориентированных технологий обусловливает не только выбор используемого языка программирования, но и внесение соответствующих кор- рективов в большинство аспектов разработки программного обеспечения. Мы воспользуемся процессом инкрементальной разработки, переориентируем и при- меним новые нотации на стадиях анализа и проектирования и задействуем но- вые возможности языков программирования. Мы надеемся, что благодаря таким изменениям разрабатываемые программы станут более гибкими и удобными в эксплуатации, обретут возможность многократного использования, гибкость и т.д. Мы написали эту книгу прежде всего потому, что изменения в процессе раз- работки программ влекут изменения в способах их тестирования как в организа- ционном, так и в техническом отношении. Приводимые ниже изменения созда- ют предпосылки для совершенствования процесса тестирования: У нас имеется возможность изменить отношение к тестированию. В усло- виях многих программных сред менеджеры и разработчики рассматривают тестирование как неизбежное зло. Тестирование, которое разработчики должны проводить своими силами, прерывают процесс создания про- граммных кодов. Экспертизы, проверки программных кодов, а также на- писание драйверов тестирования программных модулей требуют затрат времени и средств. Процессы тестирования, ответственность за которые возложена на разработчиков, в большинстве случаев мешают программи- рованию. Однако, если мы сможем убедить каждого в том, что тестирова- ние существенно содействует разработке корректно функционирующих программ с самого начала, и что оно фактически может использоваться для оценки степени готовности разрабатываемых программ и в качестве средства контроля за ходом процесса разработки, то сможем создавать еще более качественное программное обеспечение. У нас имеется возможность выбрать подходящий момент для включения тестирования в процесс разработки. Фактически почти все признают, что чем скорее проблема будет обнаружена, тем меньше ресурсов потребуется на ее решение. Тестирование программных модулей и проверка взаимо- действия и функционирования компонентов системы выявляет проблемы, но эти виды отладки обычно начинаются не раньше, чем начнется кодиро- вание программ. Системное тестирование, как правило, выполняется на завершающих стадиях разработки или, по крайней мере, на этапах, пре- дусматриваемых соответствующими планами. Системное тестирование рассматривается как способ проверки, насколь хорошо удается разработчи- кам соблюсти требования технического задания. Разумеется, это непра- вильный подход. Решение касательно того, какой объем тестирования дос- таточен, когда его следует выполнять и кто это должен делать, принимается только в контексте хорошо продуманной стратегии тестиро- вания, которая согласуется с процессом разработки программного обеспе- чения проекта. Мы покажем, как начинать тестирование на более ранних
Глава 1. Введаниа 17 стадиях разработки. Мы покажем также, как может взаимно переплетаться работа по тестированию и разработке программного обеспечения и какой вклад вносит каждая из них в успешные результаты другой. У нас имеется возможность использовать новые технологии для проведе- ния тестирования. Подобно тому, как объектно-ориентированные техноло- гии позволяют повысить эффективность процесса разработки программно- го обеспечения, эти технологи позволяют использовать преимущества проблемно ориентированного программирования для повышения эффек- тивности тестирующих программ. Мы покажем, как можно тестировать модели программно-аналитические модели анализа и проектные модели и как с использованием методов объектно-ориентированного программиро- вания разрабатывать драйверы отладки программных модулей и уменьшить объемы кодирования, необходимые для тестирования программных компо- нентов. Для кого предназначена эта книга? Книга написана для: Программистов, которые уже работают с программным обеспечением тести- рования, но хотели бы больше узнать об объектно-ориентированных про- граммах тестирования Администраторов, которые несут ответственность за разработку программ- ного обеспечения и которые хотели бы знать, как и на каких стадиях раз- работок следует включать тестирование в планы работ Разработчиков, которые несут ответственность за тестирование создавае- мых ими программ и которые должны учитывать проблемы тестирования в процессе анализа, проектирования и написания программных кодов. Имея такой обширный контингент потенциальных читателей, мы изо всех сил стремились найти приемлемый уровень детализации описания проблемно- ориентированной разработки и тестирования — базовых понятий, ассоцииро- ванных с тестированием программного обеспечения, объектно-ориентированным программированием и языком UML (Unified Modeling Language — Унифициро- ванный язык моделирования) для изложения результатов анализа и проектиро- вания. Мы решили провести краткий обзор этой тематики — того, что, по на- шему мнению, должен знать читатель, дабы правильно усвоить все то, что мы хотели сказать. Представляемые нами подходы и методы справедливы для всех объектно-ориентированных программ, а не только для тех, которые написаны на C++ и Java. Мы предлагаем следующий сценарий разработки программного обеспечения, который считаем близким к идеальному: Процесс разработки должен быть инкрементальным, с итерациями, кото- рые возникают на каждом шаге.
18 Глава 1. Вваданиа Модели должны быть реализованы на языке UML. Проектирование программного обеспечения осуществляется в соответ- ствии с современными принципами проектирования, с применением принципов наследования, сокрытия данных, абстракций, слабой связнос- тью и высокой непротиворечивостью. Разумеется, мы понимаем, что большинство организаций разработали для внутреннего использования собственные процессы и системы обозначений. В связи с этим мы сосредоточим свое внимание на принципах и методах. Что представляет собой тестирование программных продуктов и чем оно не является По существу, тестирование программного обеспечения (или кратко «тестиро- вание» в контексте данной книги) представляет собой процесс выявления нали- чия дефектов в программных системах. Дефект может быть привнесен на стадии разработки или сопровождения, в результате чего появляется один или большее число ошибок на сленге «багов» — ошибок, недоразумений, пропусков и даже злого умысла со стороны ряда разработчиков. Тестирование означает попытки обнаружения дефектов. Тестирование не подразумевает работу, связанную с вы- явлением и устранением ошибок. Другими словами, в тестирование не входит отладка или действия по устранению последствий ошибок.' Тестирование имеет большое значение в силу того, что этот процесс суще- ственно содействует тому, чтобы конкретное программное приложение делало именно то, что ожидают от него проектировщики. В некоторых случаях задача тестирования усложняется: теперь цель этого процесса заключается в том, чтобы проектируемое приложение не делало ничего более того, для чего оно предназначено.2 Мы должны здесь признать, что некоторым специалистам, выполняющим тестирование программ- ного обеспечения, в обязанности вменяется отладка этого программного обеспечения. Это тем бо- лее верно при тестировании программных модулей и проверке взаимодействия и функционирова- ния компонентов системы. Тестирование представляет собой процесс выявления отказов из-за ошибок в программах. Отладка — это процесс выявления источников отказов, т.е. ошибок, и вне- сение в программу соответствующих исправлений. В рассматриваемом случае может иметь место взаимное наложение в том смысле, что иногда тестирование структурируется так, чтобы оказаться полезным относительно обнаружения ошибок. Тем не менее, тестирование и отладка — это два различных вида деятельности. Разумеется, это свойство приобретает особое значение в системах, «улучшения» которых угрожают потерями материальных ценностей и жизни людей. В то же время трудно осуществлять тестирова- ние дополнительных функциональных возможностей без анализа программных кодов; только не- многие специалисты по тестированию делают это. Не имея перед собой программных кодов, все, что могут сделать тестировщики. — это предугадать ошибки и улучшения, которые могут последо- вать со стороны разработчика, и подготовить тесты, способствующие их обнаружению. Взять, на- пример, проблему обнаружения «Пасхальных яиц», затаившихся в программном обеспечении. (Чи- тателю. должно быть, известна популярная в Америке детская игра, суть которой заключается в нахождении как можно большего количества пасхальных яиц, предварительно спрятанных предус- мотрительными взрослыми — прим, ред.)
Глава 1. Введение 19 В любом случае тестирование вносит существенный вклад в защиту пользо- вателей от отказов программного обеспечения, которые могут повлечь за собой потери времени, материальных ценностей, заказчиков и даже человеческих жиз- ней. Программное обеспечение — что же это такое? Мы определяем программное обеспечение как совокупность инструкций и данных, необходимых для выпол- нения некоторой задачи на компьютере. Уточним это определение — все пред- ставления этих инструкций и данных. В частности, понятие представления включает в себя не только исходные программные коды и файлы данных, но и модели, построенные в процессе проведения анализа и проектирования. Про- граммное обеспечение может быть и должно подвергаться тестированию во всех его представлениях. Подобно тому как архитекторы и строители могут изучать чертежи нового здания еще перед тем, как будет вынут первый кубометр котло- вана под фундамент, мы можем проводить исследования аналитических и про- ектных моделей программного обеспечения, прежде чем будут написаны первые строки исходного программного кода. Тестирование позволяет добиться того, чтобы готовый программный продукт соответствовал требованиям технического задания, однако тестирование — это еще не обеспечение качества. Некоторые программисты ошибочно отождествляют понятия тестирования и обеспечения качества (quality assurance). Во многих организациях на обеспечение качества обычно возлагают ответственность за раз- работку планов тестирования и за выполнение системного тестирования. Тести- рование представляет собой необходимую, но отнюдь не достаточную часть лю- бого процесса обеспечения качества. Обеспечение качества подразумевает деятельность, ориентированную на предотвращение появления дефектов, равно как и устранение дефектов, которые вкрались в программный продукт. Группа, отвечающая за обеспечение качества проекта, устанавливает стандарты, которым должны следовать проектировщики, чтобы создавать высококачественные про- граммные продукты. Сюда входит подготовка документов, в которых обоснова- ны проектные решения, процессы, направляющие проектную деятельность, и данные, которые представляют эти решения в соответствующих цифрах. Никакое тестирование не решает задачи повышения качества компьютерной программы. Тестирование содействует распознаванию отказов, благодаря чему разработчики могут отыскать ошибки в программах и устранить их. Чем больше- му тестированию мы подвергаем систему, тем больше убеждаемся в ее правиль- ном функционировании. Однако в общем случае тестирование не может гаран- тировать того, что система работает правильно на все 100 процентов. Следовательно, основной вклад тестирования в обеспечение качества заключает- ся в обозначении проблем, которых хотелось бы избежать прежде всего. Эта за- дача для своего решения требует реализации процессов, выходящих за рамки те- стирования. Тестирование содействует повышению качества программного продукта, по- могая выявлять проблемы на ранних стадиях процесса разработки. К счастью,
20 Глава 1. Введение некоторые виды тестирования можно выполнить на стадии, на которой еще не началось собственно написание программных кодов. В этой книге мы даем опи- сание некоторых полезных методов тестирования, однако эти методы требуют тесного сотрудничества персонала, выполняющего тестирование, с разработчика- ми программного обеспечения, а разработчиков — с персоналом, проводящим тестирование (тестировщиками). Какими особенностями обладает тестирование объектно- ориентированного программного обеспечения? Объектно-ориентированные возможности в языках программирования вне всяких сомнений оказывают влияние на некоторые аспекты тестирования. Такие особенности, как наследование классов и интерфейсы, поддерживают полимор- физм, по условиям которого программа манипулирует объектами таким образом, что класс, которому они принадлежат, неизвестен. Специалисты, осуществляю- щие тестирование, должны сделать так, чтобы программы работали независимо от того, что представляет собой класс, к которому принадлежат эти объекты. Языковые средства, которые поддерживают и вынуждают сокрытие данных, спо- собны усложнить тестирование, поскольку некоторые операции время от време- ни должны быть включены в интерфейс класса специально для целей тестирова- ния. С другой стороны, наличие таких средств позволяет улучшить качество тестирующих программ и обеспечить их повторное использование. Не только изменения в языке программирования оказывают очевидное воз- действие на тестирование, но также изменения процесса разработки программ- ного обеспечения и смещение акцентов при анализе и проектировании. У мно- гих операций по тестированию объектно-ориентированного программного обеспечения имеются прототипы в традиционных процессах. Мы все еще при- меняем тестирование программных модулей, хотя понятие модуля теперь изме- нилось. Мы по-прежнему осуществляем проверку взаимодействия и функцио- нирования компонентов системы с тем, чтобы убедиться, что подсистемы взаимодействуют корректно. Мы по-прежнему проводим регрессивное тестиро- вание с целью убедиться, что последний цикл изменений программ не сказался неблагоприятно на возможностях системы, а система способна делать все то, что делала и ранее. Различия между «старыми» и «новыми» методами разработки и тестирования программного обеспечения намного глубже, нежели выбор в качестве базового понятия объекта вместо понятия функции, преобразующей входные данные в выходные. Основное различие заключается в том, что объектно-ориентирован- ное программное обеспечение разрабатывается в виде набора объектов, которые по сути дела моделируют задачу и взаимодействуют между собой в стремлении отыскать ее эффективное решение. В основе такого подхода лежит принцип, со- гласно которому решение задачи может со временем претерпевать изменения, и в то же время структура и компоненты задачи не меняются в той же степени
Глава 1. Введение 21 или столь же часто. Следовательно, программа, структура которой определяется особенностями задачи (и вовсе не непосредственно требуемым решением), обла- дает большей приспосабливаемостью к последующим изменениям. Программист, знакомый с задачей и ее компонентами, способен распознать ее и в программ- ном обеспечении. Благодаря упомянутому обстоятельству он делает программное обеспечение более удобным в сопровождении и эксплуатации. Более того, по- скольку компоненты были получены в соответствии с потребностями задачи, они могут быть повторно использованы при разработке других программных ре- шений таких же или подобных задач, в результате чего повышаются возможнос- ти многократного использования компонентов программного обеспечения. Большая польза такого подхода к проектированию заключается в том, что аналитические модели непосредственно отображаются на проектные модели, а те в свою очередь отображаются на программы. Следовательно, можно начинать те- стирование на стадии анализа и доводить тестирование, проводившееся на ста- дии анализа, до уровня проектного тестирования. В свою очередь, тестирование для проектирования совершенствуется до уровня тестирования на этапе реализа- ции. Это означает, что процесс тестирования может переплетаться с процессом разработки. Несложно отметить три существенных применения преимущества моделей анализа и проектирования с элементами тестирования: 1. Контрольные примеры (тестовые случаи) могут идентифицироваться на ран- них стадиях процесса, даже на стадии определения требований. Раннее тес- тирование позволяет аналитикам и проектировщикам лучше понимать и чет- ко формулировать требования и делать эти требования «поддающимися тестированию». 2. Программные ошибки могут быть обнаружены на ранних стадиях процесса разработки, тем самым позволяя экономить время, деньги и усилия. Широко известно, что чем раньше проблема обнаружена, тем проще и дешевле ее ис- править. 3. Тестовые случаи можно проверить на предмет правильности на ранних ста- диях проекта. Правильность тестовых случаев, особенно системных конт- рольных примеров, всегда представляет собой проблему. Если тестовые слу- чаи установлены заранее и применены к моделям на ранней стадии проекта, то все случаи неправильного понимания требований со стороны тестировщи- ков, могут быть исправлены на ранних этапах. Другими словами, тестирова- ние моделей позволяет добиться того, чтобы тестировщики и разработчики имели согласованное понимание системных требований. Несмотря на то что тестирование моделей приносит большую пользу, очень важно не допустить, чтобы их тестирование стало самоцелью. Тестирование про- грамм все еще остается важной частью процесса тестирования программного обеспечения. Другое различие между традиционными проектами и проектами, при реали- зации которых применяются объектно-ориентированные технологии, касается
22 Глава 1. Введение целей программного обеспечения. Предположим, например, что новой важной целью для многих компаний является производство программных продуктов многократного использования, расширяемых систем и даже объектно-ориентиро- ванных структур, представляющих повторно используемые проекты. Тестирова- ние может (и должно) помочь выявить их неспособность соответствовать этим целям. Традиционные подходы к тестированию и методы упомянутым целям не соответствуют. Обзор принятого подхода к тестированию Наша цель заключается в проведении всеобъемлющего и тщательного тести- рования программного обеспечения в условиях временных и финансовых огра- ничений. Наш подход к тестированию объектно-ориентированного программно- го обеспечения основан на научных исследованиях, а также на опыте, накопленном при сотрудничестве с различными отраслями промышленности, с такими как телекоммуникации и финансы. В рамках предлагаемого нами подхода тестирование не рассматривается как нечто, выполняемое вдогонку за разработкой. Тестирование представляет собой процесс, независимый от процесса разработки, однако тесно с ней связанный. Наш девиз: Тестировать как можно раньше. Тестировать часто. Тестировать в полном объеме. Мы отдаем предпочтение следующему многократно повторяемому процессу: Немного анализа Немного проектных решений Немного программных кодов Тестировать все, что можно. Под формулировкой «тестировать все, что можно» понимается не только то, что можно сделать технически, и то, что можно в условиях ограниченных вре- менных и материальных ресурсов. Очень хорошие результаты показывает тести- рование, выполняемое в режиме многократного повторения. Регулярное тестиро- вание позволяет обнаруживать отказы программного обеспечения на ранних стадиях разработки, благодаря чему удается избежать крупномасштабных переде- лок на последующих итерациях. Системное тестирование и приемочные испыта- ния следуют за последней тестирующей итерацией. Тем не менее, если про- граммную систему можно разрабатывать путем последовательного наращивания ее возможностей, то системное тестирование можно проводить после каждого та- кого наращивания. Какие виды тестирования должны проводиться для объектно-ориентирован- ного программного обеспечения? Тестирование моделей Тестирование классов вместо тестирования программных модулей
Глава 1. Введение 23 Проверка взаимодействия и функционирования компонентов вместо ком- плексных испытаний системы Системное тестирование (и испытания подсистем) Приемочные испытания Проверка развертывания системы (самотестирование) Каждый из перечисленных видов тестирования описывается далее в книге. Предлагаемый нами процесс тестирования определяет вид тестирования для каждого вида деятельности по разработке программного продукта. Мы не думаем, что вы будете или должны применять на практике все то, что описано в данной книге. Редко бывает так, что разработка обеспечена всем не- обходимым для выполнения всех уровней и видов тестирования, которые здесь рассматриваются. Мы надеемся, что будет шанс отыскать среди них некоторое число подходов и методов, применение которых окажется возможным, полезным и допустимым в смысле выделенных ресурсов на реализацию проекта. Далее приводится логическое обоснование девиза «Тестировать как можно раньше. Тестировать часто. Тестировать в полном объеме». Тестировать как можно раньше Вместо того чтобы привлекать тестировщиков в работу на завершающих эта- пах проектирования, следует предоставить им поле деятельности в походящие моменты на стадиях анализа и проектирования. Модели, предполагающие тести- рование на этапах анализа и проектирования, не только способствуют выявле- нию проблем на ранних этапах процесса разработки программного продукта (на которых их устранение сопряжено с меньшими затратами средств и усилий), но и позволяют выяснить, какие требуется затратить усилия на выполнение адек- ватного системного тестирования путем определения того, что должно подвер- гаться тестированию. При частом и раннем тестировании предполагается, что программное обеспе- чение представлено в абстрактной форме или в неполном виде. Тестировать часто Мы твердо верим в то, что итеративный, или инкрементальный, процесс раз- работки, обеспечивающий постепенное наращивание возможностей системы (иногда такой процесс называют итеративным расширением), лучше всего подхо- дит для большинства проектов. Как только будут завершены итерации на стади- ях анализа, проектирования и реализации, программный продукт должен быть подвергнут тестированию. По завершении первых операций расширения, неко- торые виды тестирования принимают форму регрессионного тестирования. Тестировать в полном объеме Полное тестирование каждого аспекта программной системы недостижимо. Ресурсы, выделяемые под тестирование, должны быть направлены туда, где они
24 Глава 1, Ввадвниа дадут максимальный эффект. Мы отдаем предпочтение методам, в основу кото- рых положен анализ рисков, многократное использование контрольных приме- ров и статистическая выборка входных данных для контрольных примеров. Перспектива тестирования Квалифицированные тестировщики — персонал, отвечающий за тестирование программного обеспечения — должны обладать специальными навыками. Во многих отношениях подготовка квалифицированного специалиста по тестирова- нию оказывается намного более сложной, нежели хорошего разработчика, ибо это требует не только хорошего понимания процесса разработки и его продук- тов, но также и способность предвидеть вероятные отказы и ошибки в про- граммном обеспечении. Приведем простой пример. Допустим, разработчик же- лает построить алгоритм отражения некоторого образа от стенок рамки прямоугольной площади экрана компьютера. Тестировщик должен предвидеть возможные ошибки и отказы, которые могли возникнуть по вине разработчиков, и отыскать надежные и эффективные способы обнаружения отказов, которые могут быть вызваны вероятными ошибками в программах. Например, тестиров- щик желает проверить, что произойдет, если этот образ попадет точно в угол прямоугольника, или, скажем, выберется ли он из этого угла без потерь. У спе- циалиста по тестированию очень непростая работа. Специалист по тестированию должен выработать следующий поход к про- граммному продукту — он должен подвергать сомнению каждое решение, касаю- щееся данного программного продукта. Мы рассматриваем такой подход как пер- спективу тестирования. Она является темой обсуждения главы 2. Чтобы работать эффективно, тестировщик должен принять эту перспективу. Методы и процес- сы, описанные в данной книге, разработаны и представлены именно в этой пер- спективе. Структура книги Данная книга состоит из одиннадцати глав. В трех первых главах рассматри- ваются главным образом концепции тестирования и процессы тестирования. В главах с 4 по 10 подробно обсуждаются возможные методы различных видов те- стирования. Глава 11 содержит сводку результатов. Каждая глава также заверша- ется сводкой результатов (резюме) и набором упражнений. Настоятельно реко- мендуем ознакомиться со всеми упражнениями и выполнить те из них, которые вызывают интерес или имеют отношение к выполняемым вами обязанностям те- стировщика. Для большинства упражнений правильных ответов не существует, хотя для большинства из них одни ответы оказываются лучше других. Мы наде- емся, что эти упражнения принесут несомненную пользу и помогут применять наши методы к вашим проектам. Глава 1 (текущая) является, по сути дела, введением. Здесь приводится обзор концепций тестирования, краткое описание того, чем тестирование объекно-ориентированного программного обеспечения отличается от тестиро-
Глава 1. Ввадвнив 25 вания других видов программного обеспечения, а также краткий анализ предлагаемого подхода. В главе 2 рассматривается перспектива тестирования. Эта перспектива при- нимается для того, чтобы обращаться к различным аспектам процесса тестирова- ния и осуществлять проверку различных продуктов процесса разработки, а так- же исследовать базовые концепции объектной ориентации. В главе 3 дается описание процесса тестирования и то, как он соотносится с процессом разработки. Мы рассматриваем тестирование как процесс, изолиро- ванный от процесса разработки, поскольку в некотором смысле их цели взаимно исключают друг друга. Тем не менее, оба эти процесса довольно-таки тесно пе- реплетаются. В главе 4 показано, как следует тестировать модели. Ключевым условием ус- пешной разработки является тестирование на ранних стадиях разработки и час- тое тестирование. Мы отдаем предпочтение моделям анализа и проектирования с тестированием, поскольку они являются представлениями программного обес- печения. Поскольку такое тестирование содействует решению общей задачи, а именно, разработке проекта, навыки, полученные в процессе тестирования моде- лей, могут быть применены, усовершенствованы и расширены с целью тестиро- вания программных кодов в течение этапа разработки. В главе 5 рассматривается тестирование классов с относительно простыми интерфейсами и реализациями. Главное внимание при этом уделяется базовым элементам тестируемых классов. Тестирование классов в контексте объектной ориентации в первом приближении соответствует тестированию программных модулей в обычном контексте. Основной упор в главе делается на том, какие элементы класса подлежат тестированию и как это делается. Помимо прочего, будут предложены некоторые методы реализации тестовых драйверов. В главе 6 разработанные в главе 5 методы расширяются с целью тестирования классов, которые по техническим условиям и/или по условиям реализации тре- буют взаимодействия с другими классами и/или объектами. (Это в некоторой степени соответствует традиционным комплексным испытаниям системы в объектно-ориентированном контексте.) Тестирование таких классов представляет собой нетривиальную задачу, особенно при наличии динамических связей. В этой главе мы предлагаем методы работы с большим количеством тестовых слу- чаев, которые могут создаваться специально для упомянутых целей. В главе 7 рассматриваются различные способы тестирования классов в рамках иерархии наследования. Основная задача предлагаемых методов заключается в обеспечении повторного использования программного кода тестовых драйверов, а также повторного использования тестовых случаев в максимально возможной степени. Мы предложим алгоритм определения минимальных объемов тестиро- вания, необходимых для тестирования подклассов, которые уже подвергались проверке. Кроме того, будет предоставлено описание методов тестирования аб- страктных классов.
26 Глава 1. Введение В главе 8 обсуждаются вопросы параллелизма тестирования. Благодаря при- менению потоков и/или распределенных вычислений, принцип параллелизма реализуется во все большем числе систем. В главе 9 обсуждается тестирование систем. Тестирование некоторой системы, разработанной с использованием объектно-ориентированных языков программи- рования, во многом аналогично тестированию систем, разработанных в соответ- ствии с любыми другими парадигмами, поскольку тестирование системы выпол- няется на базе технического задания, а не реализации. Тем не менее, будут предложены некоторые рекомендации и подходы на основе соответствия. В главе 10 обсуждаются различные вопросы, касающиеся тестовых компонен- тов, структур и линеек программных продуктов. В главе 11 приводится краткий обзор главных тем предыдущих глав и делает- ся попытка ответить на вопрос: «Куда же следовать дальше?» Соглашения, принятые в данной книге Технические термины, впервые встречающиеся в книге, выделяются полужир- ным шрифтом. Исходные программные коды примеров представляются моноширинным шриф- том. Этот шрифт используется также и для представления фрагментов исход- ных кодов, которые встречаются по тексту. Большая часть программных кодов, приводимых в книге, реализована на языке C++. Однако предлагаемые нами методы не ограничиваются языком C++. C++ выбран по той причине, что он получил широкое распространение и в ка- кой-то степени сам служит источником большей части проблем, с которыми сталкивается тестировщики. Время от времени мы будем давать рекомендации, касающихся особенностей тестирования в среде Java. Имена классов выделяются полужирным шрифтом. Временами придется сталкиваться с текстом, помещенным в рамку. Назначе- ние таких рамок состоит в том, чтобы добавить в изложение некоторые допол- нительные подробности или вспомогательную информацию, касающуюся пред- мета обсуждения. Существуют различные виды упомянутых информационных блоков, и ниже приводится описание каждого из них. СОВЕТ Совет представляет собой рекомендацию, которая дается с тем, чтобы сделать тестирование более простым и эффективным. Иногда совет касается тестирова- ния программ, неписанных на C++ или Java. В некоторых случаях совет представ- ляет собой описание некоторого общего метода, который, как мы полагаем, будет полезен для читателя.
Глава 1. Введение 27 ВОПРОС Возникают ли у вас некоторые вопросы неоднократно? Да, возникают. В рамках данного типа мы даем ответы на часто возникающие вопросы. СОПУТСТВУЮЩАЯ ТЕМАТИКА Иногда некоторое понятие обсуждается с большей степенью детализации в сносках наподобие этой. Время от времени мы будем пользоваться такими сносками для обсуждения некоторых вещей, не вписывающихся в основную тематику, но тем не менее, имеющих отношение к затрагиваемым вопросам, о которых, по нашему мне- нию, у вас должно складываться собственное представление. Переходящий пример - игра Brides На протяжении всей книги будет использоваться один пример как иллюстра- ция различных подходов и методов тестирования. Это позволит сосредоточить основное внимание на методах тестирования и не распыляться понапрасну на множестве мелких примеров. В этом разделе читатель будет ознакомлен с инте- рактивной компьютерной игрой Brides (кирпичики). Эта игра очень похожа на игру Breakout, одну из первых коммерческих видеоигр, получившей широкое распространение среди пользователей компьютеров Apple II. Этот пример берет начало из курса, который был разработан с целью обуче- ния концепциям объектно-ориентированного проектирования и программирова- ния на языке C++. Мы стремились найти такое приложение, структура которого была бы достаточно проста для понимания и которое можно было бы разрабо- тать за неделю обучения, и в то же время чтобы оно допускало дальнейшее рас- ширение и было интересно программистам, подготовка которых позволяет им работать в различных прикладных областях и средах. Цель, которую мы пресле- дуем как в нашем курсе, так и в данной книге, используя такой пример, заклю- чается единственно в том, чтобы он мог служить иллюстрацией понятий, ис- пользуемых при проектировании и тестировании объектно-ориентированных программ. Базовые компоненты игры «Кирпичики» «Кирпичики» — это аркадная игра (игра для компьютерных автоматов), ис- ходная конфигурация которой показана на рис. 1.1. Игровое поле представляет собой прямоугольник, ограниченный двумя стенами, полом и потолком. В игро- вом поле находится также стопка кирпичей, которую будем называть «кучей кирпичей». Цель играющего заключается в том, чтобы выбить все кирпичи из кучи, попадая в каждый из них шайбой, которая, в свою очередь, приводится в движение при помощи лопатки, управляемой игроком. Когда шайба попадает в кирпич, тот разрушается. Шайба отражается от стен, потолка, от кирпичей (ко-
28 Глава 1. Введение торые при этом разбиваются) и лопатки. Начиная игру, игрок получает в свое распоряжение три шайбы, посредством которых он разрушает кирпичи. Игрок выигрывает, когда все кирпичи разрушены. Игрок проигрывает, если весь запас будет израсходован, прежде чем все кирпичи будут разрушены. В начале игры шайба, помещенная в центр игрового поля, начинает двигаться вниз. Игрок управляет лопаткой с помощью мышки, подключенной к компьюте- ру. Игрок должен перемещать лопатку так, чтобы шайба отражалась от нее, а не «падала на пол». Когда шайба ударяет в лопатку, она отражается от нее и уст- ремляется вверх. Всякий раз, когда шайба попадает в кирпич, последний разру- шается. Если шайба проходит мимо лопатки и ударяет в пол, она выходит из игры и в игру вступает одна из оставшихся шайб. Когда все шайбы выходят из игры, игра завершается, а игрок проигрывает. Физические основы игры Во время своего движения по игровому полю шайба натыкается на различные компоненты игрового поля. При этом они взаимодействуют между собой следу- ющим образом (см. рис. 1.2, 1.3 и 1.4). Потолок и стены. Шайба отражается от потолка и стен в соответствии с зако- нами физики, при этом трение и действие сил тяжести игнорируется — другими словами, угол падения равен углу отражения. Пол. Пол поглощает шайбу. Шайба, ударяющаяся в пол, от него не отражает- ся, а просто выходит из игры. Кирпичи. Шайба отражается от кирпича таким образом, что угол ее падения равен углу отражения. При попадании шайбы в кирпич, последний разрушается. Обратите внимание на такую особенность — шайба может ударять в кирпич как сверху, так и снизу. Каждый кирпич обладает достаточной толщиной, чтобы шайба могла попасть в него сбоку. Для простоты можно считать, что кирпичи не имеют толщины. Следовательно, меняется только вертикальная составляющая направления движения шайбы, когда она ударяет в кирпич. Рисунок 1.1. Начальная позиция игры Brides («Кирпичики»)
Глава 1. Введение 29 Рисунок 1.2. Взаимодействие между шайбой и границами игрового поля Рисунок 1.3. Взаимодействие шайбы с кирпичами Рисунок 1.4. Взаимодействие шайбы с лопаткой
30 Глааа 1. Введение Лопатка. Игрок использует лопатку, чтобы управлять направлением движе- ния шайбы. Шайба отскакивает от лопатки, после чего направление ее движения определяется двумя факторами: направлением движения шайбы и той частью лопатки, о которую она ударяет. Разделите лопатку на три равных части и назо- вите ближней третью левую третью часть лопатки, если шайба попадает в нее при движении слева, и правую третью часть лопатки, если шайба попадает в нее, двигаясь справа. Дальняя треть определяется аналогично, а серединой будет оставшаяся треть. Отражение шайбы подчиняется следующим правилам: Если шайба попадает в ближнюю треть лопатки, то она отражается в точ- но том направлении, в каком она пришла. Если шайба попала в среднюю треть лопатки, то ее угол отражения не- много круче угла падения. Движение шайбы несколько ограничено по на- правлению в том смысле, что оно никогда не может быть строго верти- кальным. Если шайба попадает в дальнюю треть лопатки, то угол ее отражения не- сколько меньше угла падения. Движение шайбы несколько ограничено по направлению в том смысле, что оно никогда не может быть строго гори- зонтальным. Шайба. В начале игры игроку выделяется ограниченный запас шайб, но в игре постоянно присутствует только одна шайба. Как только шайба коснется пола, в игру включается следующая шайба (естественно, если таковая имеется). Новая шайба сохраняет направление и скорость предыдущей и двигается в соот- ветствии с автоматическим таймером. Столкновения могут изменить направле- ние движения шайбы, но не ее скорость. Среда, в которой реализована игра Первая реализация игры «Кирпичики» является приложением для среды Microsoft Windows и ведет себя следующим образом: Игра начинается в момент запуска соответствующей программы. Игрок может «выйти» из игры в любой момент до того, как он выиграет или проиграет. Игрок может объявить в игре «паузу» в любой момент до окончания игры. Игрок может «возобновить» игру после паузы. Если игрок выигрывает, то на экран выводится поздравление. Аналогично, в случае проигрыша на экран выводится сообщение, в котором выражается сочувствие проигравшему. Упражнения 1-1 Рассмотрим приложение, которое будет использоваться для составления расписания работы конференц-зала некоторого учреждения. Это приложение обладает графи- ческим пользовательским интерфейсом, который позволяет задавать дату, время
Глава 1. Введение 31 суток и продолжительность игры (в виде пятнадцатиминутных приращений). После этого оно воспроизводит на экране список конференц-залов, доступных в указан- ное время, и позволяет резервировать требуемое помещение. Приложение позволя- ет отменять заказ. Проект разрабатывается по принципу поэтапного наращивания, т.е. путем расширения функциональных возможностей. Рассмотрим следующих два плана. Какой из них имеет большие шансы на успех? Какие виды тестирования должны быть выполнены после каждого наращивания возможностей системы? План А План В Приращение 1: Разработать пользовательский интерфейс Приращение 2: Разработать подсистему хранения данных Приращение 3: Разработать прикладную подсистему (обработки заявок на аренду помещения) Приращение 1: Разработать программные средства ввода значений даты, времени суток и продолжительности аренды, а также выдачи информации о наличии доступных помещений Приращение 2: Разработать программное средство резервирования помещений Приращение 3: Разработать программное средство отмены аренды помещений 1-2 Составить список средств, представленных в языке (языках) объектно-ориентиро- ванного программирования, используемых в вашей компании, у которых нет ана- логов в языках, применяемых ранее. Рядом с каждым таким средством в сжатой форме укажите, как можно воспользоваться тестовым программным обеспечением, ориентированным на использование разработчиком конкретного языка программи- рования. 1-3 Если вы в настоящее время работаете над каким-либо проектом, введите обозначе- ние приращений или основных этапов разработки. Некоторые из них могут быть неформальными. Подумайте над тем, какие виды тестирования можно реализовать во время каждого приращения, и что вы должны проверить по завершении каждого приращения.
Перспектива тестирования ► Вы хотите знать, какую роль играет тестирование? Обратитесь к разделу «Перспектива тестирования». ► У вас нет четкого понимания концепций объекта? Обратитесь к разделу «Понятия объектно-ориентированного программирования». ► Вы ощущаете потребность в моделях языка UML? Обратитесь к разделу «Продукты разработки». Перспектива тестирования Перспектива тестирования есть способ восприятия любого разрабатываемого продукта и оценки его пригодности. Лицо, осуществляющее проверку продуктов с позиций этой перспективы, выполняет тщательное исследование программного обеспечения и всех его представлений с целью обнаружения ошибок. Направле- ние, в котором производится поиск ошибок, определяется как результат систе- матического анализа и интуитивного подхода. Именно перспектива делает реви- зии и проверки такими же действенными, как и контрольные прогоны программы. Ревизия практически никогда не позволяет найти то, чего не хватает — т.е. цель ревизии заключается в обосновании того, что уже существу- ет; она не предназначена для систематических просмотров с целью установки, что все, что должно входить в программное обеспечение, в него включено. Перс- пектива тестирования требует, чтобы каждый блок программного обеспечения доказывал, что он не только выполняет то, что от него требуется по специфика- ции (техническому заданию), но и что он выполняет только то, что должен вы- полнять в соответствии с требованиями спецификации, и ничего более. Таким образом, программный продукт подвергается тестированию, чтобы определить, способен ли он делать то, что от него ожидают. Он также подвергается тестиро- ванию с целью убедиться, что он не делает ничего из того, что и не должен де- лать. 32
Глава 2. Перспектива тестирования 33 ПРОВЕРКИ, РЕВИЗИИ И КОНТРОЛЬНЫЕ ПРОГОНЫ Тестироеение программного обеспечения обычно выполняется кек некоторая сово- купность проверок, ревизий и контрольных прогонов. Все эти действия предназна- чены для выявления отказов. Проверке — это исследование программного обеспечения в соответствии с конт- рольным списком неиболее часто возникающих проблем. Большея честь позиций в этом списке обусловлене семантикой языке прогреммировения и/или соглашения- ми, регламентирующими процесс прогреммировения, непример, соглашением, требу- ющим, чтобы кеждая переменная программы должна быть инициирована некоторым значением, прежде чем оне будет использоване, или чтобы указатели и ссылки получвли приемлемые знечения, прежде чем их можно будет использовать. Совре- менные компиляторы языков объектно-ориентированного прогреммирования могут обнеружиееть многие из проблем, обозначенных в большинстве текого рода прове- рочных списках. Ревизия предстаеляет собой исследование программного обеспечения с целью об- наружения ошибок и дефектов, часто даже до того, как это прогреммное обеспе- чение будет запущено в реботу. Ревизии выполняются в контексте разрабатываемой системы и ориентированы на более глубокое исследовение прогреммного обеспече- ния, нежели проверки. Ревизия призвене исследоветь содержание каждой чести программы и определить, несколько оне соответствует некоторым или всем требо- ваниям приложения. Ревизия преднезначенв для выявления твких ошибок, кек не- способность выполнять то или иное требование спецификации или ее неправильное понимание, а также ошибок в программной логике. В рамкех некоторых видов ревизии производится проверка более мелких деталей, например, правильно ли выбрены имена переменных или обледеют ли используемые алгоритмы необходимой эффективностью. Контрольный прогон предстевляет собой тестирование программного обеспечения в контексте выполнения программы. Осуществляя прогон программы, тестировщик стремится определить, способна ли программа вести себя адекватно; он подает на ее вход различные неборы данных и проверяет, несколько правильны результаты не выходе. В задечу тестировщиков входит отбор соответствующих наборов входных данных, определение, какие выходные данные правильны, и поиск средств наблюде- ния за выходными данными. Тестирование методом контрольного прогона программ (по сравнению с проверками и ревизиями] составляет основное содержание настоящей книги, при этом мы рас- ширяем метод прогоне с тем, чтобы он включел в себя не только тестируемые программы, но и специальный вид ревизии, в рамках которой практикуется симво- лическое выполнение неисполняемых представлений системы. Вспомните данное нами вышв определение программное обеспечения как программного кода и всех его предстевлений. Перспектива тестирования может быть принята либо тем же программистом, который разрабатывал тестируемый программный продукт, либо другим специа- листом, который может обладать независимой точкой зрения как на специфика- цию, так и на сам программный продукт. Как специалист, в чьи обязанности входит тестирование специальных рабочих продуктов, так и любой программист, принимающий участие в разработке проекта на той или иной ее стадии, должны принимать перспективу тестирования. Любого, кто принимает в качестве руко- водящего принципа сформулированную выше перспективу тестирования, мы
34 Глава 2. Пврспектива тестирования будем называть тестировщиком. Разработчик, подвергающий тестированию соб- ственную работу, является таким же тестировщиком, как и специалист, который руководствуется перспективой тестирования во всех своих действиях. Перспектива тестирования должна быть Скептической: подвергать сомнению качество и требовать его подтвержде- ния. Объективной: не отдавать предпочтения никаким предположениям. Тщательной: не упускать из виду ни одного важного вопроса. Систематической: поиски дефектов должны быть воспроизводимыми. В данной главе мы обсудим различные аспекты объектно-ориентированной технологии, руководствуясь сформулированной выше перспективой тестирова- ния. Во-первых, будет выполнена ревизия концепций объектно-ориентирован- ного программирования. Какие особенности этих концепций оказывают влияние на тестирование программного обеспечения, которое было разработано с их ис- пользованием? Мы также сделаем ряд предположений относительно правильного использования объектно-ориентированных технологий. Затем будут рассмотрены некоторые продукты процесса разработки и обсуждены причины возможных от- казов в программном обеспечении, которое они представляют. Понятия объектно-ориентированного программирования Объектно-ориентированное программирование концентрируется вокруг шести основных понятий: 1. объект 2. сообщение 3. интерфейс 4. класс 5. наследование 6. полиморфизм По-видимому, программисты вкладывают различный смысл в эти понятия, большая часть которых имеет право на существование. Мы определяем некото- рые из этих понятий, возможно, несколько строже, чем это принято, поскольку строгость облегчает тестирование понятий и устраняет возможные недоразуме- ния при определении того, что нужно тестировать. Например, в то время как для большинства программистов различие между операциями и методами (или функциями-членами) несущественна, эти различия имеют важное значения для тестировщиков, поскольку подход к тестированию операции, которая является частью спецификации класса, и способом манипулирования объектом, несколько отличается от тестирования метода, представляющего собой фрагмент программ- ных кодов, которые вместе реализуют операцию. Подобное различие помогает проводить отделить проблемы, связанные с испытаниями на базе спецификации от проблем, связанных с испытаниями на базе реализации.
Главе 2. Перспектива твстироввния 35 Мы подвергнем ревизии каждое базовое понятие объектно-ориентированного программирования и сделаем ряд замечаний в этом плане с позиций перспекти- вы тестирования. Поскольку известно, что объектно-ориентированные языки программирования поддерживают многочисленные объектно-ориентированные программные модели, будем пользоваться понятиями в формулировках, приня- тых в таких языках, как C++ и Java. Конкретные различия в языках обусловли- вают некоторые различия типов возможных ошибок и, соответственно, различия в необходимых видах тестирования. Мы попытаемся обратить внимание читате- ля на такого рода различия на протяжении всей книги. Объекты Объект — это операционная категория, инкапсулирующая как конкретные значения данных, так и программные коды, которые манипулируют этими зна- чениями. Например, информация о конкретном банковском счете и операции, необходимая для манипулирования этими данными, образует объект. Объекты суть базовые вычислительные категории в объектно-ориентированных програм- мах, которые мы рассматриваем как совокупность объектов, которые вступают во взаимодействие между собой с целью решить конкретную задачу. В процессе выполнения программы в результате этого взаимодействия объекты создаются, претерпевают изменения, к ним осуществляется доступ, они уничтожаются. В контексте хорошо спроектированного объектно-ориентированного проекта объект в программе является представлением некоторой специальной категории в самой задаче или ее решении. В рамках такой программы объекты вступают в различные отношения, которые отражают взаимоотношения их аналогов в про- блемной области. В контексте игры «Кирпичики» мы различаем довольно боль- шое число объектов, в том числе лопатка, шайбы, куча кирпичей, когда в ней имеются кирпичи, игровое поле, границы игрового поля (стены, потолок и пол) и даже самого игрока. Объект шайба инкапсулирует множество атрибутов, вклю- чая размер, форму, местонахождение в игровом поле (в том случае, когда она вступила в игру) и текущую скорость перемещения. Он также поддерживает операции перемещения и удаления шайбы после того, как шайба ударяется в пол. Мы полагаем, что в программе, которая реализует игру «Кирпичики», име- ются объекты для каждой шайбы; например, в начальной стадии игры мы ви- дим, что одна шайба находится в игре, а другие остаются в запасе. В процессе игры объект шайба взаимодействует с другими объектами — с игровым полем, лопаткой и кучей кирпичей — подчиняясь физическим законам, положенным в основу игры, о которых шла речь при описании игры. Объекты подлежат тестированию в процессе разработки программного обеспе- чения. Основными задачами тестирования объектно-ориентированного про- граммного обеспечения являются проверки на соответствие поведения объекта его спецификации и правильности его взаимодействия с другими объектами во время выполнения программы. 2*
36 Глава 2. Перспектива тестирования □ПИСАТЕЛЬНАЯ И ОПЕРАЦИОННАЯ СЕМАНТИКА ОБЪЕКТОВ Сради наших клиентов и студентов имеет масто определенная степень неоднознач- ного толкования различий между теми аспектами объактно-ориентироаанного про- граммирования, которые имеют отношение к опредалению классов и интерфейсов и касаются использования объектов. Мы будем называть их, соответственно, опи- сательными и операционными аспектами объектно-ориентироаанного программи- рования. Описательный аспект. Определание класса на первый взгляд может показаться признаком, обозначающим конец диапазона определений, однако в некоторых язы- ках программирования, таких как CLOS, структура и содержание такого определения описываются метаклассом. Подход, использующий метаклассы, теоретически может расширить диапазон определений до бесконечности, поскольку можно построить метакласс для любого класса, включая и сами метаклассы. В некоторых языках программирования, таких как Java, динамический диапазон определений означает возможность определять классы а процессе выполнения программы. Операционный аспект. Операционный конец указанной выше диапазона определений соответствует концепции, согласно которой объект является основой действий, про- исходящих в системе. Объект обладает механизмом, необходимым для приема со- общений, диспетчеризации методов и возврата результатов. Он также связывает атрибуты экземпляров с методами. Подобного рода информация может находиться на статическом конца такого диапазона (как это имеет место а C++] или может быть болае динамичной, как в объектах CLOS, которые содержат произвольные сегменты.
Глава 2. Перспектива тестирования 37 Одной из характеристик объекта может быть его жизненный цикл. Жизнен- ный цикл конкретного объекта начинается в момент его создания, он проходит через последовательности состояний и завершается уничтожением объекта. С точки зрения перспективы тестирования в отношении объектов примем во внимание следующие замечания: Объект инкапсулирует данные и программные коды. Это обстоятельство существенно упрощает полное определение объекта, благодаря чему объект легко идентифицируется, его нетрудно обойти в системе, им легко мани- пулировать. Объект скрывает информацию. Это подчас приводит к тому, что внесен- ные в объект изменения трудно заметить, в силу чего становится труднее выполнять анализ результатов тестирования. Объект может перейти в состояние, в котором затем удерживается на про- тяжении всего своего жизненного цикла. Это состояние может далее стать неадекватным и служить причиной некорректного поведения. Объект обладает жизненным циклом. Такой объект может подвергнуться анализу в любой момент на протяжении жизненного цикла с целью опре- делить, находится ли он состоянии, соответствующем жизненному циклу. Запоздалое построение объекта или его преждевременное уничтожение до- вольно часто являются источниками отказов программного обеспечения. В главе 6 описывается целый ряд различных методов тестирования взаимо- действий объектов. К некоторым другим аспектам тестирования мы обратимся в главах 5 и 7. Сообщение Сообщение1 представляет собой запрос на выполнение определенной опера- ции конкретным объектом. Кроме имени операции в сообщении может быть указаны значения — фактические параметры, — которые будут использоваться при выполнении операции. Получатель может вернуть значения отправителю. Объектно-ориентированная программа представляет собой совокупность объектов, которые взаимодействуют между собой с целью решения конкретной задачи. Это взаимодействие достигается путем обмена объектов сообщениями. Мы называем объект, инициирующий сообщение, отправителем, а объект, полу- чающий сообщение, получателем. В результате посылки некоторых сообщений появляются ответы некоторой особой формы, такие как возвращаемые значения * В терминологии C++ под сообщением понимается вызов функции-члена. Программисты, работаю- щие в среде Java и в среде Smalltalk, рассматривают сообщения как вызовы методов. Мы восполь- зуемся этими терминами при обсуждении программных кодов на Java и C++, однако мы будем пользоваться более общим термином сообщение в обсуждениях, не затрагивающих языковых про- блем. Имейте в виду, что между вызовами функций-членов и самими фукнциями-членами большая разница. Вызов метода и метод — это совсем не одно и тоже.
Глава 2. Перспектива тестирования или исключительные ситуации (или просто исключения), которые пересылаются от получателя к отправителю. Выполнение объектно-ориентированной программы обычно начинается с того, что генерируются экземпляры некоторых объектов, после чего передается сообщение одному из этих объектов. Получатель сообщения, в свою очередь, посылает сообщения другим объектам, возможно даже самому себе, с целью вы- полнения некоторых вычислений. В некоторых управляемых событиями средах сами среды периодически отправляют сообщения и ждут отклика на внешние события, примером которых могут служить щелчки кнопками мыши и нажатия на клавиши клавиатуры. С позиций перспективы тестирования примем во внимание следующие заме- чания относительно сообщений: У каждого сообщения есть свой отправитель. Отправитель определяет мо- мент передачи сообщения и может в этом плане принять неправильное решение. У каждого сообщения имеется получатель. Получатель может быть не гото- вым к приему некоторого конкретного сообщения, поступившего в его ад- рес. Получатель может оказаться не в состоянии выполнить правильное действие в ситуации, когда он получает неожиданное сообщение. Сообщение может содержать фактические параметры. Эти параметры будут использованы и/или скорректированы получателем во время обработки полученного сообщения. Объекты, переданные в качестве параметров, дол- жны быть в адекватном состоянии до его обработки и после, при этом они должны обеспечивать выполнение интерфейсов, на которые рассчитывает получатель. Все эти проблемы являются центральными для проверки взаимодействия и функционирования компонентов и рассматриваются в главе 6. Интерфейс Интерфейс представляет собой совокупность объявлений, регламентирующих поведение. Отдельные поведенческие шаблоны объединяются в единую группу, поскольку они определяют действия, относящиеся к одному конкретному поня- тию. Например, некоторый интерфейс может описывать набор поведений, ха- рактерных для движущихся объектов (см. рис. 2.1). Интерфейс является строительным блоком для спецификаций. Специфика- ция определяет совокупный набор общедоступных поведений класса (его опре- деление дается ниже). Java содержит синтаксическую конструкцию interface, ко- торая обеспечивает эту возможность и не допускает объявления каких-либо переменных состояния. Вы можете получить тот же результат в C++, объявив абстрактный базовый класс, содержащий только общедоступные, чистые вирту- альные методы.
Глава 2. Перспектиаа тестирования 39 public interface Movable{ Movablepiece(PlayingField fieldPtr, Point initialLocation, Velocity initialvelocity) Point getPosition(); Velocity getVelocity() ; void setvelocity(Velocity newVelocity); void tick(); void move(); void collideWith(ArcadeGamePiece apiece,Point aPoint){} void collideWithPaddle(Paddle aPaddle,Point aPoint); void collideWithPuck(Puck aPuck,Point aPoint); void reverseY(); void reverseX(); } Pl/lcyHOK 2.1. Объявление в среде Java интерфейса Movable С точки зрения перспективы тестирования в отношении интерфейсов при- мем во внимание следующие замечания: Интерфейс инкапсулирует спецификации операций. Из этих специфика- ций поэтапно строятся спецификации более крупных конструкций, таких как классы. Если интерфейс содержит поведения, которые не связаны от- ношениями с другими поведениями, то реализация такого интерфейса приведет к появлению ущербных проектов. Каждый интерфейс вступает в отношения с другими интерфейсами и классами. Интерфейс может быть описан как тип параметра для поведения с целью обеспечить программисту, реализующему проектный замысел, возможность передавать этот интерфейс как параметр. Мы будем употреблять термин интерфейс для описания наборов объявлений поведений независимо от того, будет ли использоваться синтаксис интерфейса или нет. Класс Класс есть совокупность объектов, которые совместно используют общую концептуальную базу. Многие программисты рассматривают класс как шаблон («нож для пирога») для построения объектов. Мы можем согласиться с тем, что это сравнение достаточно точно характеризует роль классов в написании объект- но-ориентированных программ, тем не менее, мы предпочитаем воспринимать класс как некоторый набор. В этом случае определение класса есть определение того, что представляют собой элементы этого набора. По крайней мере, такой подход лучше определения, согласно которому класс есть тип, ибо некоторые объектно-ориентированные языки вообще не используют понятия типа. Объекты образуют базовые элементы для выполнения объектно-ориентирован- ных программ, в то время как классы являются базовыми элементами для опре- деления объектно-ориентированных программ. Любое понятие, прежде чем быть
40 Глава 2. Перспактива тестирования представленным в программе, должно быть реализовано сначала путем определе- ния соответствующего класса, после чего должны быть построены объекты, опре- деляемые этим классом. Процесс построения объектов называется процессом со- здания экземпляров, а результат этого процесса называется экземпляром. Мы будем попеременно употреблять как понятие экземпляр, так и понятие объект. Концептуальная база, общая для всех объектов класса, может быть выражена термином, состоящим из двух частей: Спецификацией класса, представляющей собой объявление того, что может делать каждый объект класса. Реализацией класса, представляющей собой определение, как каждый из объектов делает то, что они могут делать. Рассмотрим определение в среде C++ класса Pucksuply игры «Кирпичики». На рис. 2.2 показан заголовочный файл C++, а на рис. 2.3 — исходный файл упомянутого класса. Структура, состоящая из заголовочного файла и одного или большего числа исходных файлов, широко используется для построения опреде- лений классов.2 В контексте C++ заголовочный файл содержит спецификацию класса в виде набора операций, объявленных в общедоступной области объявле- ния класса. К сожалению, атрибуты приватных (и защищенных) данных также должны быть объявлены в файле заголовков, хоть они и являются частью реали- зации. fifndef PUCKSUPPLY_H #define PUCKSUPPLY_H class PuckSupply { public: PuckSupply () ; -PuckSupply () ; Puck* get(); int size() const; private; static const int N = 3; int _count; Puck* _store [N] ; } ; #endif Рисунок 2.2. Заголовочный файл C++ для класса Pucksuply. 2 Java требует, чтобы спецификация и реализация физически находились в одном файле. Тем не ме- нее, и в этом случае существует логическое разграничение того, что объект делает и того, как он это делает.
Глава 2. Перспектива тестирования 41 #include "PuckSupply.h" PuckSupply:: PuckSupply () _count(N) { int i ; for ( i = 0 ; i < N ; ++i ) { _store[i] = new Puck; } ; } PuckSupply:: “PuckSupply () ( int i ; for ( i = 0 ; i < _count ; ++i ) ( delete _store[i]; } ; } Puck* PuckSupply:: get () { return ( _count > 0 ? _store [—_count] 0 ) ; } int PuckSupply::size() const { return _count; Рисунок 2.2. Исходный файл C++ для класса Pucksuply. Чтобы иметь возможность создавать или манипулировать объектом из другого класса, соответствующему программному сегменту достаточно получить доступ к спецификации класса этого объекта. В среде C++ это обычно достигается за счет использования директивы include (включить) с указанием заголовочного файла класса соответствующего объекта: #include "PuckSuply.h" Разумеется, благодаря этой команде обеспечивается доступ к любой инфор- мации, необходимой для компиляции программы, но в то же время она предос- тавляет больше информации, чем нужно для организации взаимодействия клас- сов. КЛАССЫ КАК ОБЪЕКТЫ Языки обьектно-ориентированного программирования, обычно поддерживают, прямо или косвенно, в рамках соответствующего языка, идею, согласно которой класс сам по себе является объектом, и в силу этого обстоятельства возможны операции над классом и для него предусматриваются соответствующие атрибуты. В языках C++ и Java операции и значения данных, ассоциированные с конкретным классом, син- таксически обозначаются ключевым словом static. Мы будем называть такие опе- рации статическими операциями. Из наличия общедоступных статических опера- ций в спецификации класса следует, что сам такой класс является объектом, который может передаваться в сообщениях. С позиций перспективы тестирования мы должны рассматривать этот класс как объект и создавать для него, равно как и для всех его экземпляров, тестовые наборы. С позиций перспективы тестирования мы всегда должны относиться с подозрением к непостоянным статическим данным, имеющим отношение к классам, ибо такие данные могут вносить изменения в поведение экземпляров.
Глава 2. Перспектива тестирования В главе 4 мы рассмотрим проблемы, которые могут возникнуть из-за того, что проектировщики имеют доступ к возможным реализациям классов, и как обнаружить эти проблемы во время проводимых ревизий. Спецификация класса Спецификация класса представляет собой описание того, чем является класс и что экземпляры этого класса могут делать. Спецификация класса включает спе- цификацию каждой операции, которые может совершать каждый из его экземп- ляров. Операция — это действие, которое может быть применено к объекту с це- лью получения некоторого результата. Операции делятся на две категории: Операции доступа (или инспекции) — accessor (inspector) operations — пре- доставляют информацию об объекте — например, значение некоторого ат- рибута или общая информация о состоянии объекта. Этот вид операций не вносит изменений в объект, для которого она запрашивалась. В С++ операции доступа могут и должны быть объявлены как const. Операции модификатора (или мутации) — modifier (mutator) operations — меняют состояние объекта путем назначения одному или нескольким ат- рибутам новых значений. Мы прибегаем к помощи такой классификации в связи с тем, что при тести- ровании операции доступа отличаются от операций модификации. В специ- фикацию класса могут быть включены операции, которые одновременно пос- тавляют информацию и выполняют ее обработку.3 Некоторые операции модификаторов вообще не вызывают никаких изменений ни при каких услови- ях. Во всяком случае, мы определяем эти операции как операции модификато- ров. Имеются два вида операций, которые заслуживают специального рассмотре- ния: Конструктор представляет собой операцию над объектами типа класса, в результате которой создается новый объект, при этом в момент появления нового экземпляра, производится его инициализация. Деструктор есть операция, проводимая над экземпляром объекта и исполь- зуемая для выполнения любой обработки данных, необходимой на заклю- чительной стадии жизненного цикла объекта. Конструкторы и деструкторы отличаются от средств доступа и модификато- ров в том, что они вызываются косвенно в результате появления и исчезнове- ния объектов. Некоторые из этих объектов видимы в программах, другие не ви- димы. Хорошим тоном в практике объектно-ориентированного проектирования считается, когда опера- ция принадлежит к какой-то одной категории, но не к обоим сразу.
Глава 2. Перспектива тестирования 43 Оператор х = а + b + с; в котором а b с и х являются объектами одного и того же класса, вызывает кон- структор этого класса по меньшей мере дважды с тем, чтобы создать объекты, которые хранят промежуточные результаты и разрушаются в конце оператора, а именно tmp! = а + Ь; tmp2 = tmp, + с; х = tmp2; Класс представляет некоторую концепцию либо в задаче, которая решается за счет применения соответствующего программного обеспечения, либо в решении этой задачи. Мы рассчитываем, что описания того, что представляет собой класс, является частью спецификации класса. По всей видимости, не совсем по- нятно, например, для чего предназначается класс PuckSuply, объявленный в программе на рис. 2.2, без пояснения, что он представляет набор шайб, который предоставляется в распоряжение игрока на начальной стадии игры «Кирпичики». Когда игрок позволяет шайбе в процессе игры упасть на пол, программа заменя- ет ее другой шайбой, беря ее из запаса шайб, пока этот запас не будет исчерпан (в этот момент игра завершается поражением игрока). Мы также полагаем, что с каждой операцией, определение которой содержит- ся в спецификации класса, связаны некоторые понятия и ограничения, напри- мер, имеют ли операции size() и get() над классом PuckSuply для вас какое-либо особое значение? Следовательно, для каждой операции должна быть своя специ- фикация, в которой описано, что собственно эта операция делает. Специфика- ция класса Pucksuply представлена на рис. 2.4. Запас шайб — это набор шайб, которые до поры не используются в игре; запас каждый раз можно уменьшать на единицу. Шайбы создаются запасом шайб в момент, когда он создается сам. • Инвариант класса. Счетчик, ессоциировенный с запасом шайб, всегда принимает целые значения в диапазоне от нуля до трех включительно. • Операция sized может применяться в любой момент. Она возвращает количество шайб, оставшихся в приемнике, и не оказывает влияния на состояние приемника. • Операция get() может применяться, если в приемнике остается по меньшей мере одна шайба, т.е. когда атрибут size (размер] больше нуля. Результат операции заключается в том, что в игру возвращается шайба, а их запас уменьшается на единицу. • Для выполнения конструктора не требуется никаких предусловий. Результатом выполнения конструктора является зепас из трех шайб — т.е. значение атрибута size равно трем. • Для выполнения деструктора не требуется никаких предусловий. Деструктор уда- ляет все шайбы, которые остаются в уничтожаемом объекте. Рисунок 2.4. Спецификация класса Pucksuply, построенная на основе контрактов
Глава 2. Перспактива тестирования Операция size() может быть применена в любой момент (без каких-либо предварительных условий); она возвращает число шайб в приемнике. Операция get() может быть применена, если по меньшей мере одна шайба остается в запасе, т.е. когда size() > 0. В результате выполнения этой опе- рации очередная шайба вводится в игру, а количество шайб в приемнике уменьшается на единицу. Хорошо определенная семантика операции критична как в отношении разра- ботки, так и в отношении тестирования, и несомненно достойна времени и уси- лий, затрачиваемых на правильную ее формулировку. Можно воспользоваться различными формами записи с тем, чтобы дать описание семантики при усло- вии, что она хорошо понятна всем, кто должен работать с ней. Мы дадим опи- сание семантики, выделив несколько различных особенностей: Предусловия для некоторой операции определяют условия, которые долж- ны быть выполнены еще до того, как эта операция начнет выполняться. Предусловия обычно формулируются в терминах атрибутов объекта, со- держащего рассматриваемую операцию, и/или атрибутов любых фактичес- ких параметров, включенных в сообщение с запросом на выполнение этой операции. Постусловия для некоторой операции предписывают условия, которые должны сохраняться и после того, как эта операция будет выполнена. По- стусловия обычно формулируются в терминах (1) атрибутов объекта, со- держащего операцию; (2) атрибутов любых фактических параметров, включенных в сообщение, которое затребовало, чтобы эта операция была выполнена; (3) в терминах значения любого ответа; и/или (4) в виде со- общений об исключительных ситуациях, которые могут при этом возник- нуть. Инварианты предписывают условия, которые должны выполняться на про- тяжении всего жизненного цикла объекта. Инвариант класса определяет набор границ рабочих областей конкретного экземпляра класса. Имеется также возможность определить инварианты интерфейса, равно как и инва- рианты для конкретных фрагментов программ. Инвариант класса можно рассматривать как постусловия, связанные с каждой операцией. Они дол- жны выдерживаться и после завершения операции, хотя методу, реализую- щему операцию, разрешается нарушать инварианты во время выполнения операций. Инварианты обычно формулируются в терминах атрибутов или состояний объектов. Совокупность спецификаций всех операций конкретного класса составляет часть описания поведений его экземпляров. О поведении трудно судить только по спецификациям операций, таким образом, поведение обычно разрабатывается и представляется на более высоком уровне абстракции в терминах состояний и переходов (см. раздел «Диаграммы состояний»). Поведение описывается за счет
Глава 2. Перспектива тестирования объявления набора состояний экземпляра с последующим описанием того, как различные операции вызывают переход из одного состояния в другое. Состоя- ния, ассоциированные с запасом шайб в игре «Кирпичики», определяют, пуст запас или не пуст запас. Если его размер равен нулю, то запас пуст, в против- ном случае он не пуст. Пустой запас определяется атрибутом размера запаса шайб. Если размер равен нулю, то запас шайб пуст, в противном случае он не пуст. Вы можете удалить шайбу из запаса, только если он не пуст, т.е., если за- пас не нулевой. При разработке спецификации операции для определения интерфейса между получателем и отправителем можно воспользоваться одним из двух базовых подходов. У каждого подхода имеется набор правил, регламентирующих способы определения ограничений и обязанностей отправителя и получателя при необ- ходимости выполнить ту или иную операцию. В спецификации, представленной на рис. 2.4, реализуется контрактный (contract) подход. В основу спецификации, представленной на рис. 2.5, положен подход защитного программирования (defensive programming). В условиях контрактного подхода основное внимание уделяется предварительным условиям, а постусловия достаточно просты, в то время как в условиях подхода защитного программирования имеет место проти- воположный подход. В условиях контрактного подхода, который представляет собой метод проек- тирования, разработанный Бертрандом Мейером (Bertrand Meyer, [Меуе94]), ин- терфейс определен в виде обязательств отправителя и получателя, вступивших во взаимодействие. В этом случае операция определена как набор обязательств каждой стороны. Запас шайб представляет собой набор шайб, на использующихся в игрв, который можно уменьшать на единицу за один раз. Шайбы создаются классом запаса шайб в момант, когда он создается сам. Примечание: Со временем можно добавить спо- соб увеличения количества шайб в запаса. • Инвариант класса. Счатчик, ассоциированный с запасом швйб, всегда принимает целые значения в диапазоне от нуля до трах включительно. • Операция aize() может применяться а любой момант. Она возвращает число шайб, оставшихся у получателя, и на алиявт на состояние приемника. • Операция get(J может применяться в любое время. Если по маньшей маре одна шайба остается в получателя, т.в„ когда атрибут aize (размер] больше нуля, то результат операции асть возврат указателя на шайбу, который уменьшает запас швйб на единицу. В противном случав возвращается нулевое значвниа указателя, а атрибут count (число швйб) равен нулю. • Для выполнения конструктора на требуется никаких предварительных условий. Результатом выполнения конструктора является создание запаса из трех шайб — т.в. значение атрибута count равно трем. • Для выполнения деструктора нв требуется никаких предварительных условий. • Деструктор удаляет все шайбы, которые остались в запаса. Рисунок 2.5. Спецификация класса Pucksuply на базе защитного программирования
46 Глава 2. Перспектива тестирования Обычно они оформлены как некоторый набор предварительных условий и постусловий для выполнения конкретной операции, а также совокупности инва- риантных условий, которые должны оставаться неизменными в процессе выпол- нения всех операций, которые можно рассматривать как постусловия для всех операций. Предварительные условия определяют обязанности отправителя — т.е., прежде чем отправитель сможет послать запрос получателю на выполнение той или иной операции, он должен позаботиться о выполнении всех предвари- тельных условий. После того как все предварительные условия будут удовлетво- рены, получатель должен обеспечить выполнение всех требований, сформулиро- ванных как постусловия и как инварианты класса. В условиях контрактного подхода особое внимание следует уделять проектированию интерфейса класса с тем, чтобы предварительные условия были достаточно полными, дабы обеспе- чить получателю возможность выполнить постусловия (если это невозможно, потребуется включить дополнительные предусловия), чтобы перед отправкой со- ответствующего сообщения отправитель мог определить, выполнены ли все не- обходимые предварительные условия. Как правило, набор методов средства дос- тупа позволяет проверить выполнение заданных условий. Более того, в условиях, когда все предварительные условия удовлетворены, необходимо строго следить за тем, чтобы постусловия охватывали все возможные исходы конкрет- ной операции в предположении, что все предусловия выполнены. В условиях подхода с использованием защитного программирования интер- фейс, равно как и любые предположения с его стороны относительно собствен- ного состояния и значений любого из входных данных (аргументов или значе- ний глобальных данных) во время формирования запроса, определяются главным образом в терминах получателя. В рамках такого подхода операция обычно возвращает определенные сигналы, характеризующие состояние резуль- тата запроса — успешное или неудачное выполнение в силу конкретной причи- ны, например, по причине неправильного входного значения. Такой сигнал обычно поступает в форме кода возврата, который связывает некоторое значе- ние с каждым возможным результатом. В то же время получатель может предос- тавить в распоряжение отправителя объект, который инкапсулирует состояние запроса. Более того, чаще используются исключительные состояния, поскольку в настоящее время их поддерживают многие языки объектно-ориентированного программирования. Некоторые операции определены таким образом, что ника- кие указания о состоянии в случае отказа не возвращаются, вместо этого выпол- нение программы прекращается, если условия запроса не выполняются. Есте- ственно, такое поведение недопустимо для большинства систем программного обеспечения. Основная цель защитного программирования заключается в том, чтобы рас- познать ситуацию «мусор на входе» и тем самым исключить ситуацию «мусор на выходе». Соответствующая функция-член следит за тем, чтобы на вход не попа- ли некоррекные данные, после чего она сообщает состояние процесса обработки запроса отправителю. Этот подход повышает сложность программного обеспече-
Главе 2. Перспективе твстироввния 47 ния, поскольку каждый отправитель должен сопроводить запрос на выполнение операции кодом, позволяющий определить состояние процесса обработки запро- са, а затем независимо от исхода проверки выполнить программу, реализующую необходимые восстановительные действия. При таком подходе обычно увеличи- вается как размер программ, так и время исполнения, поскольку проверка вход- ных данных производится при каждом обращении, даже если сам отправитель их уже проверил.4 Контрактный подход и защитное программирование представляют собой две противоположных точки зрения на спецификацию программного обеспечения. Как следует из названия, защитное программирование отражает недостаток до- верия отправителя по отношению к получателю. И наоборот, контракт свиде- тельствует о том, что ответственность по отношению друг к другу соблюдается как отправителем, так и получателем. Получатель производит обработку запроса исходя из предположения, что входные данные соответствуют установленным предусловиям. Отправитель полагает, что все условия будут выполнены по за- вершении обработки запроса. Нередко случается так, что в спецификациях опе- раций в рамках одного и того же класса применяются оба подхода, поскольку каждый из них обладает собственными достоинствами и недостатками. Проект интерфейса, основанного на соглашениях, устраняет необходимость производить проверку выполнения предусловий при каждом вызове.5 Это позво- ляет воспользоваться более совершенной технологией разработки программного обеспечения и повысить эффективность программ (и труда программиста). Од- нако при этом возникает один важный вопрос: «Как реализуются контракт в контексте исполняемой программы?» Разумеется, соглашения налагают опреде- ленные обязательства как на отправителя, так и на получателя. Тем не менее, может ли получатель рассчитывать на то, что каждый отправитель соблюдает все предусловия? Последствия возникновения ситуации «мусор на входе» могут оказаться катастрофическими. Выполнение программы в условиях, когда отправитель не может выполнить всех предусловий, по всей вероятности, приведет к искажению данных, а это чревато серьезными последствиями! Очень важно, чтобы все взаимодействия, выполняемые с соблюдением условий контракта, проходили тестирование на предмет соответствия этому контракту. 4 Интересно отметить, что приведенные выше программные коды, которые были написаны в рамках подхода защитного программирования, в редких случаях выполняют проверки с целью убедиться, что получатель выполнил затребованную операцию — т.е. недоверие проявляется только со сторо- ны получателя. Возможно, такая практика берет начало из того факта, что программный код полу- чателя обычно проходит через процедуру тестирования, в силу чего он считается достаточно на- дежным и будет работать корректно. Неправильное использование может иметь место только на стороне отправителя; эти вопросы будут рассматриваться в главе 5. s Определенную пользу при отладке приносит включение в программу кодов для проверки выполне- ния предусловий. Эти коды могут быть затем «извлечены» из программ по окончании отладки сис- темы, но перед ее завершающим тестированием. В языке Eiffel fMeyeOO] имеются языковые сред- ства поддержки проверки контракта и ключи компилятора, позволяющие при необходимости включать или отключать эту поддержку.
Главв 2. Перспектива тестирования С позиций перспективы тестирования подход, используемый в интерфейсе, определяет виды тестирования, которые необходимо проводить. Контрактный подход упрощает тестирование классов, но в то же время усложняет тестирова- ние взаимодействий, поскольку мы должны быть уверены в том, что каждый от- правитель выполняет предусловия. Подход защитного программирования услож- няет задачу тестирования классов (поскольку контрольные примеры должны быть рассчитаны на любые возможные результаты) и тестирования взаимодей- ствий (поскольку мы должны убедиться в том, что можем получить все возмож- ные результаты взаимодействий и что отправитель воспринимает их должным образом). СОВЕТ Выполните ревизию предусловий, постусловий и инвариантов на контролепригод- ность во время разработки. Достаточно ли четко определены эти ограничения? Содержит пи спецификация средстве, с помощью которых осуществляется про- верка предусловий? Реализация класса Реализация класса описывает, как объект представляет свои атрибуты и вы- полняет операции. Она охватывает несколько компонентов: Наборы значений данных, которые хранятся в объектах данных и которые иногда называют переменными экземпляров, или просто переменными. В значениях данных хранятся некоторые или даже все значения, связанные с атрибутами объекта. При этом вовсе не обязательно отображение «один к одному» атрибутов на значения данных. Значения некоторых атрибутов могут быть получены по значениям других атрибутов — например, гори- зонтальная составляющая движения шайбы может быть вычислена по ее скорости. Некоторая избыточность представления вычисляемых атрибутов в конкретных случаях оказывается даже полезной, ибо она позволяет по- высить эффективность функций-членов в смысле уменьшения времени выполнения. В некоторых случаях атрибут, выбранный для того или иного объекта, может не быть представленным вообще, поскольку он не нужен в приложении. Удаляя этот атрибут, мы уменьшаем объем памяти, необхо- димый для размещения такого объекта. Наборы методов, которые в C++ получили название функций-членов, а в Java — методов, и которые представляют собой программный код, исполь- зуемый для реализации алгоритма совершения операции, объявленной в общедоступной или приватной спецификации класса. Такой программный код обычно использует или устанавливает значения объектных перемен- ных. Он производит обработку любых значений фактических параметров, проверяет, не возникли ли исключительные условия, и вычисляет возвра- щаемые значения, если это предусмотрено спецификацией исполняемой операции.
Глава 2. Перспектива тестировения 49 Наборы конструкторов, предназначенных для инициализации новых эк- земпляров (в начале их жизненного цикла). По существу, конструктор представляет собой операцию, выполняемую над объектом типа класс. Деструктор, который выполняет все виды обработки, связанных с уничто- жением экземпляров (при достижении этим экземпляром конца жизненно- го цикла). Набора приватных операций в приватном интерфейсе.6 Приватные опера- ции обеспечивают поддержку реализации общедоступных операций. Тестирование классов представляет собой важный аспект общего процесса те- стирования, поскольку классы образуют строительные блоки объектно-ориенти- рованных программ. Поскольку класс суть абстракция того общего, что характе- ризует экземпляры этого класса, то процесс тестирования классов должен проводиться таким образом, чтобы для тестирования были отобраны представи- тельные образцы членов классов. Рассматривая класс с позиций перспективы тестирования, можно отметить следующие потенциальные источники отказов, которые могут появиться на ста- диях разработки и реализации этих классов: Спецификация класса содержит операции построения новых экземпляров. Эти операции могут допускать ошибки при инициализации атрибутов но- вых экземпляров. Класс определяет свое поведение и значения атрибутов во взаимодей- ствии с другими классами. Например, переменная экземпляра может при- нимать значение экземпляра другого класса или некоторый метод может послать сообщение параметру (когда таковым является экземпляр другого класса) с тем, чтобы тот выполнил какую-то часть необходимых ему вы- числений. Эти другие классы могут быть построены некорректно и стать причиной отказа класса, использующего их в определении. Реализация каждого класса «удовлетворяет» спецификации этого класса, но это отнюдь не означает, что сама спецификация безошибочна. Реализа- ция может нарушать требования более высокого уровня, такие как, напри- мер, принятые критерии проектирования, либо она может просто непра- вильно моделировать концепцию, положенную в ее основу. Реализация может не поддерживать все необходимые операции либо не- правильно выполняет эти операции. Класс определяет предусловия выполнения каждой операции. В самом классе могут отсутствовать средства, воспользовавшись которыми, отправитель мо- жет проверить выполнение предусловий перед отправкой сообщения. 1 Для простоты изложения мы обычно будем пользоваться термином «приватный» для обозначения любого аспекта класса, который не принадлежит к категории общедоступных. Язык C++ поддер- живает приватные и защищенные компоненты класса, a Java поддерживает еще большее количество уровней доступа к компонентам.
Глава 2. Перспектива тестирования ПОДСИСТЕМЫ И КЛАССЫ Класс с точки зрения систем и подсистем — это очень интересное понятие. Системы или подсистемы могут описываться многими различными способами как класс, ассоциированный со сложным поведением, и в то же время как класс, наделенный состояниями, переходами и интерфейсом. В этой книге большое внимание уделяется тестированию классов. Многие из обсуждеемых здесь методов тестирования могут быть расширены до тестирования систем и подсистем, если система и на самом деле описене кек клесс. Однеко, обратите внимение на то обстоятельство, что сложность этого классе немного превышает сложность класса, подобного Point. Это можно рассматриветь кек указание на то, что некоторые проблемы следует решеть на уровне тестирования классов. Со своей стороны, выбранный подход к определению интерфейса, будь то контрактный подход или подход защитного программирования, дает начало воз- никновению различных категорий потенциальных проблем. В рамках контракт- ного подхода достаточно провести тестирование ситуаций, в которых предусло- вия выполняются. В рамках подхода защитного программирования потребуется выполнить проверку каждого возможного входного набора данных, чтобы иметь уверенность, что на выходе получены корректные результаты. Наследование Наследование — это отношение между классами, которое позволяет давать определение нового класса, за основу которого взято определение существующе- го класса. Такая зависимость одного класса от другого позволяет повторно ис- пользовать спецификацию и интерфейс старшего «по возрасту» класса.7 Важное преимущество такого подхода состоит в том, что для этого не требуется какая- либо модификация существующего класса; появление нового класса никак на нем не отражается. Новый класс называется субклассом или (в C++) производ- ным классом. Если какой-либо класс является наследником другого класса, этот другой класс будем называть суперклассом или (в C++) базовым классом. Мно- жество классов, каждый элемент которого прямо либо косвенно наследует свой- ства какого-то одного конкретного класса, называется иерархией наследования. В такой иерархии наследования выделяется корень, и этим корнем является класс, от которого прямо или косвенно наследуются все остальные классы. ’ В языках программирования, которые поддерживают множественное наследование, новый класс может быть определен в терминах одного или большего количества существующих классов. С++ поддерживает множественное наследование, в то время как множество других объектно-ориенти- рованных языков его не поддерживают. Многие разработчики избегают использования множествен- ного наследования в силу его сложности. Иногда множественное наследование приносит большую пользу, например, в случае моделирования аналогичных свойств двух каких-нибудь подклассов, на- ходящихся в иерархии наследования на одном уровне. Мы сосредоточимся главным образом на изучении свойств единичного наследования, тем не менее, все же обратимся к множественному на- следованию при рассмотрении важных случаев отладки.
Глава 2. Перспектива тестирования 51 Каждый класс иерархии, за исключением корня, имеет ноль или большее число предшественников; таковыми будут класс (классы), свойства которого они наследуют прямо или косвенно. Каждый класс иерархии, за исключением корня, имеет ноль или большее число наследников, коими являются классы, которые прямо или косвенно наследуют его свойства. Хороший стиль объектно-ориентированного проектирования требует, чтобы наследование использовалось только для реализации отношения есть (или есть вид). Наилучшие результаты наследование приносит, будучи примененным к спецификациям, но не к реализациям. Это требование станет очевидным в кон- тексте полиморфизма включения. С позиций перспективы тестирования наследование: Порождает механизм, посредством которого ошибки переносятся из класса в его наследники. Тестирование класса, проведенное сразу же после его проектирования, устраняет такие ошибки еще до того, как они распрост- ранятся на другие классы. Порождает механизм, благодаря которому появляется возможность много- кратно использовать контрольные примеры. Поскольку подклассы наследу- ют определенную часть спецификаций и реализаций суперкласса, мы в принципе можем воспользоваться контрольными примерами, применяв- шимися для тестирования суперкласса, при тестировании подклассов. Модели представляют собой вид отношений. Использование наследования только лишь для многократного использования программных кодов, по всей видимости, вызывает определенные трудности в сопровождении. В основном, это проблема обеспечения качества проекта, но мы покажем, что это настолько распространенная ошибка в условиях объектно-ориенти- рованного программирования, что тестировщики могут внести существен- ный вклад в успех проекта, если проверят его на правильность использо- вания механизма наследования. Кроме того, правильное использование механизма наследования в проекте создает благоприятные предпосылки для тестирования классов в рамках контрольного прогона программ (глава 7). Полиморфизм Полиморфизм — это возможность рассматривать объект как принадлежащий к более чем одному классу. Система типов в языке программирования может быть определена с расчетом на поддержку нескольких различных стратегий согласова- ния типов. Наиболее безопасной может оказаться стратегия точного соответ- ствия, в то же время полиморфная система типов поддерживает гибкие и удоб- ные в сопровождении проекты.
52 Глава 2. Перспектива тастирования ПРИНЦИП ПОДСТАНОВКИ Наследование может использоваться только для модалирования отношения есть (или есть вид}. Другими словами, если D есть подкласс класса С, то это следуат понимать как тот факт, что D есть вид класса С. Экземпляр подкласса D, постро- енный по принципу подстановки [LiWi94], можат задействоваться всякий раз, когда ожидается экземпляр класса С. Другими словами, асли какая-либо программа, спроектирована с расчетом на работу в некотором контаксте с экземпляром класса С, то экземпляр класса D а этом контексте можно заменить, а программа, тем на менее, будет работать. Чтобы это имело место, поведение, ассоциированное с клас- сом D, должно быть в какой-то мере согласовано с поведением, ассоциированным с классом С. Один из методов обаспачания «подставимости» заключается в том, чтобы ограни- чивать изменения поведений при переходе от классов к подклассам. Поведение, ассоциироаанноа с некоторым классом, можат быть определено чараз наблюдаемые состояния экземпляра и семантику, которая ассоциирована с различными операци- ями, определенными для экземпляра данного класса. Поведение, ассоциированное с подклассом, может быть определено в терминах инкрементальных изменений на- блюдаемых состояний и операций, определенных аго базовым классом. Согласно принципу подстановки, при определении поведения, ассоциированного с новым подклассом, допустимы только такие изменения: • Предусловия для каждой операции могут остаааться теми жа или быть менее строгими, т.е. менев ограничивающими с точки зрения клиента. • Постусловия для каждой операции должны оставаться теми же или быть болаа строгими, т.е. их должно быть, по маньшей мере, столько же или больше, чем в суперкласса. • Инвариант класса должен быть таким же или строжа, т.а. вводить дополнитель- ные ограничения. Эти ограничения на изменение поведений должны вноситься разработчиками. С позиций перспективы тастирования наблюдаемых состояний мы можем показать, что: • Наблюдаемые состояния и все переходы из одного состояния а другое, ассо- циированные с базовым классом, должны сохраняться подклассом. • Подкласс может увеличить количество переходов из одного такого состояния в другое. • Подкласс может увеличить количество наблюдаемых состояний при условии, что каждое новое состояние есть либо параллельное состояние, либо подсостояние существующего состояния. Полиморфизм включения Полиморфизм включения — это вхождение различных форм в один и тот же класс. Поддержка полиморфизма включения объектно-ориентированными язы- ками программирования8 предоставляет программистам возможность подстанов- 8 Некоторые называют такую поддержку динамическим связыванием. Динамическое связывание представляет собой установленную на время исполнения программы взаимосвязь между операцией, затребованной в сообщении, и методом, выполняющим затребованную операцию. Но в то же вре- мя динамическое связывание — это механизм, посредством которого полиморфизм включения реа- лизуется средой выполнения программы. В C++ динамическое связывание запрашивается через ключевое слово virtual в объявлении функции-члена.
Глава 2. Перспектива тестирования 53 ки объекта, спецификация которого соответствует спецификации другого объек- та, вместо этого другого объекта в запросе на выполнение конкретной операции. Другими словами, отправитель в объектно-ориентированной программе может использовать объект в качестве параметра, пользуясь реализацией его интерфей- са, а не его классом в целом. В среде C++ полиморфизм включения возникает из отношения наследова- ния. Подкласс наследует общедоступный интерфейс от своего базового класса9, и благодаря этому обстоятельству экземпляры подкласса могут откликаться на те же сообщения, что и базовый класс10. Отправитель может манипулировать эк- земплярами любого класса посредством значения, которым может быть либо ссылка, либо указатель, и результирующим типом которого является базовый класс. При помощи этого значения можно вызывать функции-члены. В среде Java полиморфизм включения поддерживается как через отношение наследования, существующее между классами, так и через отношение реализа- ции, существующее между интерфейсами и классами. Отправитель может мани- пулировать объектами при помощи ссылки, объявленной как для класса, так и для интерфейса. Если ссылка ассоциирована с интерфейсом, то такая ссылка мо- жет быть привязана к экземпляру любого класса, который по определению реа- лизует этот интерфейс. На наше определение класса как набора объектов, которые совместно исполь- зуют общую концептуальную базу (см. стр. 22), оказывает влияние прежде всего взаимосвязь наследования и полиморфизма включения. Класс, служащий кор- нем иерархии, устанавливает общую концептуальную базу для всех объектов та- кого набора. Наследник этого корневого класса уточняет поведение, установлен- ное корневым классом и всеми другими его предшественниками. Объекты классов-наследников все еще содержатся в наборе, образованном корневым классом. Таким образом, класс-потомок определяет поднабор объектов для каж- дого из наборов объектов, являющихся его предшественниками. Предположим, что спецификация игры «Кирпичики» расширена с целью включения дополни- тельных типов кирпичей — скажем, твердых кирпичей, каждый из которых ис- чезает с игрового поля только после того, как шайба попадет в него два раза, а также т.н. ускоряющих кирпичей, которые увеличивают скорость перемещения любой шайбы после того, когда она от них отскакивает. Классы HardBrick и PowerBrick могут быть определены как подклассы класса Brick. Отношения меж- ду соответствующими двумя наборами объектов иллюстрируются на рис. 2.6. Обратите внимание на то, как полиморфизм отражается на структуре класса Brick — он содержит 24 элемента: 10 «простых» кирпичей, 8 твердых и 6 уско- ряющих кирпичей. 9 Мы полагаем, что используется общедоступное наследование. Мы считаем, что защищенное и при- ватное наследование должно применяться только в редких случаях. 10 Экземпляры производных классов обладают способностью откликаться на дополнительные сообще- ния, поскольку производный класс определяет дополнительные операции в своем общедоступном интерфейсе.
54 Глава 2. Перспектива тестирования Твердые и ускоряющие кирпичи обладают специальными свойствами, но в то же время они реагируют на те же сообщения, что и «простые» кирпичи, хотя, возможно, и по-разному. Наборы объектов, представляющие классы, можно рассматривать в двух перс- пективах: 1. В перспективе класса каждый набор содержит все экземпляры. В принципе, размер такого набора не ограничен, как в случае с кирпичами, поскольку теоретически мы можем обеспечить кирпичами любое количество сеансов игры «Кирпичики» и даже другие игры для игровых автоматов (памятуя о том, что класс Brick не обязательно связан только с игрой «Кирпичики»). Неограниченные наборы легче всего представить, воспользовавшись диаг- раммой Венна. 2. В перспективе выполняемой программы каждый такой набор изображается в виде совокупности элементов, в которой каждому элементу соответствует существующий экземпляр. Наборы, показанные на рис. 2.6, представлены именно в такой перспективе. Обе перспективы могут оказаться полезными при тестировании. В тех случа- ях, когда класс требует тестирования за пределами контекста любой прикладной программы (см. главы 5 и 6), мы будем проводить его тестирование, выбирая произвольные элементы, руководствуясь первой перспективой. Когда востребо- вано тестирование класса в контексте исполнения прикладных программ или в контексте устойчивости объектов, мы можем прибегнуть ко второй перспективе, чтобы убедиться в том, что размер набора выбран правильно, а его элементы со- ответствуют объектам в проблеме или в ее решении. Полиморфизм включения обеспечивает огромные возможности для тестиро- вания. Вы можете вносить любые изменения в структуры и программы интер- фейсов, не обращая внимания на то, какому классу принадлежит объект, кото- рому адресовано сообщение с запросом выполнить ту или иную операцию. Полиморфизм включения поднимает разработку и программирование на более высокий уровень абстракции. По существу, появились предпосылки для опреде-
Глава 2. Парспектиаа тестирования 55 Рисунок 2.7. Диаграмма наборов иерархии наследования класса Brick. ления класса, у которого экземпляры отсутствуют, зато вместо этого существуют экземпляры у его подклассов. Абстрактный класс — это класс, основной задачей которого является определение интерфейса, поддерживаемого всеми его наслед- никами." Применительно к рассматриваемому примеру игры «Кирпичики», при введении новых типов кирпичей альтернативной формулировкой является опре- деление абстрактного класса с именем Brickies с последующим определением трех его подклассов: PlainBrick, Hard Brick и PowerBrick (см. рис. 2.7). В числе абстрактных классов, которые будут использоваться при проектиро- вании класса Brickies, входят следующие абстрактные классы: Класс Sprite для представления предметов, которые могут появиться на игровом поле. Класс MovableSprite, который является подклассом класса Sprite, пред- ставляющий спрайты, которые могут перемещаться по игровому полю. Класс StationarySprite, который является подклассом класса Sprite, пред- ставляющий спрайты, которые не могут перемещаться по игровому полю. Puck и Paddle суть конкретные подклассы класса MovableSprite, в то время как Brick есть подкласс класса StationarySprite. Использование абстракций по- зволяет прибегать к помощи полиморфизма во время разработки. Например, мы можем проектировать на уровне игрового поля, содержащего спрайты, не имея полного представления о всем разнообразии этих спрайтов. Мы можем проекти- ровать на уровне подвижных спрайтов, перемещающихся в игровом поле и стал- кивающимися с другими спрайтами — как с движущимися, так и с неподвиж- ными. 11 Абстрактный класс может также определять конкретные ограничения на реализацию своих наслед- ников. И в C++, и в Java имеются специальные синтаксические конструкции для определения аб- страктных классов, которые не допускают создания экземпляров этих классов во время выполне- ния программы.
56 Глава 2. Перспектива тестирования ПОДКЛАССЫ 1/1 ПОДТИПЫ Рассмотрим провктнов решение, првдусматриввющвв использование полиморфизме включения. Изображенные на приводимой ниже диаграмме классы С и D являются наследниками классе В. Экземпляры класса А убеждены в том, что они посылают сообщения экземплярам класса В (тип формального параметра есть В]. Полимор- фный атрибут, назначенный системой типов, позволяет применить экземпляры клас- сов С и D вместо экземпляре класса В. В каждом классе метод dolt() реализован по-свовму. Прввильнов провктироввнив программного обеспечения в контвкств наслвдоавния и полиморфизма включения трвбувт дозированного использоввния мвхвнизмв насле- дования (в в Jbvb также и интерфейсов]. Очень важно, чтобы поведение нв менялось в случаях добавления новых классов, которые расширяют иерархию классов. Если, напримвр, кирпичи могли бы двигаться, то их ужв нельзя было бы клвссифицироаать как стационврныв спрайты. Хороший тон в проектировании трвбувт, чтобы каждый производный класс был подтипом, т.в., чтобы спецификация подклвсса полностью соотввтстаовала всем спецификациям своего нвпосрвдстввнного првдшвстввнника. Это требование обязательно в твх случаях, когда по отношению к предусловиям и постусловиям каждой унаследованной операции применяются следующие правила: . • Метод trylt() классе А написан с целью удовлетворить предусловиям операции dolt() класса В, првждв чвм этот класс вызовет dolt() Если трвбувтся выпол- нить подстановку экземпляров С или D, то предусловия для C::dolt() или □::dolt( ) нв должны привносить каких-либо новых условий в предусловия для B::dolt(), либо нам придется вносить изменения в А с твм, чтобы соответству- ющим образом приспособить классы С и D. • Метод trylt() класса А написан с расчетом удовлетворить предусловиям опе- рации dolt() классе В, прежде чвм этот класс вызовет dolt() Если трвбувтся выполнить подстановку С или D, то предусловия для C::dolt() или D::dolt() нв должны привносить квких-либо новых условий в предусловия для B::dolt(), либо нем придется вносить изменения в А с твм, чтобы соответствующим образом приспособить классы С и D. • Инвариант, определенный для В, должен сохранять силу в экземплярах классоа С и О. Можно добавлять и дополнительные инварианты. Эти требования нетрудно понять а контексте контрактного программного обеспече- ния. Предусловия формулируют обязатальства каждого отправителя, а постусловия и инварианты классов формулируют обязательства получателя при любых взаимо- действиях. Требования тех же или болвв слабых (мвнвв строгих) предусловий и инвариантов означают, что получатель может оправдать ожидания отправителя и удовлетворить требованиям контракта, сформулированным для класса А, даже если этот получатель может сделать больше того, на что рассчитывает отпрввитвль.
Глава 2. Перспектива тастирования 57 Если бы спецификация игры была расширена за счет включения твердых и ускоряющих кирпичей, то большая часть программы осталась бы неизменной, поскольку как твердые кирпичи, так и ускоряющие кирпичи — это всего лишь неподвижные спрайты. Те части программы, которые при этом подвергаются из- менениям, включают лишь компоненты, осуществляющие построение фактичес- ких экземпляров классов. Полиморфная ссылка скрывает фактический класс объекта ссылки. Манипу- лирование всеми такими объектами ссылок осуществляется через их общий ин- терфейс. C++ и Java обеспечивают поддержку определения фактического класса объекта ссылки во время выполнения программы. Хороший тон объектно-ориен- тированного программирования требует, чтобы такого рода проверки во время прогона программы были сведены до минимума, главным образом, в силу того, что они затрудняют сопровождение, поскольку расширение иерархии класса вле- чет за собой увеличение числа проверок типов. Тем не менее, возникают ситуа- ции, когда такие проверки оказываются вполне оправданными. С позиций перспективы тестирования можно выделить следующие функции полиморфизма включения: Полиморфизм включения позволяет проводить инкрементальное расшире- ние систем путем добавления новых классов вместо модификации суще- ствующих. В таких расширениях могут возникать непредвиденные взаи- модействия. Полиморфизм включения позволяет иметь один или большее число пара- метров полиморфных ссылок. Это приводит к увеличению количества возможных типов фактических параметров и требует надлежащего тести- рования. Полиморфизм включения позволяет любой операции задавать описание ответов, представляющих собой полиморфные ссылки. Фактический класс объекта ссылки может оказаться некорректным либо неожиданным для от- правителя. Динамическая природа объектно-ориентированных языков программирования уделяет повышенное внимание тестированию представительных образцов рабо- чих конфигураций. Статический анализ позволяет выявить потенциальные взаи- модействия, которые могут иметь место, но только рабочая конфигурация может показать, что происходит на самом деле. В главе 6 мы рассмотрим статистичес- кий метод, который содействует обнаружению конфигураций, позволяющих вы- явить максимальное число ошибок при минимальных затратах ресурсов. Параметрический полиморфизм Параметрический полиморфизм есть способность определять тип в терминах одного или большего числа параметров. Шаблоны в C++ обеспечивают возможность порождать «новый» класс во вре- мя компиляции. Этот класс новый в том смысле, что вместо формального пара-
58 Глава 2. Перспектива тестирования метра в определении подставляется фактический параметр. Благодаря этому свойству можно впоследствии создавать экземпляры новых классов. Эта возмож- ность широко использовалась в C++ для создания стандартной библиотеки шаб- лонов (Standard Template Library). Интерфейс простого класса List (класс постро- ения списков) показан на рис. 2.8. template<class ItemType, class Кеу> class List{ public: void add(ItemType* item); ItemType* retrieve(Key searchvalue); ) Рисунок 2.8. Шаблон класса List в C++. С позиций перспективы тестирования параметрический полиморфизм под- держивает различные типы отношений, берущих начало от наследования. Если шаблон работает в рамках одного сеанса порождения экземпляра, то нет гаран- тии того, что он будет работать и в рамках другого такого сеанса, поскольку программный код шаблона под правильной реализацией может подразумевать такие операции, как создание (детальных) копий и деструкторов. Этот факт дол- жен быть проверен на стадии проверки программного обеспечения. Имеется воз- можность писать шаблонные драйверы для тестирования различных частей шаб- лонов. АБСТРАКЦИЯ На протяжении этой главы мы время от времени обращались к понятию абстракции. Абстракция представляет собой процесс удаления подробностей из представления. Абстракция позаоляет рассматривать конкретную проблему или ее решение с раз- личных уровней детализации. Тем самым она предоставляет возможность оставлять вне нашего поля зрения те аспекты, которые на текущий момент не представляют никакого интереса. Объектно-ориентированные технологии интенсивно используют понятие абстракции — например, корневой класс иерархии наследования моделирует идею на более высоком уровне абстракции, нежели его наследники. В следующем разделе мы ознакомимся с целым рядом системных моделей, которые разрабаты- вались с целью повышения уровня детализации. Если рассматривать уровни абстракции с позиций перспективы тестирования, то в процессе разработки программного продукта уровни абстракции сопровождаются параллельными уровнями тестирующего анализа. Другими словами, начиная с наи- высшего уровня абстракции, мы можем проводить асе более тщательное исследо- вание разрабатываемого программного продукта и, следовательно, накапливать все более эффективный и точный набор тестов.
Глава 2. Перспектива тестирования 59 Продукты разработки Успешная разработка и успешное тестирование программного продукта суще- ственно зависят от качества документации. В процессе разработки появляется множество рабочих продуктов, которые представляют систему и/или предъявляемые к ней требования на различных уровнях разработки. Форма и содержание этих продуктов определяется различ- ными факторами, в том числе корпоративными интересами, квалификацией и опытом разработчиков, а также ограничениями на сроки разработки. Эти про- дукты представлены в различных формах записи. В этой книге мы используем язык UML (Unified Modeling language — Унифицированный язык моделирова- ния) [RJB98] в качестве языка концептуального моделирования и язык C++ в качестве языка программирования. Конечным продуктом любых разработок программного обеспечения являются программы и сопровождающая их документация, в том числе руководства для пользователей и эксплуатационная документация. Другие рабочие продукты раз- работки, включая аналитические и проектные модели, архитектурные модели и требования, которые оказывают влияние на качество разрабатываемой системы, реализуются обычным путем. Для этих продуктов характерен более продолжи- тельный жизненный цикл, чем у текущего проекта, и они могут использоваться и для других разработок. В данном разделе мы опишем некоторую совокупность объектов, которые, как мы полагаем, имеют существенное значение для успешной разработки объектно-ориентированного программного обеспечения. В наших примерах бу- дет задействоваться язык UML.12 Ваши программные продукты могут быть пред- ставлены в других нотациях, однако указанные выше модели так или иначе дол- жны отображать ту же информацию, которая описывается в этом разделе. В силу того, что эти продукты являются моделями представления программного обеспе- чения, мы будем их обсуждать с позиций перспективы тестирования. В языке UML модель представляет собой совокупность диаграмм. Каждая мо- дель отображает систему на конкретном уровне абстракции. Мы рассматриваем эти модели в данной главе, поскольку в главе 4 речь пойдет о том, как прово- дить «системные» испытания на моделях, а не на программных кодах. Виды ди- аграмм на языке UML, которые применяются при системном моделировании, перечисляются в списке на рис. 2.9. В монографии UML Distilled: A Brief Guide to the Standard Object Modeling Language [FoSb99| дается краткий и в то же время достаточно полный обзор языка UML.
60 Глава 2. Перспектива тастирования Диаграмма случаев использования (Use case diagram) На этой диаграмме представлены действующие субъекты и случаи использования системы, а также отношения между этими примерами использования. Диаграмма классов (Class diagram) На этой диаграмме представлены определения конкретных классов и отношения, связывающие эти классы. Пакетная диаграмма (Package diagram) На этой диаграмме представлены классы, объединенные в группы по определенному логическому принципу, а также зависимости между группами. Диаграмма последовательностей (Seguence diagram) На этой диаграмме показаны последовательности сообщений, представляющие алгоритм. Диаграмма состояний (State diagram) На этой диаграмме представлены различные конфигурации значений атрибутов данных и сообщений, которые преобразуют данные из одной конфигурации в другую. Диаграмма действий (Activity diagram) Представляет множество всевозможных путей в рамках логической структуры метода. Рисунок 2.9. Диаграммы UML, используемые в этой книге Аналитические модели Анализ охватывает те виды деятельности в рамках процесса разработки про- граммного обеспечения, целью которых является формулировка задачи, подле- жащей решению, и определение требований, обусловливающих решение этой задачи. В процессе разработки рассматриваемого нами примера выполняются два вида анализа: анализ предметной области и анализ приложения: Анализ предметной области (domain analysis) направлен на изучение конк- ретного класса задач — т.е. области общих интересов (или предметной об- ласти), в которой лежит конкретная задача, представляющая для нас не- посредственный интерес. Что касается игры «Кирпичики», то в фокусе анализа предметной области находится область аркадных игр (игр для иг- ровых автоматов), которая охватывает игры, содержащие схожие компо- ненты, такие как Asteroids или РасМап, или область компьютерных игр, в которую входят карточные или настольные игры, подобные Solitaire или Monopoly. Анализ прикладной области имеет дело в первую очередь с абст- рактными понятиями. В области аркадных игр такими абстракциями явля- ются игроки, спрайты. игровые поля. Анализ предметной области особенно полезен в тех случаях, когда анало- гичные проблемы в той же предметной области придется решать в буду- щем, либо когда основные требования недостаточно четко сформулирова- ны. Результаты анализа предметной области служат отправной точкой для анализа каждого конкретного приложения. Анализ приложения (application analysis) сосредоточивается на конкретной задаче и требованиях к ее решению. Что касается анализа приложения применительно к игре «Кирпичики», то его целью является главным обра- зом сама игра. Анализ приложения имеет дело, прежде всего, с конкрет-
Глава 2. Перспектива тестирования 61 ними понятиями. В игре «Кирпичики» таковыми являются шайбы, лопат- ка и кирпичи. Общие моменты, характерные для тех или иных классов, могут быть выделе- ны путем использования интерфейсов или абстрактных классов, таких как Brick (см. рис. 2.7). Эти общие моменты могут быть представлены на этапе анализа предметной области как абстракции либо синтезированы на базе свойств, общих для двух или большего числа конкретных классов, выявленных на этапе анализа приложения. На этапе тестирования представлений программного обеспечения, построен- ных во время анализа, нет нужды проводить различие между результатами ана- лиза прикладной области и анализа приложений. Это различие проявится в раз- мере области, охватываемой моделью (модели прикладной области отличаются особой протяженностью) и в уровне полноты проводимого тестирования (анали- тические модели прикладной области содержат меньше различного рода дета- лей). Объектно-ориентированный анализ сосредоточивается на том, что делает сис- тема с позиций перспективы видов используемых объектов, и на том, в каких отношениях эти объекты находятся между собой. Анализ предусматривает клас- сификацию объектов в задаче, включая идентификацию соответствующих атри- бутов и операций, идентификацию отношений между классами и экземплярами различных видов объектов, и определение поведенческих параметров различных видов объектов. Все они представлены в модели, содержащей различные виды диаграмм. ВОПРОС Представляют ли результаты анализа программное обеспечение, если основ- ной целью анализа является задача (или область задачи) и требования, предъявляемые к ее решению, но не само решение? Да, представляет. В проакте объактно-ориентированного программного обеспече- ния должно быть построено представление задачи путем создания необходимых объектов, отображающих субъекты задачи, после чего между этими объектами устанааливаются соотаетстеующиа отношения. Поиск решения начнется после того, как программные объекты обретут способность езаимодействовать в направлении достижения решения. Поскольку праеильноа решение можно получить только с учетом структуры задачи, и в силу того, что эта структура отображается в ана- литических моделях, аналитические модели являются представлениями программ- ного обеспечения. Аналитическая модель представляет систему с точки зрения того, что предпо- лагается сделать. Назначение аналитической модели заключается в том, чтобы разработчики, их заказчики и другие заинтересованные стороны достигали по- нимания задачи и требований к ее решению. Обычно по результатам анализа производится пересмотр спецификации требований, которые были сформулиро- ваны, во-первых, с точки зрения разработчиков и не учитывали требований маркетинга, и во-вторых, на базе модели решаемой задачи, описанной в терми-
Глава 2. Перспектива тестирования нах объектов. Чтобы представить систему в различных ракурсах, используются всевозможные виды диаграмм. Если рассматривать диаграммы с позиций перс- пективы тестирования, то те из ник, которые моделируют систему, могут содер- жать неправильную информацию, либо не во всех диаграммах одна и та же ин- формация может быть представлена правильно, либо такая модель не полностью отображает всю необходимую информацию. В главе 4 будут обсуждаться пробле- мы тестирования таких представлений. Сейчас мы опишем некоторые из диаг- рамм, призванных служить основой для разработки и тестирования. Диаграммы случаев использования При разработке объектно-ориентированного программного обеспечения эф- фективное формулирование требований достигается за счет применения набора специальных случаев использования. В описании игры «Кирпичики» из главы 1 компоненты и правила игры описываются естественным языком и представляют- ся в виде соответствующих иллюстраций. Это описание представляет собой дос- таточно полное определение требуемого программного обеспечения, хотя оно ос- тавляет некоторые требования открытыми для интерпретации, например, размер игрового поля, тон сообщения с соболезнованиями, может ли игрок «выйти» из игры, если игра приостановлена, размер и скорость перемещения шайбы, на- сколько лопатка чувствительна к перемещению мыши. Случай использования (use case) описывает, как действующий субъект исполь- зует систему, чтобы выполнить ту или иную задачу. Действующие субъекты (или актеры, actars) исполняют различные роли'3, в каких пользователи выступают по отношению к системе, т.е. один человек может использовать систему, выступая в различных ипостасях. Согласно описанию игры «Кирпичики», изложенному в главе 1, в этой игре имеется только один действующий субъект, а именно, иг- рок, который принимает участие в игре. Мы можем постулировать наличие еще одного действующего субъекта, вменив ему в обязанность подбор значений не- которых параметров игры при ее установке на различных типах компьютеров, например, значение скорости перемещения шайбы, размер начального запаса шайб, цвет кирпичей, шайбы, лопатки и игрового поля. Спецификация не пре- дусматривает наличия такого действующего субъекта, однако опытный аналитик всегда рассматривает вопрос необходимости существования в системе некого ад- министративного пользователя. Разумеется, лицо, осуществляющее установку игры «Кирпичики» в той или иной системе, само может быть игроком. (См. [FoSb99] или [JCJO92], в которых проводится анализ случаев использования.) Случаи использования могут описываться с различной степенью абстракции. Рассмотрим, например, некоторые случаи использования игроком игры «Кирпи- чики» на высоком уровне (см. рис. 2.10). 13 Действующий субъект не обязательно должен быть лицом одушевленным. Им может быть, напри- мер, другая программная система.
Глава 2. Пврспактиаа тестирования 63 Ни в одном из случаев использования не указано, как игрок начинает игру, как делает паузу в игре, возобновляет или завершает игру. Фактически, ни в од- ном из них игра «Кирпичики» даже явно не упоминается. Их можно применить для множества других аркадных игр. У них имеется один и тот же действующий субъект (игрок), перед ними поставлена одна и та же задача манипулирования сеансом игры, который является объектом (или классом), выбранным нами для того, чтобы представлять аркадную игру в ее динамике14. Как таковые, мы мо- жем рассматривать их как случаи использования на уровне предметной области. Эти случаи использования на уровне предметной области можно уточнить для игры «Кирпичики» (см. рис. 2.10). Наименование Описание Start (Начало) Игрок начинает сеанс игры. Pause (Пауза) Игрок делает перерыв в игре. Resume (Возобновление) Игрок возобновляет сеанс игры. Stop (Останов) Игрок завершает сеанс игры. Рисунок 2.10. Случаи использования на уровне предметной области для аркадных игр Случаи использования не обязательно охватывают каждое требование. Слу- чаи использования обычно сопровождаются дополнительными описаниями или диаграммами, вбирающими в себя все подробности (такие как, например, требо- вания к рабочим характеристикам), которые не сразу становятся понятными пользователям, и требования, предъявляемые к интерфейсам с подсистемами, которые от пользователей скрыты. Случаи использования упорядочены в иерархию с помощью двух отношений uses (использует) и extends (расширяет). Можно конкретизировать некоторые случаи использования и развернуть их в наборы более специфических случаев использования. Четыре первых случая использования, представленные на рис. 2.11, являются расширением случаев использования, показанных на рис. 2.10. Такая структура помогает упорядочить то, что может повлечь за собой большое количество случаев использования. Поиск конкретного случая использования производится путем обнаружения случая использования более высокого уровня, который охватывает концептуальную область конкретного случая использования. 14 Обычно термин игра используется как для обозначения деятельности, регламентируемой опреде- ленным набором правил, такой как, например, футбол, так и для обозначения конкретного приме- ра деятельности, формулируемой фразой «Мы выиграли первую игру нового сезона». В условиях выполненного анализа мы проводим различие между этими двумя понятиями и представляем пер- вое из них в виде класса Game, а второе — в виде класса Match. Можно не соглашаться с таким подходом и рассматривать класс сам по себе как объект, а каждый экземпляр этого класса трак- товать как то, что мы обозначаем термином матч.
Глава 2. Перспектива тестирования В свою очередь, случай использования более высокого уровня служит указателем на последовательность случаев использования с возрастающей конкретизацией. Наименование Start Brickies (Начать игру «Кирпичики») Pause Brickies (Прервать игру «Кирпичики») Resume Brickies (Возобновить игру «Кирпичики») Stop Brickies (Завершить игру «Кирпичики») Move Paddle (Переместить лопатку) Описание Игрок начинает сеанс игры «Кирпичики», запуская в работу соответствующее приложение для Windows. Игрок прерывает сеанс игры «Кирпичики» нажатием кнопки мыши. Игрок возобновляет сеанс игры «Кирпичики», отпуская нажатую кнопку мыши. Игрок завершает сеанс игры «Кирпичики», выбирая nyi Quit (Выход) из меню File (Файл) Игрок перемещает мышь вправо или влево, чтобы передвинуть лопатку вправо или влево. Рисунок 2.11. Случаи использования для игры «Кирпичики» на уровне приложения Общее для двух случаев использования поведение может быть выделено в от- дельный «функциональный» пример поведения. В результате каждый из случаев использования будет связан с общим случаем использования отношением uses. В рассматриваемом примере игры «Кирпичики» каждый из случаев использова- ния Breaking a Brick (Разлом кирпича) и Hitting a Wall (Попадание в стену) свя- зан отношением uses со случаем использования Move Paddle. Благодаря этому становится возможной инкапсуляция деталей общего поведения, что упрощает сопровождение программ. Случаи использования не являются представлениями программного обеспече- ния. Они представляют требования, которым должно соответствовать программ- ное обеспечение. Следовательно, тестировать случаи использования нельзя, тем не менее, можно выполнять их ревизию. Требования играют важную роль в тес- тировании, поскольку они служат источником случаев использования — в част- ности, требования, предъявляемые к системе, дают начало случаям использова- ния для тестирования системы. В главе 4 мы покажем, как следует начинать тестирование аналитических и проектных моделей, представляющих систему, с помощью случаев использования. Случаи использования, выбранные для тесто- вых моделей, могут быть модифицированы с целью тестирования системы путем контрольного прогона системных программ, о чем пойдет речь в главе 8. В контексте конкретного случая использования можно определить один или большее число сценариев. Сценарий показывает конкретное приложение или конкретный экземпляр случая использования. В частности, случай использова- ния Move paddle может дать начало следующим сценариям: Перемещение лопатки влево с таким расчетом, чтобы ее столкновение с шайбой, которая движется слева направо, произошло в средней трети ло- патки.
Глава 2. Перспектива тестироеения 65 Перемещение лопатки влево с таким расчетом, чтобы ее столкновение с шайбой, которая движется слева направо, произошло в дальней трети ло- патки. Перемещение лопатки влево с таким расчетом, чтобы ее столкновение с шайбой, которая движется слева направо, произошло в ближней трети ло- патки. Эти сценарии применяются к тем объектам, которые получили конкретные значения соответствующих атрибутов, например, определено, в какую часть ло- патки ударяет шайба (см. раздел «Физические основы игры «Кирпичики») и в каком направлении движется шайба. В противоположность этому, случаи ис- пользования ориентированы на использование объектов безотносительно значе- ний атрибутов. Случаи использования обычно формулируются на естественном языке, в то же время возможно употребление случаев использования для графического представления всех видов применения системы. Диаграмма случаев использова- ния, показанных на рис. 2.11, представлена на рис. 2.12. Диаграммы классов Диаграмма классов представляет статическую картину множества классов и отношений между классами. На диаграмме класса обычно показаны операции и атрибуты каждого класса, а также ограничения, накладываемые на отношения между объектами. Рисунок 2.13. служит графической иллюстрацией аналитичес- кой модели игры «Кирпичики», представленной на языке UML. Игрок Рисунок 2.12. Случаи использования для игры «Кирпичики»
66 Глава 2. Перспектива тестирования Рисунок 2.13. Диаграмма класса на стадии прикладного анализа для игры «Кирпичики»
Глввв 2. Перспектива тестирования Вполне возможно, что на этой диаграмме вы ожидали увидеть класс Mouse. Мы решили не показывать его, поскольку мышь — всего лишь механизм, при помощи которого осуществляется перемещение лопатки. Помимо мыши, можно воспользоваться любым другим указательным устройством ввода, даже клавиша- ми стрелок на клавиатуре. Мы предпочитаем рассматривать подобного рода воп- росы на стадии проектирования. В рамках этого проекта Sprite, MovableSprite (перемещающийся спрайт) и StationarySprite (неподвижный спрайт) суть абстрактные классы; их имена выде- лены на диаграмме курсивом. Спрайт — это графический компонент аркадной игры.15 Движущийся спрайт — это предмет, который перемещается в игровом поле в произвольном направлении, например, когда в условиях игры «Кирпичи- ки» лопатка ударяет по шайбе или шайба ударяет о различные кирпичи. Эти аб- страктные классы появились в результате анализа проблемной области аркадных игр и были включены в эту модель. Уже на начальной стадии мы рассматривали возможность реализации таких аркадных игр, поэтому мы не пожалели времени на выбор соответствующих абстракций. Даже если мы не начали с анализа пред- метной области, мы бы, по всей видимости, обратили внимание на подобие операций и атрибутов, связанных с шайбами, лопатками и кирпичами, и в кон- це концов ввели бы в употребление эти абстрактные классы, даже если по тем или иным причинам отказались бы от термина «спрайт», получившего широкое распространение в среде разработчиков компьютерных игр. На практике диаграммы классов могут достигать больших размеров. Группы классов могут представляться в виде пакетов. В языке Java для этой цели имеет- ся специальная конструкция package, в то время как в языке C++ с этой целью используется синтаксическая конструкция namespace. Представленная на рис. 2.14 диаграмма пакетов для игры «Кирпичики» содержит три пакета. Классы прикладной области заключены в пакет game.domain, так что ими легко можно воспользоваться в другой компьютерной игре в будущем. Пунктирные стрелки показывают, что классы, специфичные для игры «Кирпичики», зависят от клас- сов-контейнеров, сгруппированных в пакет game.containers. Рисунок 2.14. Диаграмма пакетов игры «Кирпичики» 15 Этот термин появился в эпоху разработки первых компьютерных игр для Atari. 3*
68 Глава 2. Перспектива тестирования ДИАГРАММЫ КЛАССОВ НА ЯЗЫКЕ UML - ЭТО ЭЛЕМЕНТАРНЫЕ КОМПОНЕНТЫ Прямоугольник класса разбит на три раздела. Конкретная диаграмма не обязатель- но должна состоять из трех разделов, если при этом достигается большая четкость всей диаграммы. Абстрактные классы и объекты на диаграмме выделяются курси- вом. Имя класса Атрибуты данных Сигнатуры операций Соединительные линии между классами представляют отношения. Существуют три базовых отношения, каждое из которых обозначается при помощи различных концов линий. Понятие Символ Ассоциация - равноправная видимость 1..П 1 Агрегирование - один объект является частью другого о Наследование - одно определение используется в качестве основы для другого > Цифры, числа и символы означают номера экземпляров каждого класса, вовлека- емого в отношение. Предыдущая ассоциация устанавливает, что один экземпляр класса, указанного слева, объединен конкретным отношением с одним или большим числом экземпляров класса, указанного справа. Видимость атрибутов отражается с использованием префиксов: Префикс Видимость + public (общедоступный) - видимый всем ассоциированным объектам # protected (защищенный) - видимый только для методов классов, связанных отношением наследования - private (приватный) - видимый только в рамках текущего класса Диаграммы состояний Диаграмма состояний описывает поведение объектов класса в терминах на- блюдаемых состояний этого объекта и то, как объект меняет свои состояния в результате событий, оказывающих воздействие на объект. На рис. 2.19 показаны два примера диаграмм. Состояние (state) — это особая конфигурация значений атрибутов данных. То, что мы наблюдаем в состоянии, есть различие в поведе- нии при переходе из одного состояния в другое. Иначе говоря, если одно то же сообщение передавать на объект дважды, то он каждый раз может вести себя по- разному, в зависимости от того состояния, в каком он находился в момент по-
Глава 2. Перспактива тестирования лучения этого сообщения. Переход (transition) из одного состояния в другое, т.е. изменение состояния, вызывается событием, которым обычно бывает появ- ление сообщения. Ограничение (gnard) — это условие, которое должно быть удовлетворено, прежде чем переход станет возможным. Ограничения полезны для представления переходов в различные состояния под воздействием одного и того же события. Действие (action) обозначает обработку, которая должна быть выполнена во время перехода. Каждое состояние может определять процесс, ко- торый должно быть выполнен, когда объект находится в этом состоянии. Экземпляр класса Puck может находиться либо в состоянии в игре, либо в со- стоянии вне игры. Если экземпляр находится в игре, то он либо перемещается, либо не перемещается. Наблюдаемое состояние шайбы In play (в игре) имеет два подсостояния Moving (перемещается) и Not Moving (не перемещается). In play — это суперсостояние. Moving и Not Moving — подсостояния. Подсосто- яние наследует все переходы, входя и выходя из своего суперсостояния. На диаграмме параллельных состояний можно отразить группу состояний, ко- торые представляют поведение объекта в перспективе двух или большего числа независимых возможностей. Параллельные состояния будут рассматриваться ниже, при изучении реализации игры «Кирпичики» в среде Java. В перспективе тестирования подобного рода диаграммы можно рассматривать как диаграмму непараллельных состояний, если сначала дать определения состояний, которые определены в виде комбинаций состояний различных параллельных частей, с последующим определением соответствующих переходов. Спецификации классов Диаграммы классов определяют классы и показывают атрибуты и операции, ассоциированные с экземплярами этих классов. Диаграммы состояний служат иллюстрацией поведения экземпляра класса. В то же время ни на одной из ди- аграмм не показаны детали семантики, связанные с каждой операцией. Для по- добных спецификаций мы будем прибегать к языку OCL (Object Constraint Language — язык объектных ограничений) [WK99], Ограничения OCL выражают- ся в контексте диаграмм классов. Ограничения касаются атрибутов, операций и ассоциаций, определенных на этой диаграмме. Как показывает пример, представленный на рис. 2.19, язык OCL выражает семантику операций в терминах предусловий и постусловий. Инвариантные ус- ловия можно устанавливать для класса или интерфейса и они должны выпол- няться как в момент поступления запроса на выполнение какой-либо операции, представленного в виде сообщения, так и по завершении выполнения запраши- ваемой операции. (Допускается, чтобы метод, реализующий операцию, нарушал на время выполнения операции существующие инвариантные условия.) Усло- вия — это вычисляемые булевские выражения и они привязаны к диаграмме классов. Ограничения, отображенные на рис. 2.15, используют атрибут size запа- са шайб и переменную ассоциацию шайб, содержащую от одной до трех шайб, показанную на проектной диаграмме класса (см. рис. 2.18).
Глава 2. Парспвктива тестирования ДИАГРАММЫ СОСТОЯНИЙ - ИТОГОВАЯ ТАБЛИЦА ЯЗЫКА UML В диаграмма состояний конкрвтнов состояниа првдстввлявтся овалом, а переход — дугой, проведенной из одного состояния к другому. У каждой дуги имеется метка, состоящая из трах частей, причем любая из этих чвствй можвт отсутствовать. Event [guard] / action Понятие Обозначение Состояние - конфигурация значений данных Переход - следующее допустимое состояние ► Подсостояние/суперсостояние Параллельное состояние Обозначение puck-> в ограничениях для операции get() означает необходи- мость следовать вдоль связи puck по направлению к набору ассоциированных объектов. Использование атрибута size в ограничении никоим образом не требу- ет от реализации класса, чтобы она использовала переменную с именем size. Оно всего лишь означает, что реализация требует представления этого атрибута в том или ином виде, т.е. либо как переменную, либо в форме алгоритма, кото- рый вычисляет требуемое значение, используя другие атрибуты. Спецификации операций не должны часто предписывать реализацию. Синтаксис языка OCL слишком детализирован и не особенно подходит для изложения в виде какой-то таблицы. (Подробное описание этого языка можно найти в [WK99].) Возможно, вы отдаете предпочтение более неформальной нотации, например, такой, как показанная на рис. 2.15. Для целей отладки требуется более доброт- ная спецификация каждой операции. Если разработчики не подготовили таких спецификаций, то, по нашему мнению, эту задачу должны возложить на себя тестировщики. Практически невозможно тестировать программы, цели которых неопределенны и неоднозначны. Следовательно, наличие таких спецификаций не только существенно упрощает тестирование, но и повышает качество про- граммного обеспечения и, по-видимому, содействует последующему повторному использованию классов.
Глава 2. Перспектива тестирования 71 PuckSupply size >= О AND size <= 3 PuckSupply::PuckSupply() pre: - none post: size = 3 AND pucks->forAll ( puck: Puck | not puck. inPlay () ) PuckSupply::"PuckSupply() pre: - none post: Puck->size() = Puck@pre->size() - size@pre void PuckSupply::size() const pre: - none post: result = size Puck * PuckSupply::get() pre: count > 0 post: result = pucks->asSequence->first AND size = size@pre - 1 Рисунок 2.15. Семантика операций класса PuckSupply на языке OCL Диаграммы последовательностей сообщений Алгоритм может быть описан как взаимодействие между объектами. Диаграм- ма последовательностей сообщений отображает обмен сообщениями между объек- тами, создание объектов и ответы на сообщения.16 На стадии анализа диаграммы последовательностей используются для иллюстрации процесса, протекающего в предметной области, т.е., как в общем случае решаются задачи благодаря взаи- модействию различных объектов в рамках сценария. Пример диаграммы после- довательностей сообщений показан на рис. 2.16. В диаграмме последовательнос- тей сообщений объект представляется в виде прямоугольника, а его жизненный цикл обозначается направленной сверху вниз пунктирной линией. Отсчет вре- мени на диаграмме ведется сверху вниз. Объект, изображенный в верхней части диаграммы, существует в начале процесса обработки. Объекты, показанные ниже, создаются в соответствующие моменты времени. Сообщение представлено на диаграмме в виде стрелки с заштрихованным наконечником. Ответ на сооб- щение представлен в виде стрелки с незаштрихованным наконечником. Расши- рение линии жизненного цикла отражает активизацию процесса, когда в теку- щую последовательность вовлекается еще одна из операций объекта. Диаграмма последовательностей сообщений может быть построена на любом уровне абстракции. Сценарий случая использования можно представить на диаг- рамме последовательностей сообщений. Алгоритм отдельно взятого метода клас- са также можно представить в такой нотации. На рис. 2.16 показана диаграмма последовательностей сообщений, описывающая выигранный сеанс игры «Кирпи- чики». 16 Язык UML дает определение диаграмм взаимодействия, которые отображают ту же информацию, но при этом предпочтение отдается структуре ассоциаций, а не последовательностям. В этой кни- ге мы пользуемся диаграммами последовательностей; тем не менее, диаграммы взаимодействия не- сомненно полезны, особенно в тех случаях, когда требуется явно отобразить взаимодействие объектов.
Рисунок 2.1 В. Диаграмма последовательностей сообщений для игры «Кирпичики».
Глава 2. Перспектива тастирования 73 Диаграммы последовательностей сообщений способны также отображать па- раллельные процессы. Асинхронное сообщение обозначается стрелкой с полови- ной наконечника. Для создания нового объекта, нового потока или для обмена данными с текущим потоком можно использовать асинхронные сообщения. СОВЕТ Определите опарации срадства доступа (инспектора], которые выдают наблюдае- мые состояния получателя. Спецификация на языке OCL, показанная на рис. 2.15, передает ту жв информа- цию, что и приводимая ниже диаграмма класса PuckSupply. Спецификация на OCL яалявтся болев полной, в то жв время диаграмма состояний лвгча воспри- нимается большинством специалистов. Некоторые из предусловий для опвратороа представлены на диаграмме состояний неявно, в то время как опрадалания состояний даны неявно в спецификациях на OCL. Например, в контекста диаграммы состояний операция get() допускается только в тах случаях, когда объект Supply (запас шайб) находится в состоянии Not Empty (не пуст), поскольку ни один из переходов из состояния Empty (пуст) на был помечен событием get(). Это предусловие отражено в OCL-спецификации как ограниченна на атрибут count (число шайб), ассоциированный с классом PuckSupply, при этом состояние Not Empty вообще нв упоминается. Мы предпочитаем проектировать классы таким образом, чтобы состояния были явно представлены в интерфейсе класса. Благодаря этому спецификация стано- вится болва интуитивной, что до некоторой степени облагчаат проверку предусло- вий отправителем и двпает ее болев надежной, а это, в свою очередь, упрощает задачу твстироаания. Что касается класса PuckSupply, то мы можам ввести в интерфейс специальную операцию запроса isEmpty(), возвращающую булевское значение, и выразить предусловие чераз эту операцию, запрашивающую данные с состоянии. Скорректированная таким образом OCL-спацификация для класса PuckSupply принимает вид: PuckSupply count >= О PuckSupply::PuckSupply(); pre: -- - none post: count = 3 AND pucks->asSet->size = 3 AND pucks->forAll( puck: Puck | not puck.inPlay() PuckSupply::-PuckSupply(); pre: -- none post: pucks@pre->isEmpty void PuckSupply::size() const; pre: -- none post: result = count bool PuckSupply::isEmpty() const; pre: -- none post: result = ( count = 0 ) Puck pre: post: AND count = count@pre - 1 * PuckSupply:: get () ; not self.isEmpty() result = pucks->asSequence->first Разумеется, диаграмма состояний также должна быть скорректирована с учетом новой операции.
74 Глава 2. Перспектива тестирования Операция средства доступа, возвращающая информацию о состоянии, до некото- рой степени облегчает тестирование (см. главу 5) и позволяет устанавлиаать предусловия проверки. Включение таких операций в интерфейс класса можат слу- жить примером проектирования с учетом потребностей тестирования. ДИАГРАММЫ ПОСЛЕДОВАТЕЛЬНОСТЕЙ СООБЩЕНИЙ В ТЕСТИРОВАНИИ Диагреммы последоватальностей сообщений и диаграммы взаимодействия приносят ощутимую пользу на этапа анализа и особенно на стадии проектирования. Мы широко их используем в тестовых моделях для того, чтобы получить результаты «выполнения» контрольного примера. Приводимая ниже диаграмма отображает объекты и их состояния при вводе данных для тестового случая и отображает последоватальность сообщений, которые приводят к правильным выходным данным. Рисунок 2.17. Диаграмма действий для метода move() класса Риск Диаграммы действий Диаграммы последовательностей сообщений позволяют вычленять отдельные следы из множества взаимодействий объектов. Трудно, а иногда даже и невоз- можно представить на этих диаграммах циклы и параллелизм. Диаграмма дей- ствий дает более развернутое представление, в котором используется комбина- ция нотаций блок-схем и сетей Петри. Диаграмма действий, показанная на рис. 2.17, реализована для метода move() класса Puck.
Глава 2. Перспектива тестирования 75 ДИАГРАММЫ ДЕЙСТВИЙ - ОБОБЩЕННОЕ ПРЕДСТАВЛЕНИЕ В UML В диаграмме действий вертикальные линии образуют своеобразные «водные дорож- ки». Каждая твкая дорожке представляет собой пространство для маневра для объекте, имя которого укезено в его вершине. В рессматриеаемом случае каждый объект не имеет имени, на что указывеет двоеточие перед именем класса. Горизон- тальная полосе обозначеет точку синхронизации, в которой должны встретиться два потоке. Фигурный прямоугольники «выдачи сигнала» и «захвете сигнала» обознача- ют генерацию и захвет (try и catch) исключений. Ромбом на диаграмме предстев- лен блок принятия решения в прогреммном коде. Понятие Обозначение Понятие Обозначение Блок принятия решений Завершение потока <^Condition Выдача сигнала propertyVeto X Exception / Обработка — Q>ropertyChange(Q Захват сигнала \ propertyVeto / Exception Точка синхронизации •
Глава 2. Перспектиее тестирования Проектные модели Проектная модель показывает, в какой степени программное обеспечение со- ответствует предъявляемым требованиям. Главное достоинство принципа объек- тно-ориентированного программирования заключается в том, что проектная мо- дель представляет собой уточнение и расширение аналитической модели. С позиций перспективы тестирования — это хорошая новость, ибо это означает, что мы можем многократно использовать и расширять контрольные примеры, разработанные для аналитических моделей. В процессе проектирования исполь- зуется значительное количество таких диаграмм, но по большей части они ори- ентированы на решение, а не на задачу. Таким образом, эти диаграммы отобража- ют объекты, фигурирующие как на уровне решения, так и на уровне задачи. Поскольку обозначения остаются теми же, что и описанные выше, постараемся вникнуть в суть представленной проектной информации. Диаграммы классов Диаграммы классов используются в проекте с целью описания видов объек- тов, которые будут создаваться программным обеспечением. У каждого класса, представленного на такой диаграмме, имеются имя, атрибуты и операции, при этом каждый класс связан отношениями с другими классами, показанными на диаграмме. Мы надеемся увидеть на проектной диаграмме класса большую часть классов и отношений, обображенных в аналитических моделях, а также классы, экземпляры которых способствуют решению задачи. Некоторые классы исчезнут с диаграммы, поскольку они не играют никакой роли в процессе решения зада- чи. Другие классы, скорее всего, получат дополнительные атрибуты и вступят в отношения, введенными для них классами и объектами, фигурирующими на уровне решения. Суть добротного объектно-ориентированного проектирования может быть представлена диаграммой классов, которая отображает основные осо- бенности задачи (как ее отображает аналитическая диаграмма классов) и повы- шает роль программной версии объектов в задаче, которые благодаря взаимодей- ствию друг с другом и приводят к решению задачи. Диаграмма классов на стадии проекта для игры «Кирпичики» показана на рис. 2.18. Обратите внимание на то, что введение классов уровня реализации, таких как класс Mouse, представляющий мышь, которой снабжен компьютер, и класс Hint, представляющих объект, который нужен для отслеживания событий во время выполнения программы, приводит к необходимости модификации со- держимого экрана. На этой диаграмме показаны некоторые классы, такие как CMainFrame, CView и CDocument, из библиотеки базовых классов MFC (Microsoft Foundation Class) [MFC], погружающие игру «Кирпичики» в среду Windows в соответствии с требованиями. Стрелка с незаштрихованным наконеч- ником в некоторых ассоциациях указывает на перемещаемость, т.е. выбор на- правления, с учетом которого ассоциации фактически должны быть реализова- ны. Ассоциации могут быть двунаправленными и однонаправленными.
Глава 2. Парспектива тестирования 77 Рисунок 2.18. Диаграмма классов для игры «Кирпичики» на стадии проектирования
78 Глааа 2. Парспектива тастирования Стрелки указывают, какие объекты знают о тех или иных отношениях. Мы не часто указываем перемещаемость в диаграммах классов на этапе анализа, од- нако находим их исключительно полезными в диаграммах классов на стадии проектирования. В диаграммах последовательностей обмен сообщений между объектами может иметь место в направлении перемещаемой ассоциации. Диаграммы состояний Диаграммы состояний, используемые на стадии проектирования, те же, что и на стадии анализа. Основное отличие между ними обусловливается появлением в диаграмме классов на стадии проектирования диаграмм состояний новых клас- сов и, потенциально, появлением новых подсостояний, которые могут оказаться полезными на этапе реализации. Диаграммы на стадии проектирования могут отображать больше действий, связанных с переходами и большее число процес- сов, связанных с состояниями. В примере с игрой «Кирпичики» нужны специ- альные механизмы для управления движением шайбы и лопатки. Мы останови- ли свой выбор на таймере событий, предоставляемой системой Windows из библиотеки MFC, чтобы сделать выполнение программы независимым от скоро- сти процессора. В связи с этим мы вводим на стадии проектирования класс Timer (см. рис. 2.18), который выполняет обработку событий, запланированных таймером, и манипулирует соответствующими спрайтами во время игры. Диаг- рамма состояний класса Timer показана на рис. 2.19. Таймер ведет список на- блюдателей, другими словами, перечень объектов, заинтересованных в получе- нии уведомлений всякий раз, когда таймер фиксирует событие. Когда таймер запущен в работу, он уведомляет каждого из закрепленных за ним наблюдателей о том, что произошло событие таймера, при помощи сообщения notify().
Глава 2. Перспектива тестирования 79 TimerObserver — это абстрактный класс (см. рис. 2.18), который представляет наблюдателей. Полиморфизм включения позволяет осуществить присоединение любого экземпляра подкласса класса TimerObserver и выдать соответствующее уведомление. В основу этого фрагмента реализации положен проектный шаблон Observer [GHJV94], Что касается перспективы тестирования, то у нас возникает необходимость подготовить контрольные примеры для класса, который адекватно тестирует пе- реходы из одного состояния в другое и обеспечивает корректную обработку со- общений в каждом состоянии. Возможно, потребуется проверить, правильно ли шаблон Observer включен в структуры классов Timer и TimerObserver. Может оказаться возможным многократное использование некоторых контрольных при- меров и тестовых драйверов, разработанных с целью тестирования других клас- сов, структура которых основана на том же шаблоне. Диаграммы последовательностей сообщений Диаграммы последовательностей сообщений используется на стадии проекти- рования для описания алгоритмов — т.е. того, какие объекты участвуют обра- ботке данных с целью нахождения того или иного аспекта решения и как эти объекты взаимодействуют между собой, дабы оказать влияние на эту обработку. Основное отличие от использования этих диаграмм на этапе анализа заключает- ся в наличии в проектных диаграммах объектов уровня решения. Диаграмма последовательностей сообщений начальной стадии игры «Кирпичики» показана на рис. 2.20. Она представляет собой алгоритм построения объектов, необходи- мых для запуска очередного сеанса этой игры. В перспективе тестирования воз- можными ошибками являются нарушения контракта, неудача при попытке со- здания объектов требуемого класса и пересылка сообщений, для которых на диаграмме классов не указана перемещаемость между отправителем и получате- лем. Исходные коды Исходные коды программ и сопровождающая их документация являются фи- нальным представлением программного обеспечения. Транслятор (компилятор или интерпретатор) превращает исходный программный код в выполняемый. Предполагается, что исходный программный код представляет собой точный перевод подробных проектных моделей на язык программирования, тем не ме- нее, мы обязаны тщательно проверить это предположение. В объектно-ориенти- рованных системах программный код содержит определения классов, а также сегмент, который создает экземпляры некоторого класса (классов) и производит запуск программ в работу — например, функция main() в C++ или статический (static) метод main() в Java. Каждый класс использует экземпляры других клас- сов с тем, чтобы осуществить какую-то часть своей реализации. Эти экземпляры вместе с параметрами сообщений образуют существенную часть отношений меж- ду объектами.
Рисунок 2.20. Диаграмма последовательностей начальной стадии сеанса игры «Кирпичики». Главе 2. Перспективе тестирования
Глава 2. Перспектива тестирования 81 Тестирование фактических программных кодов было основной задачей боль- шинства традиционных методов тестирования и является главной темой боль- шей части глав данной книги. Исходные программные коды можно подвергать тестированию либо по мере их разработки, компонент за компонентом, либо как завершенный программный продукт по окончании стадии разработки. При этом приходится решать следующие основные проблемы: Кто проводит тестирование. Тестирование может выполняться разработчи- ками, которые приспосабливают перспективу тестирования применительно к своим программам. Каждый сеанс тестирования должен быть кем-то проверен на полноту и соответствие. Что подвергается тестированию. Каждый класс можно тестировать отдель- но от других, прежде чем он будет использован как часть системы. Однако некоторые классы настолько сложны в реализации, что построение специ- альных тестовых драйверов для их тестирования вне контекста системы требует значительных затрат. Когда следует завершить тестирование. Тестирование можно выполнять многократно и в различные моменты этапа разработки. Естественно, чем раньше, тем лучше, однако тестирование в условиях меняющихся специ- фикаций может оказаться неэффективным и даже деморализующим. Ранее тестирование может повлечь за собой необходимость увеличения объемов регрессионного тестирования, а это также требует затрат ресурсов. В главе 4 мы обсудим, как следует тестировать аналитические и проектные модели до того, как будут готовы рабочие программы. Как проводить тестирование. В главе 1 мы провели анализ методов тести- рования, в основу которых были положены функции и спецификации. Чтобы тестирование было эффективным, следует прибегнуть к обоим рас- смотренным методам. Какой необходим объем тестирования. Исчерпывающее тестирование каж- дого компонента программного обеспечения и системы в целом редко оп- равдывает себя на практике и вряд ли вообще возможно. Должны быть определены критерии адекватного тестирования, а затем внедрены на практике. Основой такой адекватности часто служат охват программ и ох- ват спецификаций. Все эти вопросы будут подробно рассмотрены применительно к планирова- нию тестирования в главе 3.
Глава 2. Перспектива тастирования ВОПРОС Влияет ли каким-либо образом на тестирование выбор языка программирования? Выбор языка программирования для реализации заметно отражается на тестиро- вании. Некоторые языки программирования допускают возможность возникнове- ния одних видов ошибок и устраняют такую возможность для других видов оши- бок. Например, C++ является строго типизированным, благодаря чему снижается число возможных ошибок в интерфейсах, поскольку компилятор C++ гарантирует соответствие типов формальных и фактических параметров. В Java механизм типов также хорошо развит, но он обладает большей динамикой, нежели C++, в связи с чем его компиляторы менее эффективны при выявлении проблем, обус- ловленных, напримвр, циклическими ссылками. Механизм типов языка Smalltalk менее строг, так что потрабуется больше усилий, чтобы убедиться, что проекти- рование и реализация не таят в саба интерфейсные ошибки, а частности, непра- вильно выбранные типы фактических параметров. С другой стороны, в C++ сохра- няется возможность наличия е программах ошибок, связанных с применением указателей, например, висячиа ссылки и мусор, что традиционно характерно для языка С. Такиа языки программирования, как Java и Smalltalk, е которых реали- зован механизм сборки мусора, устраняют подобного рода ошибки, связанные с применением указателей. C++ поддерживает понятие дружественности, которое позволяет некоторым частям программы обходить механизм сокрытия данных. Выполняемый тестовый код можат быть объявлен дружественным по отношению к тестируемому классу, и это обсто- ятельство обаспечит тестовому коду доступ к реализации, благодаря чему сам тестовый код сократится, хотя это можат послужить причиной возникновения еще одной проблемы, поскольку тастирование станет слишком тесно связанным с реализацией. Java поддерживаат интерфейсы. Нужно ли их тестировать? Если предположить, что некоторый класс реализуат интерфейс, то следует провести тестирование, чтобы получить достаточно веское подтверждение того, что семантика каждой операции интерфейса в полном объема поддерживается самим классом. Тестировщики должны будут знать, требует ли интерфейс точной семантики и может ли класс соответствовать своему назначению, если ослабить прадусловия и/или ужесто- чить постусловия. Помните, что инвариантами класса являются неявными пред- и постусловиями (см. раздал «Производные классы и подтипы»]. ВОПРОС Мы заметили, что некоторые инструментальные средства CASE - например, Rational Rose, могут генерировать программные коды на базе проектных моделей. Как это отражается на тестировании? Исходя из предположения, что средства генерации программных кодов работают корректно, мы можем отметить два основных проявления этого влияния: 1. Большую часть тестирования требуется выполнять в контексте проектной мо- дели. 2. Применение подхода, ориентированного на тестирование реализаций, возможно, с целью проварки адекватности тестирования, требует, чтобы тестировщик по- нимал структуру полученной программы. В этом ему могут оказать помощь инструментальные средства построения профиля программы, однако при этом кто-то должен уметь читать полученные программные коды. Если программистам разрешается вносить вручную изменения в генерируемый исходный программный код, то тестирование и, соответственно, сопровождение, существенно затрудняется.
Глава 2. Парспектива тестирования 83 Резюме Мы сделали обзор базовых концепций объектно-ориентированного програм- мирования. Были проанализированы некоторые виды документации, которые создаются во время разработки и которые играют важную роль при тестирова- нии. Мы рассмотрели эти вопросы с позиций перспективы тестирования, т.е. выяснили, какие могут возникнуть отказы вследствие использования различных концепций объектно-ориентированного программирования. В следующей главе будет проведено исследование самого процесса тестирова- ния. Упражнения 2-1 Назовите какие-либо объектно-ориентированные программные продукты, которые вы можете использовать при апробации различных методов и решении проблемы, обсужденных выше. В идеальном случае такой программный продукт должен ре- ализовывать завершенное приложение, однако можно ограничиться несколькими взаимодействующими классами. Соберите все документы, которые имеют отноше- ние к этому программному продукту на стадиях анализа и проектирования. Если существует спецификация для каждого класса, то убедитесь в том, что они напи- саны правильно, т.е. содержат полное и однозначное описание каждой операции. Если такие спецификации не существуют, напишите их самостоятельно. Мы реко- мендуем воспользоваться языком OCL (описание находится в [WK.99]). 2-2 Еще раз изучите диаграммы, имеющие отношение к игре «Кирпичики» и приведен- ные в данной главе, и постарайтесь их понять. 2-3 Продумайте свой подход к тестированию класса PuckSupply в соответствии с ма- териалом, изложенным в настоящей главе. Зависит ли тестирование этого класса от корректности класса Puck? Нужно ли сначала тестировать класс Puck?
Глава Планирование работ по тестированию ► Вы хотите состааить план процесса, зааершающего выполняемый аами процесс разработки? См. раздел «Процесс тестирования». ► Вы хотите проаести анализ рискоа, связанных с оценкой необходимых функциональных средста? См. раздел «Анализ рискоа - инструментальные средстаа, предназначенные для тестироаания». ► Возникла необходимость разработки планоа работ по тастироаанию для различных уровней и видоа тестовых процедур, обеспечиаающих всесторонний процесс тестирования? См. раздел «Планы аыполнения различных аидоа деятельности». Для проведения тестирования требуются значительные ресурсы. Эффектив- ность использования этих ресурсов требует четкого планирования и эффектив- ного управления. В этой главе основное внимание будет уделяться техническим аспектам планирования и составлению графиков выполнения различных видов деятельности, связанных с тестированием. Мы займемся определением того, что необходимо сделать на техническом уровне, кто может это сделать и когда это должно быть сделано. Мы наметим способ получения оценок, и в то же время не будем вдаваться в подробности построения конкретных временных графиков. Планирование на техническом уровне выполняется по соответствующим шабло- нам, которые создаются по мере появления такой необходимости у разработчи- ков. Мы дадим описание иерархии планов тестирования и соотнесем их со стандартными шаблонами, используя в качестве примера стандартный план тес- тирования, рекомендованный институтом IEEE. Мы также обсудим включение анализа рисков в процесс планирования работ по тестированию. Основу процесса тестирования кратко можно сформулировать следующим об- разом: тестировать рано, тестировать часто, тестировать в нужном объеме. Мы дадим определение более детализированного процесса, в котором для каждого этапа разработки предусматривается этап тестирования (Анализ. Тестирование. Разработка. Тестирование. Кодирование. Тестирование). Мы также рассмотрим 84
Глеве 3. Пленированиа ребот по тастировению 85 обобщенный набор этапов, в котором определим базовые задачи, выполняемые на каждом из таких этапов разработки. Кроме того, мы обсудим тестирование с точки зрения управления/распределения ресурсов, опишем различные аспекты тестирования и расскажем, как добиться компромисса между этими аспектами. Обзор процесса разработки Любой процесс представляет собой непрерывную последовательность дей- ствий, приближающую вас к завершению. Большинство книг, посвященных вопросам проектирования программного обеспечения, а также сами проектиров- щики программного обеспечения выделяют четыре основных вида деятельности в рамках процесса разработки программного обеспечения (после завершения ин- женерных проработок и до первого развертывания программного продукта): Анализ — на этой стадии основное внимание уделяется пониманию про- блемы и формулированию требований к различным компонентам про- граммного обеспечения системы Проектирование — на этой стадии основное внимание уделяется решению проблем в программном обеспечении Реализация — на этой стадии основное внимание уделяется переложению проекта в программные коды Тестирование — на этой стадии основное внимание уделяется достижению того, чтобы в ответ на входные данные программный продукт выдавал ре- зультаты, соответствующие установленным требованиям. Сопровождение начинается после установки программного продукта, при этом основные усилия направлены на внесение различных исправлений и рас- ширений. Сопровождение обычно предусматривает дальнейший анализ, проек- тирование, реализацию и тестирование. В число видов тестирования, которые применяются на этапе сопровождения, входит регрессионное тестирование, ус- пешный исход которого означает, что после внесения изменений получены те же результаты тестирования, что и до внесения изменений. Перечисленные виды деятельности могут быть сведены к более конкретным задачам. Анализ в некоторых случаях подразделяется на анализ предметной об- ласти, в задачу которого входит понимание задачи в более общем контексте, и анализ приложения, назначение которого состоит в понимании специфики зада- чи, решаемой на стадии проектирования. Проектирование включает архитектур- ные проектные решения, проектирование подсистем и пакетов программ, проек- тирование классов и построение алгоритмов. Реализация предусматривает реализацию и интегрирование классов. Тестирование включает проверку базовых модулей, интегрированных модулей, систем и подсистем. Многие проекты по разработке программного обеспечения проводятся в соот- ветствии с моделью эволюционного процесса — инкрементальная модель, спи- ральная модель или параллельное проектирование. Основное внимание будет уделяться модели инкрементального процесса.
Глааа 3. Планироааниа работ по тестированию В условиях инкрементального процесса система разрабатывается как последо- вательность приращений. Приращение представляет собой комплектующий мо- дуль, которым может быть модель, документация и программный код, реализую- щий ту или иную часть функциональных возможностей, требуемых для системы. Продукты, разработанные за одно приращение, служат основой для разработки комплектующего модуля. Последовательные включения комплектую- щих модулей приводят к наращиванию (а иногда и к изменению) функциональ- ных возможностей системы. Завершающее приращение позволяет получить раз- вернутую систему, соответствующую всем предъявляемым к ней требованиям. Комплектующие модули можно вносить последовательно, однако возможна так- же параллельная разработка двух или большего числа таких модулей. Для построения комплектующего модуля разработчики каждый раз должны выполнить анализ, разработку, кодирование и тестирование по мере необходи- мости. Обычно при построении комплектующих модулей все эти виды деятель- ности должны повторяться многократно, поскольку время от времени они нахо- дят ошибки, допущенные на предыдущих этапах работы. По мере продвижения разработок, их представления о задаче и ее решении претерпевают изменения. Мы намеренно выделяем циклический аспект инкрементальной разработки и де- лаем его частью этого процесса. Мы будем называть такой процесс инкремен- тальным, итеративным процессом. При планировании каждого комплектующего модуля мы специально включаем пункты, предусматривающие повторение того или иного вида деятельности. В их число входят этапы систематического анали- за текущих моделей, выявление ошибок на основе накопленного опыта в более поздних задачах и модификация уже готовых моделей, а не только тех, что бу- дут разработаны в будущем. Рисунок 3.1 может послужить иллюстрацией про- цессов, в которых запланировано последовательное включение комплектующих модулей. Рисунок 3.1. Упрощенная диаграмма последовательного, инкрементального и итеративного процесса разработки
Глава 3. Планирование работ по тестированию 87 Для разработки объектно-ориентированных программных продуктов особенно хорошо подходят методы эволюционной разработки, поскольку объектно-ориен- тированный анализ, разработка и реализация последовательно улучшают одну и ту же модель. Это утверждение справедливо как при внесении комплектующих модулей в программный продукт, так и при внесении приращений в сами комп- лектующие модули. На стадии объектно-ориентированного анализа мы получаем представление о задаче, моделируя ее в терминах объектов и классов объектов, их отношений и возлагаемых на них обязанностей. На стадии объектно-ориен- тированного проектирования мы решаем поставленную задачу, манипулируя теми же объектами, отношениями, выявленными на стадии анализа, и вводя специальные классы, объекты, отношения и обязанности, способствующие реше- нию задачи. Реализация непосредственно переводит точно описанный набор продуктов проектирования в программные коды. Таким образом, весь процесс разработки программного продукта предполагает последовательное совершен- ствование и уточнение модели. Результаты разработки в основном представляют собой расширение результатов анализа, а результаты реализации суть результаты разработки, представленные в программных кодах. Результаты конкретного при- ращения претерпевают расширение и уточнение при следующем приращении. Эта особенность с точки зрения тестирования является сильной стороной выб- ранного метода разработки, поскольку при тестировании усовершенствованных моделей можно ограничиться уточнением применяемых ранее контрольных при- меров, а не изобретать новые. Инкрементальная разработка программных продуктов требует инкременталь- ного тестирования этих продуктов. Программные продукты могут меняться с каждым новым комплектующим модулем, при этом изменения могут быть как запланированными, так и незапланированными. В соответствии с этим должны меняться тестовые наборы. В промежутках между приращениями, а также в рам- ках итераций должно проводится возвратное тестирование с тем, чтобы измене- ния не затронули корректно функционирующий программный код. Процесс, в условиях которого работа над одним комплектующим модулем частично совпа- дает с работой над другим таким модулем, существенно усложняет разработку и тестирование. Возникает необходимость координации последовательности вклю- чения взаимодействующих модулей с тем, чтобы ассоциированные с одним ком- плектующим модулем, но принадлежащие другому модулю, объекты подверга- лись тестированию в правильной временной последовательности. Разработка игры «Кирпичики» проводится в соответствии с планом, учитыва- ющем особенности инкрементального, итеративного процесса разработки. Пер- воначальный план представлен в виде таблицы на рис. 3.2. Начиная разработку, мы обладали четким пониманием требований, однако опыт разработки приложе- ний с использованием классов MFC (Microsoft Foundation Classes) отсутствовал, равно как и какой бы то ни было опыт разработки аркадных игр. Мы пришли к выводу, что успех проекта, прежде всего, зависит от того, насколь успешно бу- дут преодолены упомянутые выше трудности, в связи с чем решение этих про-
88 Глава 3. Планирование работ по тестированию блем было предусмотрено в планах в первую очередь. Мы также решили, что по мере продвижения работ мы будем выполнять тестирование в максимально воз- можных объемах, что означает, что результаты тестировались во время и/или в конце каждой итерации (это в таблице не показано). Неформальных итераций гораздо больше, нежели отражено в таблице на рис. 3.2. Это особенно заметно на стадии проектирования, когда мы обнаружили, что изрядный объем решений, касающихся объемов работ и поведений, на стадии анализа принят не был. Комплектующий модуль 1. Существующий пользовательский интер- фейс, отображающий отскок шайбы от стенок окна. 2. Перемещение лопатки в окне и обнаружение соударений шай&'с различ- ными предметами. 3. Отображение кучи кирпичей и обнаружение соударений. 4. Пополнение запаса шайб и обнаружение конца сеанса. Итерация 1.А. Анализ прикладной области: Построить диаграмму класса. 1.В. Анализ приложения: Построить диаграмму класса и диаграммы состояний. 1 .С. Проектирование: Изучение классов MFC и анимации 1 .D. Реализация: Закодировать элементарную програм- му Hello Word (Здравствуй, мир) с использовани- ем MFC. Включить элемент Quit в меню File. 2 .А. Проектирование: Завершить построение диаграм- мы классов, отображающих отскоки шайбы от стенок окна. 2.В. Реализация: Закодировать соударения шайбы в окне. 1.А. Анализ прикладной области: Добавить детали управления лопаткой и соударений в диаграмму классов и другие диаграммы. 1.В. Проектирование: Спроектировать классы Paddle (лопатка) и Collision (соударение). 1.С. Реализация: Выполнить инкрементальное кодирование класса Paddle на базе класса Movablesprite и класса Collision на базе класса Exeption 1.А. Анализ прикладной области: Добавить набор спрайтов в диаграмму классов. 1.В. Проектирование: Разработать алгоритм обнаруже- ния соударений. 1.С. Реализация: Закодировать класс BrickPile путем агрегирования с классом коллекции. 1 .А. Проектирование: Спроектировать алгоритм завершения сеанса, использующий исключитель- ные условия для обнаружения условия endOfMatch. 1.В. Реализация PuckSupply. Рисунок 3.2. Схема инкрементального, итеративного плана проектирования для игры «Кирпичики».
Глава 3. Планирование работ по тестированию 89 Обзор процесса тестирования Обычно процесс тестирования стоит в списке видов деятельности, выполняе- мых при разработке программного обеспечения, последним, сразу же за реализа- цией. Этот вид деятельности относится к тем типам тестирования, которые про- веряют, соответствует ли программный продукт как единое целое по своим функциональным возможностям тому, чем он должен являться. С нашей точки зрения, тестирование есть такой вид деятельности, который может быть осуще- ствлен в различные моменты разработки, а не только на завершающей стадии и применен не только к программному коду. Мы определяем процесс тестирова- ния как вид деятельности, отличный от процесса разработки, но непосредствен- но связанный с ним, поскольку цель тестирования объективно отлична от цели разработки. Соответственно, мы предпочитаем рассматривать разработку и тес- тирование как два различных, но тесно связанных процесса. Процессы разработки и тестирования отличаются друг от друга прежде всего в силу того, что перед ними поставлены различные цели и что для них харак- терны различные критерии успеха. Разработка ориентирована на построение программного продукта, который отвечает потребностям общества. Тестирование стремится ответить на вопросы, касающиеся продукта, в том числе и на вопрос, соответствует ли разрабатываемый программный продукт тем требованиям, кото- рым должен соответствовать по первоначальному замыслу. Рассмотрим, напри- мер, количество дефектов, обнаруженных в результате тестирования некоторого разработанного программного обеспечения. Считается, что чем меньше коэффи- циент отказов (отношение неудачно выполненных контрольных примеров к об- щему числу контрольных примеров), тем более успешной считается разработка. С другой стороны, считается, что чем выше коэффициент отказов, тем эффек- тивнее тестирование. Обязанности по разработке и тестированию возлагаются на различных людей, что подтверждает вывод о различии этих процессов. Идея использования раз- личных исполнителей для проведения разработки и тестирования особенно про- дуктивна с позиций перспективы комплексных испытаний системы. Тестиров- щики пишут контрольные примеры независимо от тех, кто разрабатывает программы, имея перед собой цель удостовериться в том, что создаваемая систе- ма делает именно то, что определено предъявленными к ней требованиями, а не то, что должна делать система в интерпретации ее разработчиков. Это положение справедливо на всех уровнях тестирования. Во многих реали- зациях в обязанности разработчикам вменяются некоторые виды тестирования, такие как, скажем, традиционное тестирование модулей и целостности. Однако, чтобы добиться успеха, любой специалист, берущий на себя роль и разработчи- ка, и тестировщика, должен сделать все от него зависящее, чтобы добиваться цели в обоих ролях с одинаковым рвением. Преследуя эту цель, мы практикуем так называемое «дружественное тестирование», по условиям которого один раз- работчик проводит тестирование программного модуля, спроектированного дру-
90 Глава 3. Планирование работ по тестированию гим разработчиком. Следовательно, один разработчик преследует, по меньшей мере, одну цель и несет ответственность за один набор функциональных средств, а другой разработчик преследует другую цель и несет ответственность за другой набор функциональных средств. Даже если оба процесса различны, тем не менее, между ними существует тес- ная связь. Поля их деятельности даже перекрываются, когда необходимо разра- батывать, кодировать и выполнять контрольные примеры. Все вместе они охва- тывают виды деятельности, необходимые для получения качественного продукта. Ошибки могут быть привнесены на каждой стадии процесса разработки. Следо- вательно, у каждого этапа разработки имеется связанный с ним этап тестирова- ния. Отношения между двумя этими процессами таковы, что если что-то разра- батывается, то оно подвергается тестированию, а результаты тестирования используются для определения, соответствует ли это «что-то» набору предъяв- ленных к нему требований. Процессы тестирования и разработки образуют виток обратной связи (см. рис. 3.3). Процесс тестирования возвращает выявленные им ошибки в процесс разработки.1 Протоколы обнаруженных ошибок описывают совокупности симп- томов, характеризующих эти ошибки, а полученные описания разработчик ис- пользует для выявления точного места нахождения неисправностей или ошибок. Процесс разработки передает процессу тестирования новые и исправленные про- ектные версии и версии реализации. Тестирование продуктов разработки помо- гает выявить ошибочные контрольные примеры, когда тестировщики обнаружат, что источниками «отказов» являются сами тестовые примеры или драйверы, ко- торые их выполняют, но не сам тестируемый программный продукт* 2. Результаты тестирования Результат разработки Рисунок 3.3. Процессы тестирования и разработки образуют петлю обратной связи Цель тестирования состоит в том, чтобы обнаружить отказы, но не ошибки или неисправности, которые явились причиной отказов. Выявление источников отказов вменяется в обязанности раз- работчиков. Интересным аспектом разработки тестовых примеров является выбор лиц, которые проверяют тес- товые примеры. Большинство тестовых примеров подвергаются ревизии, но в то Же время боль- шая часть процессов предусматривает лишь формальное тестирование тестовых примеров в не- больших объемах.
Глааа 3. Планирование работ по тестированию 91 КОНТРОЛЕПРИГОДНОСТЬ Одним из массивов информации, который возвращается разработчикам после тес- тирования, является оценка того, насколько тот или иной программный продукт благоприятствует тестироаанию. Контролепригодность показывает, с какой легкос- тью можно дать правильную интерпретацию результатов тестирования. В главе 7 мы покажем, насколь предлагаемая нами архитектура тастирования PACT [Parallel Architecture for Class Tasting — Параллельная архитектура тестирования классов] позволяет увеличить уровень контролепригодности благодаря преодолению сокрытия информации. Контролепригодность представляет собой подходящий контекст для изу- чения проблемы выбора благоприятного момента для тастироаания. В условиях, когда многочисленные слои программного обеспечения добавляются один поверх другого, видимость хранимых величин становится все болае призрачной. Чем нижв уровень, на котором производится тестирование того или иного программного фраг- мента, тем лучше просматриваются енутрипрограммные величины, необходимые для верификации результатов тастирования, там большей контролепригодностью обла- дает этот фрагмент. В контексте описанной выше петли обратной связи форма и содержание ре- зультатов разработки оказывает влияние на процесс тестирования. Когда разра- ботчики выбирают методы и инструментальные средства, они тем самым уста- навливают ограничения на процесс тестирования. Подумайте, например, над тем, в какой степени уровень формализма спецификаций классов упрощает вы- бор тестовых примеров, предназначенных для тестирования классов. Оценку перспективы тестирования необходимо проводить во время выбора методов и инструментальных средств разработки, и желательно в присутствии профессио- нальных тестировщиков. Форма и качество спецификации требований также оказывают влияние на этот процесс. Требования к продукту включают в себя требования к источникам тестовых примеров в системе и приемочным испытаниям. Системные тестиров- щики должны принимать участие в отборе и подтверждении правильности этих требований, чтобы обладать достаточно четким представлением о них, позволя- ющим дать правильную оценку связанных с ними рисков и контролепригоднос- ти. Метод тестирования STEP, разработанный Уильямом Хетцелем (William Hetzel) [Hetz84], предусматривает трехступенчатый подход к каждому виду тес- тирования, которому подвергается проект: 1. Анализ. Тестируемый программный продукт проверяется на наличие опре- деленных свойств, которым при тестировании следует уделять повышенное внимание, при этом определяются тестовые случаи, которые нужно постро- ить с этой целью. Мы воспользуемся несколькими методами анализа. Не- которые из них, такие как, например, тестирование ветвей программ, мож- но автоматизировать, однако множество других требуют от тестировщиков определения вручную того, что следует тестировать.
92 Глава 3. Ппанированив работ по тестированию ТЕСТОВЫЕ СЛУЧАИ И НАБОРЫ ТЕСТОВ Тастовыа (контрольные] спучви являются базовым компонентом системы. В наибо- лее обобщенной форма тестовый случай (test case) прадставпяат собой пару [входные данные, ожидаемый результат}, в которой входные денные — это описаниа данных, подаваемых на вход тестируемого прогрвммного продукта, а ожидаемый результат асть описаниа выходных данных, которые программный продукт должен предъявить в ответ на соответствующий ввод. Входные данные и ожидаемые ре- зультаты на обязательно должны быть простыми величинами, подобными строкам и целочисленным значениям; напротив, их сложность ничем не ограничивается. Зача- стую наряду с командами пользователя и данными, подлежащими обработка, в состав входных данных включается информация о состоянии системы. Ожидеамыа результаты — это на только такие пагко воспринимаемые ващи как, скажем, лис- тинги результатов, звуковые сигналы или изменения в изображениях, воспроизво- димых на экрана, но твкжа изменения и в самом программном продукта, напримар, обновление содержимого базы данных или изменение состояния самой системы, которое, в свою очарадь, впияат на обработку последующих наборов входных дан- ных. Выполнение (прогон) тестового случая — это саанс работы программного обеспечения, в рамках которого на вход программного продукта подаются наборы данных, предусмотренные спецификацией тестового случая, и фиксируются резуль- таты их обработки, которые затем сравниваются с ожидаемыми результатами, ука- занными в тестовом случае. Если фактический результат отличается от ожидаемого, это значит, что обнаружен отказ, и в таких ситуациях мы говорим, что тестируемый программный продукт «на прошап испытаний на заданном тестовом случае». Если жа полученный результат совпадает с результатом, ожидаемом в данном тестовом случае, то мы говорим, что программные продукт «прошап испытаний на заданном тестовом случае». Из тестовых случаев формируются тестовые наборы (test suite). Большинство тестовых наборов в большей или маньшай степени организованы в определенном порядка, отражающем свойства тестовых случаев. Напримар, одну часть тестовых наборов могут составлять тестовые случаи, которые тестируют возможности систе- мы, а другая часть содержит тастовыа случаи, предназначенные для углубленной проварки обычной работы системы в рамках конкретных возможностей. Если про- граммный продукт успешно справляется со всеми тестовыми случаями, то мы го- ворим, что продукт «успешно прошап испытания на тестовом набора». Одной из наиболее труднорашаамых проблем тестирования является разработка и организация тестовых наборов. Основными задачами, которые приходится решать в процесса разработки тестовых наборов, явпяатся обеспечение корректности, наблю- даемости результатов и адекватности. 2. Построение. На этом этапе создаются артефакты, т.е. искусственные объек- ты, которые требуются для выполнения тестирования. Тестовые случаи, не- обходимость которых была обоснована во время анализа, переводятся на языки программирования или языки макрокоманд, либо реализуются на специализированных языках конкретных инструментальных средств. При этом часто возникает необходимость в специальных наборах данных, в связи с чем иногда требуются значительные усилия, чтобы построить дос- таточно крупный набор данных. 3. Выполнение и анализ результатов. Часто сторонние наблюдатели видят и признают только эту сторону отладочной деятельности, однако это обычно
Глава 3. Планирование работ по тестированию 93 самый короткий этап тестирования. На этом этапе производится выполне- ние тестовых случаев, обоснование необходимости и построение которых были проведены на этапе анализа. Полученные результаты, в свою очередь, подвергаются анализу с тем, чтобы определить, успешно ли прошел про- граммный продукт испытания на тестовом наборе или не выдержал испы- тания. Иногда удается автоматизировать многие из видов деятельности по- добного типа. Это особенно полезно в условиях итеративной среды, так как по мере продвижения проекта одни и те же тестовые случаи могут применяться многократно. Тестовые наборы требуют сопровождения. По мере изменения требований должны меняться и тестовые наборы. Вы должны вносить исправления в тесто- вые наборы, в которых выявлены ошибки. Если неисправность обнаружили пользователи, то в тестовый набор должны быть добавлены тестовые случаи, ко- торые позволили бы устранить их в будущих версиях программного продукта еще до разворачивания на местах. Тестовый процесс имеет итеративную и инкрементальную природу, поэтому план его выполнения должен быть согласован с планированием связанной с ним разработки. ВОПРОС Какие данные хотят получить тестировщики от разработчиков программного продукта? Шаблон случая использования, описание которого было прадстаалано аыша, со- держит практически всю информацию, которая только требуется для разработки тестовых случаеа на системном уровна. В частности, предусловия и постусловия играют важную роль при определении очарадности выполнения тестовых случаев и получении информации о скрытых зависимостях. Структурированная модель случая использования может оказаться полезной специалистам, создающим тес- товые случаи, поскольку она снабжает их информацией о возможности повторного использования тестовых программ и данных. Последовательность моделей состо- яний, связанных с подсистамами и самой системой, такжа содействует получению информации о формировании последоватапьности действий и ожидаемых ответах тестируемых компонентов. ВОПРОС На какой стадии разработки проекта необходимо подключение тестировщиков? По традициям, сложившимся в некоторых компаниях, тестирующий персонал не подключается к работе над проектом до тех пор, пока вго разработка не продви- нется достаточно далеко. Описанная здесь связь между процессами тестирования и разработки служат доказатвльстаом того, что ранниа проактныа решения тре- буют, чтобы входные данные вводили специалисты, имеющие достаточный опыт тестирования. Таким специалистом может быть один из тестировщиков, который подключился к работе над проектом на его раннвй стадии, или разработчик, имеющий опыт тестирования.
94 Глава 3. Планированиа работ по тастированию Анализ рисков как средство тестирования Анализ рисков включается в план любых разработок. Он может оказаться критически важным при определении, что нужно тестировать во время разработ- ки и в каких объемах. В этом разделе мы опишем некоторые базовые понятия, используемые при анализе рисков. Затем мы применим эти понятия к тестиро- ванию. Мы также сравним тестирование, учитывающее уровень риска, с обосно- ванием выбора тестовых примеров на основе частоты использования функцио- нальных средств. Риски В общем случае риск представляет собой нечто такое, что угрожает успешно- му достижению целей проекта. В частности, риск есть событие, которое может произойти с некоторой вероятностью, и когда оно происходит, оно приводит к определенным потерям. Эти потери могут выражаться в виде напрасных затрат времени, в виде финансовых потерь, иногда, в зависимости от системы, это тех- нические повреждения. Для каждого проекта существует свой набор рисков; не- которые риски считаются «более высокими», нежели другие. Такое упорядочение учитывает как вероятность возникновения потерь, так и то, насколько серьезны- ми могут оказаться эти потери в смысле последствий для всего проекта. В кон- тексте тестирования, учитывающего риски, фундаментальный принцип заключа- ется в том, что наиболее интенсивному тестированию подвергаются те фрагменты системы, которые угрожают наибольшими потерями для всего проекта с тем, что- бы наиболее пагубные дефекты были гарантированно выявлены. Риски подразделяются на три основных категории: риски, связанные с проек- том, бизнес-риски и технические риски. Риски, связанные с проектом, включают административные и экологические риски (такие, например, как недостаточное обеспечение проекта квалифициро- ванным персоналом), которые не оказывают непосредственного влияния на про- цесс тестирования. Бизнес-риски обусловлены проблемами предметной области. Например, изме- нения правил отчетности в системе IRS (Information Retrieval System — инфор- мационно-поисковая система) повышает риск нарушения неизменности требова- ний, предъявляемых к системам учета, поскольку функциональные возможности системы должны быть приведены в соответствие с новым положением. Этому виду риска подвергаются функциональные возможности программы, и по этой причине данные риски распространяются и на тестирование на системном уров- не. Когда тестируемая система предназначена для работы в непредсказуемо ме- няющихся предметных областях, набор системных тестов должен исследовать расширяемость и модифицируемость системной архитектуры. Для описания технических рисков требуется привлечь некоторые понятия реа- лизации. Например, качество кода, генерируемого компилятором, или устойчи- вость программных компонентов — это чисто технические риски. Данный тип
Главе 3. Планирование работ по тестироеанию 95 рисков связан с реализацией программы и в силу этого обстоятельства связан главным образом с тестированием на уровне программных кодов. Анализ рисков Анализ рисков представляет собой процедуру выявления рисков и нахожде- ния способов, позволяющих избегать ситуаций, в которых потенциальные про- блемы становятся реальными. Итоговые результаты анализа рисков материализу- ются в виде списка выявленных рисков, упорядоченного по уровню рисков, который может быть использован для распределения ограниченных ресурсов и присвоения решениями этих задач соответствующих приоритетов. Определения рисков меняются от одного проекта к другому, они даже меняются с течением времени в рамках одного и того же проекта, ибо меняются приоритеты и страте- гии разработки. Типичные риски в проектах объектно-ориентированных про- граммных продуктов специфичны и уникальны для архитектурных свойств, для областей сложных взаимодействий объектов, для сложных поведений, обуслов- ленных спецификациями классов, и для изменяющихся и эволюционизирую- щих требований, предъявляемых к проекту. Класс, который разрабатывается с целью включения в библиотеку, требует более тщательного тестирования, неже- ли тот, что предназначается для использования в качестве прототипа. Другим определением риска может служить сложность класса, мерой которой можно из- брать размер его спецификации или количество отношений, которые он поддер- живает с другими классами. Источники рисков Для целей системного тестирования различным видам использования систе- мы присваиваются соответствующие приоритеты в зависимости от их значения для пользователя и для корректного функционирования системы. Оценка риска также может зависеть и от сложности принципов и идей, которые должны быть реализованы в различных подсистемах, от изменчивости требований, предъяв- ляемых к каждой подсистеме, и от учета особенностей предметной области в рамках конкретной подсистемы. Риски также ассоциируются с языками программирования и инструменталь- ными средствами разработки, используемыми для реализации программного продукта. Разные языки программирования допускают одни классы ошибок и исключают другие, например, строгий контроль типов в языках Java и C++ га- рантирует, что каждое отправленное сообщение (вызов функции-члена) в про- цессе выполнения программы будет правильно воспринято получателем. И на- против, отсутствие в языке SmallTalk строгой типизации означает, что во время выполнения программы на экран может быть выведено сообщение об ошибке «message not understood» (сообщение не распознано). Строгий контроль типов может существенно упростить идентификацию тестовых случаев, поскольку по- дача на вход некоторых видов входных данных исключается самим языком про- граммирования.
96 Глава 3. Планирование работ по твстироаанию Проведение анализа Исповедуемый нами подход к анализу рисков выявляет риск, сопряженный с каждым случаем использования и угрожающий успешному завершению проекта. Возможны и другие определения риска, но именно это определение наилучшим образом подходит для целей планирования работ по тестированию. Метод анализа рисков предусматривает решение трех видов задач: 1. Идентификация риска (рисков) для разработки, сопряженного с каждым случаем использования. 2. Количественная оценка риска. 3. Построение упорядоченного списка. Составитель конкретного случая использования может назначить рейтинг рисков для индивидуального случая использования, учитывая при этом, на- сколько риски, выявленные на уровне проекта, применимы к данному конкрет- ному случаю использования. Например, для тех требований, которые считаются наиболее подверженными изменениям, характерен высокий уровень риска, для тех требований, которые выходят за уровень компетентности коллектива разра- ботчиков, уровень риска еще выше, для тех требований, которые ориентируются на разработку соответствующих новых технологий, таких как аппаратные сред- ства, одновременно с разработкой программных средств, уровень риска также остается высоким. По существу, найти случаи использования с низким уровнем рисков намного труднее, чем случаи использования с высокими уровнями рис- ка. Точные наборы значений, используемые для построения рейтинговой шкалы, меняются от одного проекта к другому. Набор должен содержать достаточно большое число уровней, чтобы стало возможным разбиение множества случаев использования на группы приемлемых размеров, в то же время количество та- ких категорий не должно быть чрезмерным, ибо тогда могут появиться пустые категории. Обычно мы начинаем с того, что устанавливаем три категории: низ- кая, средняя и высокая. В проекте, охватывающем 100 случаев использования, примерно 40 из них могут оказаться в категории высокая. Это, по-видимому, та ситуация, когда мы не сможем уделить достаточно времени каждому случаю ис- пользования. Добавив категорию очень высокая и выполнив повторную класси- фикацию случаев использования, получим, скажем, 25 случаев в категории высо- кая и 15 случаев в категории очень высокая. Эти 15 случаев использования будут подвергнуты особенно тщательному исследованию. Введенная нами классификация приводит к упорядочению случаев использо- вания. Этот упорядочение используется в двух видах проектной деятельности. Во-первых, менеджеры могут задействовать его для распределения случаев ис- пользования по приращениям (но это не наша проблема!). Во-вторых, такое упорядочение может быть использовано для определения объемов тестирования, которому должен быть подвергнут каждый элемент той или иной категории. Тес-
Глава 3. Планированиа работ по тестированию 97 тирование, основанное на оценке риска, используется в тех случаях, когда риски очень велики, например, когда речь идет о жизненно важных системах. В при- мерах, приводимых на протяжении книги, мы рассматриваем, как использовать подходы, основанные на оценке рисков и на данных из профилей использова- ния, для выбора соответствующих тестовых примеров. Рассмотрим два примера. В первом мы подвергнем анализу риски игры «Кирпичики». Поскольку это очень простая система, мы этим не ограничимся и далее рассмотрим более представительный пример. Что касается игр, подобных игре «Кирпичики», то наибольшие риски пред- ставляют собой вещи, снижающие удовольствие от игры. В таблице, представ- ленной на рис. 3.4, обобщена информация, полученная в результате анализа двух основных случаев использования. Случай использования «выигрыш» полу- чил рейтинг более критичного, нежели случай использования «проигрыш». Представьте себе случай, когда вы выиграли игру, но программа отказывается признать этот факт! Частота выигрышей оценивается как более низкая по срав- нению с частотой проигрышей. Имеются п! различных последовательностей раз- биения кучи кирпичей (здесь п — количество кирпичей в куче). Таких последо- вательностей становится намного больше, если учитывать переменный характер отскоков шайбы от стен и потолка. Существуют (и — 1) + (и — 2)+...+ 2+1 способов проиграть с одной конкретной шайбой, но таких возможностей стано- вится намного больше, если учитывать промахи. Очевидно, что способов проиг- рать намного больше, чем способов выиграть. Поскольку выигрыш, равно как и проигрыш, достигается посредством одной и той же программы, то степень рис- ка при реализации каждого из случаев использования одна и та же, следователь- но, для обоих случаев риск считается одинаковым. Если мы возьмем сочетание частотности и критичности, воспользовавшись схемой, показанной в разделе «Техническая сводка — создание тестовых случаев» на базе случаев использова- ния, то оба случая использования попадают в категорию средняя. Указанная про- грамма должна тестироваться примерно с одинаковым числом выигрышей и про- игрышей. Случай Уровень использования риска Частота Критичность Сценарий Выигрыш Средний Низкая Высокая Игрок выигрывает игру Проигрыш Средний Высокая Низкая Игрок проигрывает игру Рисунок 3.4. Два случая использования игры «Кирпичики» Рассмотрим другой пример для приложения, в рамках которого записи карто- теки персонала подвергаются модификации, сохраняются и, возможно, удаляют- ся. Случаи использования сведены в таблицу, представленную на рис. 3.5. Слу- чаи использования обращаются к операции обновления записи, которая меняет имя служащего, совершая при этом обновление и удаление записи. Анализ слу- 4;,.
98 Глааа 3. Планирование работ по тестированию чая использования позволяет выявить такие объекты прикладной области, как имя, персонал и безопасность. Случай использования Уровень риска Частота Критичность Сценарий Изменение имени Низкий Средняя Низкая Пользователь вносит изменения в поле имени существующей записи в картотеке персонала, к которой имеет соответствующий санкцио- нированный доступ. Сохранение записи Средний Высокая Высокая Пользователь сохраняет запись, которая была построена заново или подвергалась изменениям. Удаление записи Высокий Средняя Средняя Пользователь удаляет существую- щую запись, к которой имеет соответствующий санкционирован- ный доступ. Pl/ICyHOK 3.5. Три случая использования для системы управления персоналом. Информация в графе «риска» показывает, что операция удаления записи об- ладает высокой степенью риска. Для способности сохранять записи характерна высокая критичность. Обычная практика предусматривает планирование тести- рования случаев использования с высокой степенью риска ранее остальных слу- чаев, поскольку они требуют больше времени на тестирование, и только в этой ситуации можно рассчитывать на завершение проекта без задержек. Сочетание критичности и частоты применения случаев использования образуют интеграль- ный показатель, который позволяет определить, какой из случаев должен тести- роваться в больших объемах. Разумеется, мы намерены подвергнуть тестирова- нию большую часть случаев использования, характеризующихся наибольшей критичностью и частотой использования. Но иногда операции с большой кри- тичностью выполняются не очень часто по сравнению с другими случаями ис- пользования. Например, вход в Internet через провайдера услуг критично, одна- ко оно выполняется всего лишь один раз за сеанс, в то время как проверка содержимого своего ящика электронной почты может осуществляться по не- скольку раз за один сеанс работы в сети. Таким образом, комбинация значений атрибутов частоты использования и критичности определяют относительные объемы тестирования. В различных проектах применяются различные методы сочетания этих значе- ний, однако существуют две стратегии общего характера, применяемые для этой цели. Традиционная стратегия предусматривает такое сочетание двух этих вели- чин, при котором выбирается наибольшая из них. Например, для случая ис- пользования «Изменение имени» применение традиционного метода дает ком- бинированное значение как среднее. Аналогично, усредняющая стратегия выбирает некоторое значение, находящееся в промежутке между двумя заданны-
Глава 3. Планирование работ по тестированию 99 ми значениями. В рассматриваемом случае такого промежуточного значения не существует, если мы не введем новую категорию, например, средне-высокую. Это нужно делать только в тех случаях, когда в какую-то одну категорию попадает достаточно большое количество случаев использования, в силу чего возникает необходимость их дальнейшей дифференциации. Применяя избранную стратегию, можно составить упорядоченный список случаев использования. Для указанных выше случаев использования, воспользо- вавшись традиционной стратегией, получим следующий список в порядке воз- растания: Редактирование имени, Удаление записи и Сохранение записи. Таким об- разом, случай Сохранение записи будет подвергнут более интенсивному тестированию, чем Удаление записи, который, в свою очередь, будет тестировать- ся в большем объеме, нежели Редактирование имени. Точное число тестовых слу- чаев, которые будут задействоваться для этой цели, мы определим позже, когда изучим методы выбора тестовых примеров. Процесс тестирования Имея в своем распоряжении модель инкрементального итеративного процес- са, можно сделать набросок процесса тестирования. Мы отложим изучение мно- гих деталей до следующих глав, поскольку, в основном, вся информация в этой книге касается именно этого процесса. Прежде всего, мы обозначим целый ряд проблем, которым следует уделить внимание, дабы придать процессу тестирова- ния четкие очертания. Затем мы подумаем над тем, каким же образом следовало бы тестировать каждый разработанный программный продукт Проблемы планирования Тестирование традиционно встраивается в процесс разработки в тех точках, в которых программный код становится доступным. Вполне обычной практикой является автономное тестирование модулей по мере их разработки, комплексные испытания подсистем по мере их компоновки из отдельных модулей и других подсистем, и, наконец, системные испытания после того, как система достигнет этой такой стадии разработки, которая позволяет проводить упомянутый вид те- стирования. Если используется инкрементальный, итеративный процесс разра- ботки, системные испытания проводятся, по меньшей мере, по завершении каж- дого приращения. Тестирование классов, а также проверка взаимодействия и функционирования компонентов осуществляются во время и после каждой ите- рации в приращении. Регрессионному или возвратному тестированию подверга- ется любой программный продукт, реализация которого изменилась, но не изме- нилась его спецификация. Если изменилось и то и другое, тестовые наборы пересматриваются и после этого используются по назначению. По условиям выбранного нами подхода тестирование проводится еще до того, как будет готов программный код. Модели, будучи подобно коду представ- лением системы, могут быть подвергнуты тестированию. В частности, проектные модели (т.е. модели, используемые на стадии проектирования) годятся для тес- 4*
100 Главв 3. Планирование работ по тестированию тирования с применением формы исполнения, которая описывается в главе 4. Используя аналитические модели, можно тестировать систему в контексте вери- фикации, убеждаясь в том, что система наделена свойствами в соответствии со спецификации. Этот последний вид тестирования не особенно отличается от традиционных подходов, в связи с чем он не является главной темой данной книги. Размерности тестирования программного обеспечения Тестирование охватывает многие виды деятельности, обязательных для вы- полнения. Все эти виды деятельности предусматривают тестирование. Что каса- ется этих видов деятельности, то мы вводим пять видов оцениваемых факторов, которые дают ответы на следующие пять вопросов:3 1. Кто выполняет тестирование! Разработчики программного продукта, неза- висимая группа, специализирующаяся на тестировании программного обес- печения, или некоторое сочетание представителей обоих групп? 2. Какие компоненты программного обеспечения подвергнутся тестированию! Будут ли подвергнут тестированию каждый компонент программного про- дукта или компонент с максимальной степенью риска? 3. В какой момент следует выполнять тестирование! Будет ли это непрерыв- ный процесс, вид деятельности, выполняемый в специальных контрольных точках, или вид деятельности, выполняемый на завершающей стадии раз- работки? 4. Как будет выполняться тестирование! Будет ли тестирование сосредоточе- но только на проверке того, что данный программный продукт должен вы- полнять, или того, как это будет реализовано? 5. Какие объемы тестирования следует считать адекватными! Как опреде- лить, в достаточном объеме выполнено тестирование, или как распределить ограниченные ресурсы, выделенные под тестирование? Термин оцениваемый фактор здесь употребляется в том смысле, что каждый такой фактор представляет собой угол зрения, под которым рассматривается не- который континуум уровней работ или подходов, при этом каждый из них не зависит от остальных. Каждый оцениваемый фактор должен быть учтен при пла- нировании работ по тестированию, в то же время необходимо определить, в ка- ком месте этого континуума проект найдет себе место. Решение, принимаемое в отношении какой-то одного оцениваемого фактора, не оказывает никакого влия- ния на решения, принимаемые в отношении остальных факторов. Все вместе эти решения определяют необходимые ресурсы, используемые методы и качество результатов общих усилий по тестированию. 3 Шестой оцениваемый фактор, отвечающий на вопрос, где должно выполняться тестирование, ва- жен в организационном плане, однако мы не будем на нем останавливаться в рамках данной книги
Глава 3. Планированиа работ по тастированию Рассмотрим теперь эти оцениваемые факторы подробнее. Оцениваемые фак- торы мы будем анализировать при обсуждении различных вопросов на протяже- нии всей книги. Для представления каждого оцениваемого фактора воспользу- емся понятием континуума. Континуум — это последовательность возможных уровней, при этом трудно определить, где кончается один уровень и начинается другой. В мире физики видимый спектр света представляет собой континуум, простирающийся от красного цвета до цвета индиго. Оранжевый цвет содержит- ся в этом спектре, однако однозначного соглашения о том, где начинается оран- жевый цвет и где он заканчивается, не существует. Тем не менее, это не мешает нам использовать оранжевый цвет или обсуждать его достоинства при выборе расцветки, скажем, спортивных костюмов. Как не существует цвета, который лучше любого другого, так нет «наилучше- го выбора» для каждого оцениваемого фактора. Тем не менее, в некоторых ситу- ациях тот или иной цвет подходит лучше других. В этой главе мы дадим описа- ние пяти оцениваемых фактора. В последующих главах при обсуждении отдельных методов мы будем отображать вопросы, возникающие в рамках от- дельных оцениваемых факторов, на все работы по тестированию. Вместе с тем, мы надеемся дать вам представление о том, как различные сочетания позиций соотносятся с качеством программного продукта. Кто выполняет тестирование? В проекте задействованы как роль разработчика, так и роль тестировщика. Разработчик — это роль, для которой характерны виды деятельности, ориенти- рованные на создание программного продукта, например, анализ, разработка, программирование, отладка или документирование. Тестировщик есть роль, для которой характерны виды деятельности, ориентированные на обнаружение оши- бок в программном продукте. Эта роль предусматривает выбор тестов, необходи- мых для конкретных целей, построение тестов, выполнение тестов и оценка ре- зультатов. Конкретный исполнитель проекта может выступать как в роли разработчика, так и в роли тестировщика. Распространенной практикой является передача разработчиками ответственности за тестирование собственных про- граммных модулей, хотя мы настоятельно рекомендуем схему с дружественным тестированием. Системное тестирование обычно поручается независимым тести- ровщикам — специалистам, на которых возложена роль тестировщиков, но не разработчиков. На рис. 3.6. показан континуум, охватывающий диапазон от ситуации, когда за все виды тестирования ответственность несут разработчики, до ситуации, ког- да все виды отладки выполняет независимый тестировщик. В последнем случае конец континуума на практике встречается не так часто, как его середина. В ча- стности, только для небольших проектов характерна ситуация, когда разработчи- ки отвечают за завершающую проверку системы, когда реализация системы про- веряется на соответствие требованиям, предъявляемым к системе в целом. В проектах, предусматривающих выполнение жизненно важных функций, каждый
Главе 3. Плвнироввнив работ по тестированию шл Ответственность несут разработчики Разработчики и независи- мые тестировщики делят между собой обязанности Ответственность несут независимые тестировщики Рисунок З.Б. Континуум для назначения ролей при тестировании классов компонент подвергается автономному модульному тестированию, которое про- водят независимые тестировщики. Имеются соответствующие правительствен- ные постановления, которые регламентируют такой вид тестирования. Между двумя этими крайностями находятся два достаточно часто выбираемых случая. В первом из них разработчики полностью отвечают за тестирование классов, и в то же время пары разработчиков обмениваются между собой программами, при- чем каждый из них тестирует чужую программу, в чем, собственно, и состоит смысл упомянутого ранее дружественного тестирования. В другом случае ответ- ственность за описания тестовых примеров возлагается на независимого тести- ровщика, в то время как разработчик несет ответственность за построение тесто- вых примеров и выполнение тестирования. В этой книге мы обсуждаем процессы и методы тестирования и обычно не указываем, кто их выполняет. Это решение должно базироваться на эффектив- ном использовании ресурсов в различные моменты всего процесса создания программного продукта. На это решение влияет позиция правительства и про- мышленное законодательство. Фактические планы тестирования в рамках проек- та должны назвать тех, кто несет ответственность за выполнение различных ви- дов тестовой деятельности. Существует множество способов назначения ролей членам коллектива разработчиков проекта, и мы еще не отыскали «наилучший» из этих способов. Какие компоненты подлежат тестированию? Какие части системы подлежат тестированию? Варианты изменяются от «ни- чего не надо тестировать» до «тестировать каждый компонент (или каждою стро- ку программного кода)», который входит в окончательную версию программного продукта. Соответствующий континуум показан на рис. 3.7. Программная система обеспечения состоит из множества компонентов. В проблемно-ориентированном программировании наиболее базовым компонентом является класс. На одном конце соответствующего континуума находится пози- ция «мы будем тестировать каждый класс, который содержится в данной систе- Ничего не надо тестировать Тестировать образец Тестировать все Рисунок 3.7. Континуум для тестирования компонентов программного продукта
Глава 3. Планированиа работ по тастированию ме». На другом конце этого континуума находится позиция «мы не будем тести- ровать никаких компонентов». Ошибки обнаруживаются случайным образом во время работы системы или благодаря распространению «оценочных копий» через Web, обеспечивающим пользователям возможность сообщать об обнаруженных ошибках. Средний участок континуума предусматривает систематический подход, воз- можно, применение статистических методов выбора поднабора из множества те- стируемых компонентов. Классы, заимствованные из других проектов или взя- тые из библиотек классов, по-видимому, тестировать не нужно. Некоторые классы трудно тестировать в автономном режиме, поскольку для этой цели не- обходимо разрабатывать специальные драйверы, генерирующие входные данные или анализирующие выходные данные. Разработка самих таких драйверов требу- ет значительных затрат на их написание; они могут потребовать интенсивной отладки и тестирования. Частично выбор соответствующего места в континууме обусловливается соотношением показателей (количеством ошибок, обнаружен- ных за час работы) тестирования с затратами усилий, необходимых для построе- ния тестовой инфраструктуры. Если тестирование абсолютно всех классов неосуществимо, какую стратегию стоит выбирать для разработки тестовых примеров? Мы предлагаем строить тес- товые примеры наудачу. Разумеется, это не самая лучшая стратегия, поскольку в данном случае многие широко используемые функции программного продукта могут избежать тестирования. В основе другой стратегии могут быть положены вероятные случаи использования программного продукта, тем самым основной акцент переносится на тесты, в рамках которых на вход программного продукта подаются наиболее характерные для него данные. Еще одна стратегия основное внимание уделяет патологическим случаям (малопонятные случаи использова- ния) в предположении (возможно, ошибочном), что если разработчики уделили внимание таким загадочным и непонятным требованиям, то они должны пра- вильно понимать все требования.4 Как часто следует выполнять тестирование? Компоненты можно тестировать по мере их разработки; их тестирование мо- жет быть отложено до того, как все они будут интегрированы в единый выпол- няемый модуль, как показано на рис. 3.8. Чем дольше откладывать работы по тестированию результатов разработки, тем более разрушительными могут стать последствия изменений, обусловленных результатами тестирования. Когда следует выполнять тестирование? Иногда тестирование выполняется только на завершающем этапе процесса разработки, т.е. системные испытания и/или приемочные испытания являются единственным формальным видом тес- тирования программного продукта. Этот подход может оказаться приемлемым в 4 Тестирование только патологических случаев использования нельзя признать хорошей стратегией.
Глввв 3. Планирование рвбот по твстироввнию Выполнять ежедневное тестирование Проводить тестирова- ние компонентов по мере их готовности Тестировать все компоненты на завершающем этапе Pl/ICyHOK 3.8. Континуум, показывающий, как часто следует выполнять тестирование программного продукта тех случаях, когда относительно небольшое число разработчиков руководствуют- ся в своей деятельности набором четко сформулированных и понятных требова- ний, но при этом не следует забывать и о том, что труд разработчиков должен быть эффективным. Хорошо известно, что чем раньше выявлена проблема, тем легче и дешевле ее решение. Поэтому на противоположном конце континуума находится решение проводить ежедневное тестирование. Между двумя этими экстремальными значениями находится требование тестирования каждого ком- понента программного продукта по мере завершения его разработки. Этот под- ход, несомненно, замедляет ранние этапы разработки, однако связанные потери с лихвой будут восполнены за счет меньшего числа проблем на более поздних этапах разработки проекта, когда отдельные модули объединяются в более круп- ные компоненты системы. Между двумя экстремальными значениями континуума находится тестирова- ние по завершении каждого приращения. Вместо сборки автономно оттестиро- ванных компонентов в единый комплектующий узел в рамках очередного приращения программного обеспечения, этот подход предусматривает интегри- рование не прошедших тестирования компонентов с последующим тестировани- ем объединенного программного кода как единого целого. Такой подход приво- дит к уменьшению стоимости тестирования по сравнению с проверкой каждого модуля по мере его написания. Успех зависит от того, насколько сложен каждый модуль, и от квалификации коллектива разработчиков. В условиях, когда функ- циональные средства программного продукта отличаются особенной простотой, в нем может оказаться лишь небольшое число ошибок, обнаруженных благодаря тестированию «извне». В случае более сложных функциональных возможностей программного продукта ошибки могут быть так глубоко запрятаны в программ- ном коде, что могут возникнуть большие затруднения в подтверждении пра- вильности значений конкретных атрибутов из-за размерности скомпонованного комплектующего модуля. Этот подход полезен при тестировании тех компонен- тов, для которых реализация тестовых драйверов требует существенных усилий. Как выполняется тестирование? Как будет выполняться тестирование? Основные подходы к тестированию программного обеспечения основаны на спецификации и реализации (см. рис. 3.9).
Глава 3. Планирование работ по тестированию Знание только спецификации Знание специфика- ции и реализации Pl/ICyHOK 3.9. Континуум, показывающий, как следует выполнять тестирование программного продукта Спецификация модуля программного продукта определяет, что этот модуль должен делать, т.е. она описывает допустимые наборы входных данных, подавае- мых на вход модуля, включая ограничения на то, как многократные вводы дан- ных должны соотноситься друг с другом, и какие выходные данные соответству- ют различным наборам входных данных. Реализация модуля программного продукта есть выражение алгоритма, порождающего выходные результаты для различных наборов входных данных с соблюдением требований спецификации. Короче говоря, спецификация указывает, что делает модуль программного про- дукта, а реализация показывает, как модуль программного продукта делает то, что он, собственно, делает. Полный учет требований спецификации обеспечива- ет гарантию того, что программный продукт выполняет все, что от него требует- ся. Полный учет требований к реализации гарантирует, что программный про- дукт не будет делать того, что от него не требуется. Спецификация играет важную роль в тестировании. Нам потребуется, чтобы для множества компонентов программного продукта были написаны специфика- ции, обеспечивающие разработку и тестирование, включая спецификации сис- тем, подсистем и классов. Вполне оправданной представляется идея построения тестовых примеров для того или иного компонента только на основе его специ- фикации. В то же время при тестировании некоторых компонентов на основе их реа- лизации очень важно быть уверенным в том, что тестовый набор был подобран с максимальной тщательностью. Например, в случае компонентов с высокой сте- пенью риска мы хотим быть уверенными в том, что буквально каждый бит про- граммного кода работает корректно. Наряду с автономным тестированием компонентов мы хотим провести тести- рование взаимодействий между различными компонентами. Этот вид тестирова- ния традиционно относится к комплексным испытаниям системы, которые имеют место, когда компоненты интегрируются в систему более высокого уровня. Цель комплексных испытаний заключается в обнаружении отказов, возникающих вследствие ошибок в интерфейсах или в силу неверных предположений относи- тельно интерфейсов. Комплексные испытания имеют особо важное значение в объектно-ориентированных системах в силу наличия полиморфизма включения, который реализуется с использованием динамического связывания. Для итеративных, инкрементальных процессов комплексные испытания про- водятся по принципу непрерывности. Они начинаются с того, что тестируются примитивные объекты, которые агрегируются в более сложные объекты, и про- двигаются в направлении усложнения объектов, которые представляют собой
Главв 3. Планирование рвбот по тестированию подсистемы, которые, в свою очередь, интегрируются в систему. В главе 6 будут рассматриваться некоторые методы построения эффективных тестовых примеров для тестирования взаимодействий. АДЕКВАТНОСТЬ ТЕСТОВЫХ СЛУЧАЕВ В практической и экономической перспективе исчерпывающее тестироввнив про- граммного продукте невозможно. Привмлемвя цель тестироввния звключается в рвзработкв доствточного числе тестовых случаев с целью убедиться в том, что в обычном режиме использования программного продукте и в ситуациях, сопряженных с опасностью для жизни людей, его отказы не возниквют. Эти рассуждения подводят нвс к принципу адекввтности тестироввния прогрвммного продукте. Тестироввть следует в достеточных объвмвх, чтобы быть болев-менее уверенным в том, что прогрвммный продукт функционирует в соотаетствии с предъявляемыми к нему требованиями. Адвкввтность можно измерить, воспользовввшись для этой цели понятием покры- тия. Покрытие может быть измерено по меньшей мере двумя способами. Один из них звключавтся в подсчете количества требоввний, сформулироввнных в специфи- кации, которое подверглось тестироввнию. Вполне понятно, что некоторые требовв- ния требуют прогонв нескольких тестовых случаев. Другой способ заключвется в подсчете выполненных компонентов программного продукта в результате прОГона тестового нвборв. Нвбор тестов можно считвть адекввтным, если определенная часть строк исходного кода, или, возможно, исполняемых аетввй исходного коде, были выполнены, по крайней мере, один раз во время прогона тестового нвборв. Эти два способа измерения отрвжают два базовых подходе к тестированию. Один из них основывается нв том, что должен выполнять прогрвммный продукт. Другой подход основан на том, квк фвктически рвбответ прогрвммный продукт. Чтобы тестирование было вдвкватным, потребуется звдействовать обв подходе. При функциональном тестировании, которое также называют тестированием в соответствии со спецификацией или тестированием черного ящика, постро- ение тестовых примеров производится, в основном, в соответствии со специфика- цией и нв зависит от того, как реализовано программное обеспечение. Этот подход полезен на всех уровнях тестирования, поскольку его преимущество перед осталь- ными подходами заключается в том, что тестовые случаи могут быть разработвны еще до того, как начнется кодирование программ. Однако, эффективность функци- онального тестирования существенно зависит от качества спецификации и способ- ности разработчиков тестового набора корректно ее интерпретировать. При структурном тестировании, которое также называют тестированием в со- ответствии с реализацией или тестированием белого ящика, построение тес- товых случаев производится на основе программного кода, представляющего собой реализацию программного продукта. Выходные данные каждого тестового случая должны быть определены спецификацией программного продукта, однако входные нвборы данных могут быть выбраны на основании внализа самого программного кода, который выполняется с целью определить различные значения, обусловлива- ющие выбор той или иной ветви исполнения программы. Основное преимущество данного подхода заключается в увеличении покрытия. Основной недостаток состоит в том, что если программист не реализует всю спецификацию, то соответствующая часть функциональных возможностей тестирования не пройдет. Наилучшим образом адекватное тестирование реализуется сочетанием обоих упомя- нутых подходов. Функциональный подход обеспечивает более интенсивное тестиро- вание, однако структурное тестирование укрепляет уверенность в том, что про- граммный продукт не будет делать ничего из того, чего не должен делать.
Глава 3. Планирование работ по тастированию Какие объемы тестирования следует считать адекватными? В общем случае на этот вопрос невозможно ответить; на него трудно отве- тить и в случае какого-либо конкретного компонента программного продукта.5 Чтобы правильно ответить на этот вопрос, потребуется рассмотреть очень многие аспекты. Одним из таких аспектов является ожидаемый срок службы программ- ного продукта. Прикладные программы, которые преобразуют данные старого приложения в форму, необходимую для нового, редко требуют тестирования в больших объемах. Необходимо также учитывать, является ли приложение, в со- став которого входит тестируемый программный продукт, жизненно важным, что обусловливает необходимость его всестороннего тестирования. Обратите внима- ние, что это есть пример решения относительно того, насколь тщательно требу- ется тестировать отдельные компоненты программного продукта. В данном случае адекватность тестирования можно рассматривать как целесо- образность продолжать тестирование до тех пор, пока выявление отказов урав- новешивается повышением качества программного продукта. Другая точка зре- ния учитывает стандарты, действующие в предметной области, которой принадлежит приложение. Тестирование призвано подтвердить тот факт, что те- стируемый программный продукт соответствует этим стандартам — например, между стандартами качества, действующими в отраслях промышленности, про- изводящих лекарства и мебель, существует очевидная разница. Различные уровни адекватного тестирования отображены на континууме, представленном на рис. 3.10, который охватывает случаи тестирования от ника- кого до минимального покрытия, когда мы выбираем только небольшое число тестовых случаев, и далее до ситуаций исчерпывающего тестирования, когда осуществляется прогон всех возможных тестовых случаев. Компании, а иногда и отдельные проекты, выбирают стратегию тестирования на основании того места, которое они занимают в континууме и которое их вполне устраивает. Объем необходимого тестирования следует определять, исходя из краткосроч- ных и долгосрочных целей проекта и в соответствии с особенностями разраба- тываемого программного продукта. Мы часто используем термин «покрытие», когда говорим об адекватности тестирования. Покрытие — это мера полноты ис- пользования возможностей программного компонента тестовым набором. Тестирование не выполняется Исчерпывающее тестирование Рисунок 3.10. Континуум, показывающий, как в каких объемах следует выполнять тестирование программного продукта Не следует путать рассматриваемый континуум с приведенным ранее, которым мы воспользова- лись, дабы решить, какой компонент программного продукта подлежит тестированию.
Глава 3. Планированиа работ по тестированию Разные специалисты прибегают к использованию различных мер, например, одна из мер может быть основана на том, задействована ли каждая строка про- граммного кода продукта хотя бы раз при прогоне конкретного тестового набора, в то время как в основу другой меры положено количество требований специ- фикации, проверенных данным тестовым набором. Соответственно, покрытие может быть выражено, например, такой фразой как «75% было выполнено во время прогона данного теста» или фразой «для проверки каждого требования спецификации построен один тестовый случай». Мы полагаем, что меры покрытия программного продукта тестовыми случая- ми сначала должны быть сформулированы в терминах требований специфика- ции и могут меняться в зависимости от приоритетов и целей проекта. Если, на- пример, эти требования сформулированы в терминах случаев использования, покрытие измеряется количеством случаев использования и числом сценариев, построенных для каждого случая использования. Покрытие, измеряемое в тер- минах реализации, полезно при оценке полноты тестового набора, ориентиро- ванного на проверку выполнения требований спецификации. Если какие-либо программные коды не выполняются, то тестировщики должны провести с пользователями определенную работу, чтобы выяснить, каких тестовых случаев не хватает или не выполняет ли программный продукт функций, не предусмот- ренных спецификацией. Анализ рисков в процессе тестирования применяется для определения уров- ня детализации и суммарного времени, затрачиваемого на тестирование конк- ретного компонента. Например, на тестирование классов, которые обладают цен- ными для приложения свойствами и предназначены для многократного использования, отводится больше времени, чем для классов, предназначенных для использования в качестве прототипов. Более-менее подходящая шкала ком- понентов, элементы которой расположены в порядке возрастания степени риска, имеет вид: Компоненты-прототипы Производственные компоненты Библиотечные компоненты Компоненты оболочки Признание наличия различных уровней риска приводит к появлению раз- личных уровней адекватного покрытия компонентов тестами. Ниже будут опи- саны алгоритмы, которые управляют тестированием конкретных программных продуктов. Для этих алгоритмов характерно то, что мы называем эффектом рео- стата, благодаря которому один и тот же алгоритм обеспечивает различные уровни тестового покрытия. Например, в разделе «Ортогональная матрица тести- рования» главы 6 обсуждается тестирование различного числа сочетаний значе- ний атрибутов.
Глава 3. Планирование работ по тестироаанию Роли в процессе тестирования Нам удалось выявить несколько важных ролей, имеющих отношение к тести- рованию проекта. От каждой из них в значительной степени зависит успех про- цесса тестирования проекта. В задачу плана проведения испытаний входит рас- пределение ответственности за тестирования между этими ролями. В рамках каждой из ролей исполнители должны распределять собственный временной ресурс между тестированием и другими своими обязанностями. План разработок во многом регламентирует план работ по тестированию. В такой об- становке обычно тестирование терпит ущерб, если отсутствует четкое описание уровней адекватного тестового покрытия и обязательств в отношении качества. Наш собственный опыт показывает, что чем большую активность в проекте про- являют специалисты по комплексным и системным испытаниям, тем выше веро- ятность, что под тестирование будут выделены соответствующие ресурсы. Сейчас опишем каждую из ролей. Каждый из исполнителей может выступать в любой роли. Один человек может выступать в нескольких ролях, однако сле- дите за тем, чтобы роли не совмещались. Тестировщик классов На тестировщика классов возлагается ответственность за автономное тестиро- вание классов по мере их разработки. Одна из задач процесса планирования заключается в координации усилий всех этих специалистов. Специалисты по проектированию программных средств на основании плана тестирования проек- та должны определить, на кого будет возложена роль тестировщика, какой уро- вень покрытия считать адекватным и как будут спланированы работы по тести- рованию. Планирование приобретает особую важность при наличии большого числа артефактов, подлежащих тестированию, и в условиях потребности укоро- ченного производственного цикла. Если эту роль взяли на себя разработчики, как это часто имеет место в реальных условиях, то на этот вид деятельности должно быть отведено определенное количество времени. По нашему мнению, это условие вполне очевидно, однако многие менеджеры при планировании ра- бот попросту игнорируют этот этап деятельности. Специалист по комплексным испытаниям Специалист по комплексным испытаниям (или тестировщик целостности) несет ответственность за тестирование некоторого набора объектов, которые, будучи разработанными различными источниками, такими как, например, два разных коллектива разработчиков, объединяются в единое целое. На них возлагается от- ветственность за тестирование достаточно представительного набора функцио- нальных средств, которое при успешном исходе служит гарантией того, что раз- личные компоненты, разработанные разными коллективами разработчиков и/или приобретенными у различных сторонних поставщиков, будут работать должным образом. Эта роль приобретает особую важность в тех проектах, в которых за-
Глааа 3. Планирование работ по тестированию действованы крупные и сложные структуры, достаточно далекие от завершения на момент тестирования. Люди в этой роли должны быть специалистами как в области разработки программных продуктов, так и в области тестирования. Системный тестировщик Системный тестировщик должен обладать хорошими знаниями прикладной области, поэтому на него возлагается ответственность за независимую проверку соответствия окончательной редакции приложения системным требований. Сис- темные тестировщики рассматривают проект с точки зрения пользователей. Очень важно учитывать эту точку зрения уже на ранних стадиях планирования работ над проектом. Один или несколько тестировщиков должны брать участие в моделировании случаев использования и начинать работы по идентификации и построению тестовых примеров в процессе формулирования требований. Руководитель испытаний Руководитель испытаний (или менеджер тестирования) осуществляет управле- ние процессом тестирования. Это может быть роль для одного менеджера, кото- рую он совмещает с другими обязанностями, либо роль, рассчитанная на не- скольких человек, которые назначаются на нее на период испытаний. Роль руководителя испытаний в данном случае мало чем отличается от роли менед- жера в любом другом виде деятельности. В обязанности менеджера входит обес- печение проекта ресурсами, координация и эффективное использование выде- ленных ресурсов. На это лицо часто возлагается «табличное» руководство работой некоторого коллектива разработчиков. Иными словами, разработчики отчитываются о проделанной работе не только перед руководителем разработок, но и перед руководителем испытаний. В этой роли проектировщики могут ока- зать существенную помощь в построении тестовой инфраструктуры. Подробное описание набора видов деятельности в тестировании На рис. 3.11 представлен краткий обзор видов тестовой деятельности для каждой фазы, которые мы определили для нашего процесса разработки. По мере обсуждения соответствующих методов в последующих главах, они будут уточ- няться. Планирование как вид деятельности Сейчас мы хотим обсудить процесс планирования работ по тестированию. Мы рассмотрим набор плановых документов, которые помогают представить ин- формацию, такую как специальные тестовые примеры, в требуемом порядке. Эти документы будут соотноситься с тем, как и когда работы по тестированию пла- нируются на выполнение.
Глава 3. Планированиа работ по тастироаанию Программный продукт Компоненты (Под)системы На основе модели На основе реализации На основе модели На основе реализации I Анализ предметной области I Модель класса предметной области Убедиться в том, что можно построить объекты по классам, представлен- ным в модели, а также в том, что можно устанав- ливать отношения. Убедиться в том, что все объекты, необходимые для построения подсистемы, доступны и функционируют корректно. Динамичес- кая модель предметной области Убедиться в том, что все состояния объекта достижимы, а предусловия могут быть проверены. Убедиться в том, что все состояния подсистемы достижимы, а предусловия могут быть проверены. Специфика- ция требова- ний предмет- ной области, например, требования к случаям использова- ния Убедиться в том, что квалифицированный пользователь не может отыскать случаев исполь- зования, не представлен- ных в модели. Провести проверку на совместимость на полном наборе случаев использо- вания; провести проверку на полноту под контролем квалифицированного пользователя. | Анализ приложения | Модель классов приложения Убедиться в том, что можно построить объекты по классам, представлен- ным в модели, а также в том, можно ли устанавли- вать отношения. Убедиться в том, что все объекты, необходимые для построения подсистемы, доступны и функционируют корректно. Динамическая модель приложения Убедиться в том, что все состояния достижимы, а предусловия могут быть проверены. Убедиться в том, что все состояния подсистемы достижимы, а предусловия могут быть проверены. Специфика- ция требова- ний прило- жения, например, требования к случаям использова- ния Убедиться в том, что квалифицированный пользователь не может отыскать случаев исполь- зования, не представлен- ных в модели. Провести проверку на совместимость полного набора случаев использо- вания; провести проверку на полноту под контролем квалифицированного пользователя. Рисунок 3.11. Краткий обзор видов тестовой деятельности.
Глава 3. Планирование работ по тестированию Программный продукт Компоненты (Под)системы На основе модели На основе реализации На основе модели На основе реализации Проектирование на уровне архитектуры Убедиться в том, что новые объекты могут быть построены из новых классов, представлен- ных в модели, а отношения могут быть установлены. Убедиться в том, что проектный шаблон был использован правильно. Убедиться в том, что все состояния достижимы, а предусловия могут быть проверены. Убедиться в том, что каждое сообщение, полученное компонентом, требует выполне- ния операции в своем интер- фейсе, а также в том, что каждый компонент находится в состоянии, в котором возможен прием сообщения. Убедиться в том, что отправи- тель каждого сообщения знаком с получателем. Убедиться в том, что каждый компонент может установить отношение, необходимое для обмена данными. Убедиться в том, что могут быть выполне- ны требования для получения результатов из диаграмм взаимо- действия. Убедиться в том, что компо- ненты подсистемы установили необходимые знакомства, внутри подсистемы и без ее участия. Убедиться в том, что подсистема способна поддерживать собственную спецификацию. Рисунок 3.11. Краткий обзор видов тестовой деятельности (продолжение).
Глава 3. Планирование работ по тестированию Программный продукт Компоненты (Под)системы На основе модели На основе реализации На основе модели На основе реализации Детальное проектирование | Выполнить проверку, независимую от приложения. Выполнить проверку, независимую от приложения. Проверить структуру на соответствие архитектуре. Проверить операцию на соответствие диаграмм взаимодей- ствия. Реализация классов | Готовые классы Проверить реализацию на соответствие спецификации классов. Проверить взаимо- действие классов с помощью тестовых случаев, в основу которых положены отношения, отобра- женные в диаграмме классов. | Реализация приложения | Готовое приложение Проверить реализа- цию, воспользовав- шись тестовыми случаями, разработан- ными с учетом особенностей случаев использования. Рисунок 3.11. Краткий обзор видов тестовой деятельности (продолжение).
Глава 3. Планированиа работ по тестированию Эти плановые документы суть рабочие документы. После каждого прираще- ния, а иногда и после специальных итераций, эти документы пересматриваются и подвергаются ревизии. Корректируются степени рисков, равно как приоритеты и временные графики. Планирование работ по тестированию Планы тестирования классов передаются на усмотрение разработчика по мере того, как тестирование становится целесообразным или необходимым. Тестиро- вание классов имеет смысл выполнять в процессе написания программных ко- дов, когда разработчик желает знать, какие детали упущены, или убедиться в правильности некоторой части реализации. Тестирование классов становится не- обходимым, когда некоторый компонент нужно добавить к базовому программ- ному коду. Полностью разработка класса может быть не завершена, но поведе- ние, которое он представляет, должно быть правильным и должно быть представлено в завершенном виде. Комплексные испытания обычно планируется проводить через заданные ин- тервалы времени, обычно в конце крупных итераций, которые означают завер- шение очередного приращения, и/или перед выпуском очередной версии программного продукта. С другой стороны, интеграция может быть продолжаю- щимся процессом, с высокой степенью итераций, который повторяется в конце каждого рабочего дня. Циклы комплексных испытаний могут быть спланированы так, чтобы они совпадали с поставками крупных сторонних производителей программного обеспечения, например, новых версий базовых структур. Системные тесты будут выполняться на крупных комплектующих модулях в заданные интервалы времени на протяжении всего цикла разработки проекта. Этот график обычно описан в плане выполнения проекта, поскольку часто тре- буется координация усилий с отдельными организациями, которые, возможно, оказывают свои услуги одновременно большому числу проектов. Оценка Оценка ресурсов, т.е. денежных средств, времени и персонала, которые по- требуются для поддержки разрабатываемых планов, является частью этих пла- нов. Это нелегко сделать, и у нас для этого нет «магической» формулы. В этом разделе обсуждаются такие факторы, как уровень покрытия, тип предметной об- ласти, необходимое оборудование, организационная модель и затраты труда на тестирование, которые учитываются при планировании. Уровень покрытия Чем выше необходимый уровень покрытия, тем больше потребуется для этого ресурсов. В литературе даются разные оценки количества программных кодов, необходимых для поддержки тестирования. Бейцер в [Beyz90] оценивает их ко- личество на уровне от 2% до 80% от размера приложения. Нам удалось полу- чить сравнительно точную оценку после того, как мы приняли каждый случай
Глава 3. Планирование работ по тестироеению использования в качестве единицы измерения. Получив оценку трудозатрат на каждый случай использования (возможно, через создание прототипа), можно оп- ределить трудозатраты на всю систему. Некоторые случаи использования отли- чаются от остальных большей областью видимости и большим уровнем абстрак- ции. Выберите случаи использования, которые характеризуются примерно одинаковым уровнем детализации в моделях, и используйте их для оценки. Если два каких-нибудь случая использования входят в некоторый обобщенный случай использования, расширяя его, то следует воспользоваться либо двумя бо- лее специальными случаями использования, либо одним обобщенным, но никак не обоими типами случаев. Тип предметной области Довольно часто программное обеспечение, ориентированное на сложную об- работку данных, переносит эту сложность в программную логику, при этом ввод данных достаточно прост. С другой стороны, системы, осуществляющие обра- ботку крупных массивов данных, не отличаются особой сложностью программ- ной логики, однако построение тестовых примеров потребует значительных уси- лий. Величина трудозатрат для построения завершенного тестового примера, включая полные наборы входных данных и правильные наборы выходных дан- ных, меняется в широких пределах. Простая программа, которая обращается с запросами к крупной базе данных, требует много времени для построения соот- ветствующих наборов данных и много времени для проверки правильности от- вета. Требуемые технические средства Системное тестирование должно проводиться в среде, максимально близкой к рабочей среде. Даже некоторые аспекты тестирования классов могут потребовать специальной аппаратной среды или моделирования аппаратных средств. Затраты на установку и обслуживание аппаратных средств или построение имитатора должны быть включены во все сметы и калькуляции. Организационная модель Мы обсудили две схемы, которые широко используются для комплектования процессов тестирования. Наш опыт показывает, что чем большей независимос- тью пользуются тестировщики из организации, осуществляющей разработку, тем выше качество тестов. В то же время, подобная независимость требует более продолжительной кривой обучения и, следовательно, увеличения затрат. По об- щему признанию, один независимый тестировщик может справиться с результа- тами труда не более чем двух-трех разработчиков. И наоборот, сильная привязка тестировщиков к организации, выполняющей разработку (или привлечение специалистов из коллектива разработчиков к тес- тированию), уменьшает затраты времени на ознакомление с системой. Специ- фикации редко когда представлены в завершенном виде или соответствуют пос-
Глааа 3. Планированив рвбот по твстированию ледним требованиям. Если тестировщиком будет лицо, принимающее участие в обсуждении решений, то такой тестировщик лучше понимает скрытый смысл требований спецификации. В то же время таким тестировщикам труднее отстаи- вать собственное мнение и сохранять объективность, если они вовлечены в раз- работку тестируемого программного продукта. Рассмотрите возможность применения дружественного подхода к тестирова- нию классов. Он обеспечивает высокую объективность оценок, благодаря чему тестирование становится более эффективным. Вместо того чтобы заставлять раз- работчиков тестировать свои собственные классы, сформируйте дружественные группы разработчиков. Два разработчика обмениваются между собой программ- ными кодами и выполняют тестирование. Поскольку тестировщик является од- новременно и разработчиком, который разрабатывает близкие по содержанию программы, он быстро входит в курс дела и приступает к тестированию гораздо быстрее, чем член независимого коллектива тестировщиков, который сначала должен тщательно изучить контекст. Оценка трудозатрат на тестирование Методы тестирования почти всегда опираются на предысторию, что позволя- ет выступать с прогнозами. Мы не будем растрачивать пространство данной книги на обсуждение этих методов. На рис. 3.12 представлена очень простая форма, предназначенная для отчета за каждый час времени, затраченного на вы- полнение различных видов тестирования. По мере изучения материала данной книги мы будем давать все более подробные инструкции по заполнению различ- ных граф этой формы. На данный момент можно подвести итог по большинству пунктов этой фор- мы, воспользовавшись предысторией с целью определения стоимости разработки отдельных классов. Из списка, представленного на рис. 3.12, видно, какие клас- сы требуется построить: Для каждого класса приложения, который нужно тестировать в автоном- ном режиме, построить класс РАСТ.6 Для каждого случая использования построить класс PAST.7 Определить, какое количество классов необходимо для построения рас- сматриваемой инфраструктуры. Общее количество классов, помноженное на трудозатраты, дает суммарные трудозатраты на тестирование классов. Вопросы планирования рассматриваются в разделе «Затраты на планирование». 6 PACT (Parallel Architecture for Component Testing) — это параллельная архитектура для тестирова- ния компонентов. Она будет рассматриваться в главе 7. 7 PAST (Parallel Architecture for System Testing) — это параллельная архитектура для тестирования системы. Она будет рассматриваться в главе 9.
Главв 3. Планирование работ по тестированию Форма подсчета трудозатрат на тестирование Количество человеко-часов, необходимых для составления плана тестирования класса на уровне разработчиков Количество человеко-часов, необходимых для построения плана тестирования класса РАСТ на уровне разработчиков Число человеко-часов, необходимых для построения силами штатных работников среды тестирования Количество человеко-часов, необходимых для планирования силами тестировщиков системных испытаний Количество человеко-часов, необходимых для построения силами тестировщиков класса PAST. _ Общие трудозатраты тестировщиков в человеко-часах Рисунок 3.12. Форма подсчета трудозатрат на тестирование Процесс тестирования игры «Кирпичики» В этом разделе будет показано, как работают описанные выше пять оценивае- мых факторов применительно к исследуемым случаям использования: Кто выполняет тестирование? Обязанности по тестированию будут рас- пределены между двумя авторами этой книги. Сайкс (Sykes) выполняет основную часть реализации, и в силу этого обстоятельства он будет осу- ществлять тестирование классов и комплексные испытания. Макгрегор (McGregor) написал случаи использования и выполнил большую часть проектирования на верхнем уровне. Он будет создавать тестовые примеры на базе случаев использования и выполнять их прогон, как только будет готова реализация системы. Сайкс будет координировать тестирование мо- делей. Какие компоненты программного обеспечения подвергнутся тестированию? Те- стироваться будут основные базовые классы. Классы высокого уровня, ко- торые построены на базе примитивных классов, обросли таким большим числом взаимодействий, что при тестировании они будут рассматриваться как кластер. Система в ее окончательном виде будет тестироваться как за- вершенное приложение. В какой момент следует выполнять тестирование? Тестирование классов будет многократно производиться на протяжении циклов разработки. Групповое тестирование классов высокого уровня также будет повторяться на протяжении циклов разработки, но эти испытания не могут быть нача- ты до начала работ со вторым комплектующим модулем, т.е. пока не бу- дут завершены примитивные классы в первом комплектующем модуле. Системное тестирование начнется сразу же после того, как станет доступ- ной исходная версия системы по завершении работ над первым комплек- тующим модулем.
Глава 3. Планирование работ ло тестированию Как будет выполняться тестирование? Тестовые примеры будут построены в виде методов класса. Для каждого класса программного продукта будет построен класс тестирования. Тестирование случаев использования будет выполняться специалистом по системам, и никакие виды автоматизации при этом применяться не будут. Это требует многократного выполнения сеансов игры. Какой объем тестирования можно считать адекватным для конкретного ком- понента? Классы будут подвергаться тестированию до тех пор, пока каж- дый общедоступный метод не будет вызван хотя бы раз. Мы не будем стремиться проверить каждую возможную комбинацию значений парамет- ров. Тестовые примеры, берущие свое начало от случаев использования, не могут покрыть всех результатов. Шаблоны документов Мы обсудим план тестирования проекта, план тестирования компонентов, план комплексных испытаний, план тестирования случаев использования и план системных испытаний. Рисунок 3.13 служит иллюстрацией взаимозависимостей перечисленных планов. Каждая стрелка на рисунке означает, что документ, на который она указывает, включен по ссылке в документ, от которого исходит эта стрелка. План тестирования проекта Рисунок 3.13. Взаимозависимость планов тестирования
Глава 3. Планирование работ по тастированию Мы представим это в формате шаблона. Такой подход полезен в силу не- скольких причин. За исключением плана системного тестирования, все осталь- ные документы существуют в нескольких экземплярах. Шаблон гарантирует не- противоречивость формы и содержания этих независимых, но в то же время родственных по содержанию документов. Чем лучше документ вписывается в тот или иной шаблон, тем меньше усилий затрачивает разработчик при созда- нии его многочисленных экземпляров. Использование шаблонов к тому же уп- рощает процесс контроля, поскольку каждый документ исполнен в одном и том же стиле, что позволяет быстро ознакомиться с его конкретным содержанием. В схеме плана тестирования, рекомендованного стандартом IEEE и представ- ленного на рис. 3.14, приводится список основных разделов, которые должны присутствовать в каждом плане тестирования, независимо от его уровня. Мы хотим рассмотреть те из них, которые имеют наибольшее значение в среде раз- работки интерактивного, итеративного объектно-ориентированного программно- го обеспечения. В последующих планах тестирования мы не будем называть раз- делы в строгом соответствии с этой схемой, однако включим в них информацию об основных требованиях. Приводимые ниже пункты плана тестирования имеют особую важность: Свойства, не подлежащие тестированию — для тестирования на уровне классов. В этом разделе содержится информацию Н1Т-анализа (Hierarchical Incremental Analysis — Иерархический инкрементный анализ, см. главу 7). Эта информация содержит данные о свойствах, которые уже подверглись тестированию и не нуждаются в повторном тестировании, и о свойствах, разработка которых запланирована на последующие итерации или на последующие комплектующие модули. Критерии приостановки тестирования и требования для его возобновления — тестирование приостанавливается, когда показатели тестирования выходят на недопустимый уровень, т.е. когда количество обнаруженных за один час ошибок опускается ниже критерия, установленного для данного разде- ла, что, собственно, и приводит к приостановке тестирования. Этот раздел имеет особую важность для проектов, разрабатываемых в итеративном ре- жиме. Обычно мы определяем один набор критериев для циклов разработ- ки на ранней стадии и другой набор — для более поздних циклов разра- ботки. В случае итеративной разработки проекта за критерий возобновления принимается возврат цикла разработки в контрольную точ- ку. Риски и сопряженность признаков — риск в этом контексте обозначает по- тенциальные проблемы, сопряженные с проведением тестирования. В их число входят возможные ошибки в правильных ответах, представленных в виде крупных наборов данных, а также вероятность того, что на различ- ных платформах будут получены разные результаты, хотя только лишь не- которые случаи подверглись тестированию.
Глава 3. Планированиа работ по тастироаанию 1.0 Введение 2.0 Тестовые элементы 3.0 Тестируемые свойства 4.0 Свойства, не подлежащие тестированию (за один цикл) 5.0 Стратегия тестирования и подход 5.1 Синтаксис 5.2 Описание выполняемых функций 5.3 Аргументация в пользу испытаний 5.4 Ожидаемые выходные данные 5.5 Специально оговариваемые исключения 5.6 Зависимости 5.7 Критерии успешного/неудачного выполнения тестирования 6.0 Критерии успешного прохождения/неудачи полного тестового цикла 7.0 Критерии вхождения/критерии выхода 8.0 Критерии приостановки испытаний и требования к их возобновлению 9.0 Тестовые комплектующие узлы/средства передачи данных о состоянии 10.0 Задачи тестирования 11.0 Требования к аппаратным средствам и программному обеспечению 12.0 Обнаружение проблем и назначение ответственных за коррекцию 13.0 Кадровое обеспечение и необходимость обучения/назначения на должности 14.0 График работ по тестированию 15.0 Риски и сопряженность признаков 16.0 Официальное одобрение Рисунок 3.14. Шаблон стандартного типа тестирования IEEE829. План тестирования проекта Назначением этого проекта является формулирование стратегии тестирова- ния, которая должна применяться к проекту. Он должен определить этапы раз- работки, на которых следует выполнять тестирование, частоту выполнения про- цедур тестирования, а также указать, на кого возложена ответственность за этот вид деятельности. План тестирования проекта может быть независимым документом, он может также быть включен в общий план работ по проекту или в план обеспечения ка- чества. Поскольку формат этого документа часто меняется, а его содержание до- статочно гибкое, мы приводим здесь примеры таблиц, в которых такого рода ин- формация представляется в обобщенном виде. В таблице на рис. 3.15 дается общая информация о видах деятельности, ко- торые необходимо выполнить, показано, как часто должен выполняться тот или иной вид деятельности, и указан ответственный за данную фазу тестирования.
Глава 3. Планироаание работ по тастироеанию Более подробная информация по всем показателям представлена в детализиро- ванном плане для данного уровня. Вторая таблица, которая приводится на рис. 3.16, назначает каждой фазе спе- циальную стратегию тестирования. Шаблон плана тестирования проекта - часть I Проект ______________________________________________ Ответственный исполнитель Уровень тестирования Виды деятельности Частота тестирования Ответственный исполнитель Компонент Выбор тестовых примеров По мере готовности компонентов Разработчик компонента Написание классов РАСТ Тестировщик компонента Интеграция Выбор тестовых примеров До выпуска комплектующего модуля Группа интеграции модулей Система Выбор тестовых примеров по случаям использования До сдачи какого-либо варианта системы внешнему заказчику Отдел обеспечения качества Построение классов PAST Рисунок 3.15. Шаблон плана тестирования проекта — часть I Шаблон плана тестирования проекта - часть II Уровень тестирования Стратегия тестирования Уровень профиля пользователя Критерий покрытия Компонент Высокий Средний Низкий Интеграция Высокий Средний Низкий Система Высокий Средний Низкий Рисунок 3.1 В. Шаблон плана тестирования проекта — часть II
Глава 3. Планирование работ по тестированию Мы предложим несколько стратегий тестирования в соответствующих главах с тем, чтобы вы смогли сделать свой выбор. В этой таблице зафиксированы так- же проектные стандарты адекватного тестирования для каждого уровня риска в рамках указанных выше трех фаз. План тестирования компонентов Назначение плана тестирования компонентов заключается в определении об- щей стратегии тестирования и конкретных тестовых случаев, которые будут использоваться для тестирования того или иного компонента. Для каждого ком- понента составляется один план, в котором должна быть обоснована необходи- мость автономного тестирования этого компонента. Мы приводим здесь шаблон, который успешно применяли в своей работе. В этот шаблон включены два типа руководящей информации: проектные критерии и проектные процедуры. Они включены для того, чтобы служить ненавязчивым напоминанием, а также во из- бежание необходимости разработки плана тестирования компонентов, в котором обобщается вся информация о тестировании компонентов данного проекта. Проектными критериями являются согласованные стандарты, определяющие, на- сколько тщательно должно быть выполнено тестирование каждого компонента. Например, проектные критерии могут требовать 100%-ного тестирования по- стусловий методов модификатора. Эти критерии должны содержать дальней- шие подробности о критериях покрытия, определенных в плане тестирования проекта. Проектные процедуры выявляют методы, которые были выбраны как лучшие способы решения той или иной конкретной задачи. Например, построе- ние класса РАСТ (см. главу 7) для каждого компонента, который будет протес- тирован в проектной процедуре. Эти процедуры содержат подробности о страте- гиях тестирования, которые были зафиксированы в плане тестирования проектов. Ниже приводятся краткие комментарии по каждому разделу шаблона. Сам шаблон находится на рис. 3.17. Мы не будем описывать разделы, в которых просто фиксируется такая информация, как, например, наименование того или иного компонента. Курсивом выделяется фактически внесенная в шаблон ин- формация. Цели построения класса. Разработчик должен заменить этот параграф списком целей построения компонента, упорядоченным по возрастанию приоритетов. Например, компонент представляет собой элемент базовой структуры приложе- ния и предназначен для использования в качестве высокоуровневой абстракции, от которой порождаются более конкретные варианты. Руководящие требования по проверке. Проектные критерии: Все 100% про- граммных продуктов, связанных с наиболее ответственными компонентами, подвер- гаются тестированию. 75% компонентов, связанных с менее ответственными ком- понентами, подвергаются тестированию. Библиотечные компоненты проходят дополнительную проверку качества. Проектная процедура: Анализ рисков использу- ется для упорядочивания компонентов класса по приоритетам с целью их проверки и тестирования.
Глава 3. Планирование работ по тестированию Построение и сохранение тестовых наборов. Разработчик должен заменить этот параграф информацией о: результатах применения HIT-анализа и подробностях использования про- цесса РАСТ для построения классов тестового драйвера (см. главу 7) запланированном предельном сроке разработки тестовых случаев спецификации тестового драйвера относительном числе тестовых случаев в каждой категории и приоритетах каждой из этих трех категорий. Функциональные тестовые случаи. Разработчик должен заменить этот параграф информацией о: тестовых случаях, разработанных в соответствии со спецификацией методе инвариантов классов сколько различных «типов» объектов подлежат тестированию. Количество этих типов зависит от начального состояния объекта. Структурные тестовые случаи. Разработчик должен заменить этот параграф информацией о: тестовых случаях, разработанных для покрытия программных кодов, и о процессе ревизии программных кодов как использовать требуемые инструментальные средства, поддерживающие покрытие тестами объектов тестирования. Тестовые случаи, построенные на базе состояний классов. Разработчик должен заменить этот параграф информацией о представлении состояний класса. Вос- пользуйтесь диаграммами состояний, если таковые имеются. Тестовые случаи для тестирования взаимодействия компонентов. Разработчик должен заменить этот параграф информацией о том, какие сообщения подверга- ются тестированию на базе процесса OATS (Orthogonal Array Testing System — система тестирования с использованием ортогональной матрицы), обеспечиваю- щего выборку образцов (см. главу 6). План тестирования случаев использования Назначение этого плана заключается в описании, какие испытания на уровне системы строятся на базе одного конкретного случая использования. При помо- щи ссылок эти планы включаются в план комплексных испытаний и в план сис- темного тестирования. На рис. 3.18, 3.19 и 3.20 представлены фрагменты шабло- на плана тестирования случаев использования. Другие фрагменты приводятся в главе 9. Планы тестирования можно строить в соответствии с модульным принципом по одному и тому же образцу как зависимости между «частичными» случаями использования.
Глава 3. Планирование работ по твстированию Шаблон плана тестирования компонента Название компонента _________________________________ Ответственный исполнитель Имя разработчика Задания на тестирование класса Проектная процедура: Тестовые случаи предназначены для тестирования компонентов в соответ- ствии с возложенными на данный компонент задачами. Руководящие требования по проверке Проектные критерии: Все 100% программных продуктов, связанных с наиболее ответственными компонентами, подвергаются тестированию. 75% компонентов, связанных с менее ответственными компонентами, подвергаются тестированию. Библиотечные компоненты проходят дополнительную проверку качества. Особо важный компонент приложения ------------------- Менее важный компонент приложения ------------------- Библиотечный компонент ---------- Проектные критерии: Анализ рисков используется для упорядочивания по приоритетам компонентов класса с целью их проверки и тестирования. Построение и сохранение тестовых наборов Проектная процедура: Алгоритм HIT используется для определения, прогон каких тестовых случаев следует осуществить для данного компонента. Проектная процедура: В обязанности разработчика входит подготовка тестовых наборов в структуре, затребованной проектом. Проектные критерии: Для каждого компонента имеется класс тестового драйвера, который содер- жит тестовые случаи для тестируемого компонента. Проектные критерии: Для каждого компонента имеются тестовые методы, которые представляют функциональные, структурные тестовые случаи, а также тестовые случаи, предназначенные для тес- тирования взаимодействия компонентов. Функциональные тестовые случаи Проектные критерии: Выполнить тестовые случаи для проверки каждого постусловия каждого мето- да. Выполнить также проверку инвариантов классов как часть тестового случая. Проектные критерии: Выполнить прогон тестовых случаев при наиболее важных значениях каждого из параметров. Структурные тестовые случаи Проектные критерии: Выполнить тестовые случаи, покрывающие каждую строку программного кода каждого метода. Критерий анализа рисков по отношению к тестируемому компоненту Тестовые случаи на базе состояний классов Проектные критерии: Выполнить прогон тестовых случаев, которые покрывают каждый переход из одного представления в другое. Тестовые случаи для тестирования взаимодействия компонентов Проектная процедура: Выполнить прогон тестовых случаев, проверяющих каждый контракт между компонентами. Воспользуйтесь процессом OATS для выбора тестовых случаев с целью их последую- щего прогона. Рисунок 3.17. Шаблон плана тестирования компонента
Глава 3. Планирование работ по тастироаанию Шаблон плана тестирования случая использования - часть 1 Наименование случая использования Ответственный исполнитель: Ответственность за отладку классов несет разработчик, которому принадлежит компонент. Имя разработчика: ________________________________________ Часть 1. Внешний вид и компоновка (повторяется для каждого экранного изображения/отчета) 1.1 Все необходимые поля данных присутствуют. Да Нет 1.2 Элементы представлены в корректном порядке. Да Нет 1.3 Неописанные поля отсутствуют. Да Нет 1.4 Начальные системные значения по умолчанию выбраны правильно. Да Нет 1.5 (Только для экранов) Обход по клавише табуляции осуществляется Да Нет в корректном порядке. 1.6 (Только для экранов) Клавиши ускоренного доступа работают правильно. Да Нет 1.7 (Только для экранов) Возможен доступ к полям в произвольном порядке. Да Нет 1.8 (Только для экранов) Меню упорядочены правильно. Да Нет 1.9 (Только для экранов) Соответствующие функции меню активны. Да Нет 1.10 (Только для экранов) Объекты реагируют корректно (на события Да Нет типа двойного щелчка и т.п.). Стандарты для тестовых случаев 1. Для каждого «обязательного» поля должны быть предусмотрены тестовые случаи, в которых значе- ние для такого поля отсутствует. 2. Для каждого граничного значения должны быть предусмотрены тестовые случаи, в которых ис- пользуется это конкретное значение. Должны быть также тестовые случаи, которые используют значения из каждого класса эквивалентности из интервала между этими граничными значениями. 3. Для каждого поля с перечислимым типом данных, кроме флажков, должны существовать тестовые случаи для выявления полей с неправильными значениями или пустых полей. 4. Для каждого поля типа даты должны предусматриваться тестовые случаи, выявляющие поля с не- правильными значениями или пустые поля. Однако, пока явно заданные ограничения на случаи ис- пользования отсутствуют, проверка граничных значений невозможна. Даты могут быть заданы не- правильно в силу особенностей формата либо из-за нарушений бизнес-правил. 5. Для каждого сценария, в котором имеет место выборка данных, должен быть предусмотрен тесто- вый случай, в котором все поля пусты, за исключением единственного, в котором данные присут- ствуют. Такой тестовый случай должен быть выбран для каждого поля, которое может редактиро- ваться пользователем. Для тех полей, которые принимают типизированные значения, например, поле имени, следует предусмотреть два тестовых случая. Например, должны проверяться как пол- ное имя, так и частичное имя. Часть 3 — Безопасность/Целостность Тестовая программа указывает уровень защиты пользователя и выполняет Да Нет тестирование каждого уровня Каждое поле точно обнаруживает неправильно заданные типы данных Да Нет и их обработку Система выдает сообщение об исправлении ошибки и предупреждающее Да Нет сообщение о возникновении необычной ситуации. Выходные данные успешно продублированы на всех серверах. Да Нет 1. Тесты, которые должны быть успешно пройдены, прежде чем станет возможным выполнение таких сценариев: 2. Тесты, которые необходимо выполнить, если неудачно завершится выполнение следующих сценари- ев: 3. Тесты, которые должны быть выполнены после успешного завершения следующих тестов: Рисунок 3.18. Шаблон плана тестирования случая использования — часть I
Глава 3. Планированив работ по твстированию Шаблон плана тестирования случая использования - часть 2 Наименование случая использования Ответственный исполнитель: Ответственность за отладку классов несет разработчик, которому принадлежит компонент. Имя разработчика: _________________________________________ Часть 4 — Сценарии тестирования В этом разделе содержатся инструкции тестировщикам о том, как проводить каждое тестирование. Эти инструкции берутся из раздела «Система отвечает...» каждого сценария случая использования и из таблиц, построенных в разделе 2 шаблона случая использования, который на рис. 3.18 не показан по причине экономии пространства книги, зато его можно найти на рис. 9.7. Сценарий # Обеспечить выполнение предусловий для тестов Тип пользователя: любой Пользователь обладает специальными полномочиями, необходимыми для выполнения операции Да Нет Система вводит в действие соответствующее управление доступом Да Нет Появляется соответствующее сообщение об ошибке при попытке несанкционированного доступа Да Нет Система отвергает систематические попытки несанкционированного доступа Да Нет Потребности конфигурации Необходимые тестовые базы данных и таблицы на месте Да Нет Выполнить следующий сценарий: Выполнить оценку результатов Общие результаты тестирования: Получен ожидаемый результат. Да Нет Значение каждого связанного поля вычислено правильно. Да Нет Выпадение из синхронизации набора связанных полей вызывает появление сообщения об ошибке. Да Нет Появилось требуемое сообщение об ошибке. Да Нет Сообщение дает точное и однозначное описание проблемы. Да Нет Система восстановилась соответствующим образом после выдачи сообщения об ошибке. Да Нет Сохранение было невозможным до исправления ошибок. Да Нет Программа соответствует стандартам по производительности. Да Нет Рисунок 3.19. Шаблон плана тестирования случая использования — часть 2
Глава 3. Планированив работ по твстированию Шаблон плана тестирования случая использования - часть 3 Наименование случая использования Ответственный исполнитель: Ответственность за отладку классов несет разработчик, которому принадлежит компонент. Имя разработчика: ____________________________________________ Часть 5 - Краткое описание тестирования случая использования Информация общего характера о тестировании как о виде деятельности (ответственный персонал, место проведения испытаний и прочее): Описание аппаратных средств, использованных для прогона тестов (модель принтера, тип сетевого соединения и прочее): ________________________________________________________________________ Неописанное спецификацией поведение, которое имело место: Отклонение от описанных спецификацией тестовых процедур: Требуется повторное тестирование. Да Нет Утверждаю: _______________ Рисунок 3.20. Шаблон плана тестирования случая использования — часть 3 Модели случаев использования могут быть структурированы таким же обра- зом, как и диаграммы классов. Отношения includes и extends обеспечивают сред- ства декомпозиции случаев использования в «частичные» случаи использования, как показано в главе 2. Частичные случаи использования объединяются с помо- щью указанных выше отношений, образуя при этом «комплексные» случаи ис- пользования. Мы выделяем три уровня случаев использования: высокоуровневые, сквозные системные и функциональные подслучаи использования. Высокоуровневые слу- чаи использования — это абстрактные случаи использования, которые могут служить основой для их расширения до сквозных системных случаев использо- вания. Функциональные подслучаи использования агрегируются в сквозные сис- темные случаи использования. Мы написали фактические сценарии тестирова- ния на языке сценариев, входящем в состав инструментальных средств тестирования, которые используют отношение генерации/специализации между высокоуровневыми и сквозными системными случаями использования. Эти тес- товые сценарии также собирают в единое целое фрагменты сценариев тестирова-
Глааа 3. Планированиа работ по тестированию ния из функциональных подслучаев использования. Обладая тремя рассмотрен- ными уровнями, проекты становятся более управляемыми, а тестовые сце- нарии — более модульными. В проекте, для которого это служило шаблоном, различаются два «вида» слу- чаев использования: функциональные и статистические случаи использования. Функциональные случаи использования изменяют данные, поддерживаемые сис- темой тем или иным способом. Статистические случаи использования получают информацию от системы, после чего обобщают и форматируют ее для последую- щего представления пользователю. Эти различия привели к различному количе- ству сеансов тестирования безопасности и устойчивости. В собственных проек- тах могут обнаружиться и другие группы случаев использования. План комплексных испытаний План комплексных испытаний особенно важен в итеративной среде разработ- ки. Некоторые специальные наборы функциональных средств появляются рань- ше других. С добавлением таких комплектующих модулей система постепенно приобретает свои очертания. Одно из осложнений, вытекающее из такого стиля развития системы, состоит в том, что план комплексных испытаний меняет свой характер на протяжении жизни проекта гораздо чаще, чем план тестирования компонентов или план системного тестирования. Компоненты, которые интегри- руются в систему на ранних стадиях как комплектующие модули, могут непос- редственно не поддерживать функциональные средства конечного пользователя, и в силу этого обстоятельства ни один из случаев использования не может слу- жить источником тестовых случаев. На этой стадии наилучшим таким источни- ком являются планы тестирования компонентов для агрегированных компонен- тов. Такие планы используются для завершения плана тестирования компонента, в задачу которого входит интегрирование этих объектов. После того как соответствующее число комплектующих модулей будет включено в систему, функциональные возможности интегрированного программного обеспечения начнут приходить в соответствие с поведением системного уровня. На этом вре- менном этапе наилучшим источником тестовых случаев являются планы тести- рования случаев использования. В обоих ситуациях тестовые случаи выбираются в зависимости от того, на- сколько такой тестовый случай нуждается в том, чтобы поведение оказывало воздействие на все интегрируемые модули. Небольшое, локально действующее поведение должно быть уже отлажено. Это означает, что тестирование должно быть более сложным и более всесторонним, нежели обычное тестирование ком- понента. В интегрированной по всем правилам объектно-ориентированной сис- теме существуют определенные объекты, которые перекрывают некоторое число других объектов, входящих в ее состав. Выбор тестов в соответствии с планами тестирования этих компонентов довольно часто позволяет получить тестовые случаи для комплексных испытаний.
Глава 3. Планироааниа работ по тастироаанию В связи с такой зависимостью планов комплексных испытаний от тестовых случаев, мы не приводим для него какого-то специального шаблона. Его формат совпадает с форматом шаблона для плана системных испытаний в том смысле, что он отражает определенную комбинацию индивидуальных планов тестирова- ния, образующую план комплексных испытаний при внедрении конкретного комплектующего модуля. План системных испытаний План системных испытаний представляет собой документ, который обобщает планы тестирования отдельных случаев использования и предоставляет инфор- мацию о дополнительных видах тестирования, которые могут проводиться на системном уровне. В каждой из глав, в которых рассматриваются методы тести- рования, будет описано тестирование жизненного цикла как один из методов, который может применяться как на системном уровне, так и на уровне отдель- ных компонентов. Для последующего использования здесь вводится таблица (рис. 3.21), которая отображает планы тестирования случаев использования на системные испыта- ния. Большая часть информации, которая требуется форматом IEEE планов тес- тирования, будет предоставлена планами тестирования индивидуальных случаев использования. План системных испытаний Номер случая использования Номер тестового случая Обоснование выбора Рисунок 3.21. План системных испытаний Итерации в планировании Итерации в процессе разработки влияют на то, как выполняется планирова- ние. Изменения в требованиях, предъявляемых к программному продукту или к комплектующему модулю, требуют, по меньшей мере, того, чтобы планы перио- дически подвергались ревизии. Во многих случаях они требуют изменений. Что- бы облегчить выполнение таких модификаций в цикле, мы поддерживаем мат- рицу обнаруживаемости неисправностей. Если организация-разработчик получает требования в традиционной форме, мы строим матрицу отображения требований на случаи использования. Часто это просто крупноформатная электронная таблица, в которой по вертикальной оси откладываются идентификаторы требований, а по горизонтальной оси —
Глава 3. Планироааниа работ по твстироаанию идентификаторы случаев использования. Вхождение в какую-либо ячейку таб- лицы указывает на то, что функциональные возможности случая использования определяются или ограничиваются соответствующим требованием. Кроме того, поддерживается вторая матрица, при помощи которой каждый случай использования соотносится с набором пакетов классов. Вхождение в ту или иную ячейку соответствующей таблицы означает, что в пакете имеются классы, которые используются при реализации соответствующего случая исполь- зования. Когда случай использования меняется, владельцы пакета получают со- общение об этом. Они проверяют функциональные средства, которые они пре- доставляют, и вносят требуемые изменения в программный код. Это вызывает последовательность изменений на нескольких уровнях тестовых случаев и, воз- можно, в планах тестирования. Планирование Объем работ по планированию зависит от нескольких факторов: от того, как часто повторно используется шаблоны от того, сколько приходится затрачивать усилий, чтобы превратить шаблон в реальный завершенный план от того, какие требуются трудозатраты, чтобы внести изменения в суще- ствующий план. Каждое из этих значений требует назначения базовой линии с тем, чтобы, отталкиваясь от нее, можно было получить реальные оценки. Система показателей тестирования Система показателей тестирования предусматривает измерения, которые дают информацию, необходимую для оценки отдельных методов тестирования и всего процесса тестирования. Показатели тестирования используются также как исход- ные данные для планирования, например, оценок трудозатрат для проведения тестирования. Чтобы получить окончательные значения показателей такого рода, потребуются меры, обеспечивающие возможность измерения покрытия и слож- ности программного продукта и позволяющие построить базу для систем пока- зателей эффективности и производительности. Покрытие — это термин, применяющийся в тестировании, который показы- вает, какие компоненты были затронуты тестовыми случаями. В этой книге мы рассмотрим целый ряд различных мер покрытий во время обсуждения методов тес- тирования. При этом основное внимание будет уделено следующим моментам: Покрытие программных кодов — какие строки программных кодов выпол- нялись. Покрытие постусловий — какие постусловия учитывались при выполнении тестовых случаев. Покрытия элементов моделей — какие классы и отношения модели были использованы в тестовых случаях.
Глава 3. Планирование работ по тестированию Система показателей покрытия выражается в терминах программного продук- та, подвергающегося тестированию, но не в терминах процесса, который ис- пользуется для эго отладки. Это дает нам основание, в соответствии с которым можно говорить, с «какой тщательностью» программный продукт был испытан. Например, рассмотрим ситуацию, когда один разработчик использует каждый логический оператор из каждого постусловия в качестве источника тестовых случаев, в то время как другой использует из постусловий только те, которые рассчитаны на благоприятные стечение обстоятельств (или т.н. условия «солнеч- ного дня»8). Второй разработчик не выполняет тестирования так тщательно, как первый, поскольку тестированием покрывается только некоторая часть операто- ров рассматриваемого постусловия. Покрытие в сочетании со сложностью может стать надежной базой для вы- числения трудозатрат, необходимых для тестирования программного продукта. Другими словами, по мере усложнения программного обеспечения, становится все труднее достичь заданного уровня покрытия. Сложность можно измерять не- сколькими способами: количеством и сложностью методов конкретного класса количеством строк программного кода количеством динамических связей. Собирая данные о рабочих характеристиках программных продуктов на про- тяжении достаточно продолжительного промежутка времени, проекты или ком- пании могут установить некоторую базовую линию, отталкиваясь от которой можно строить планы относительно каждого нового проекта. Процесс тестирования результативен, если он позволяет выявить ошибки. Он эффективен, если позволяет выявлять ошибки с минимально возможными зат- ратами ресурсов. Мы рассмотрим пару способов, позволяющих получить данные об обеих этих характеристиках. Показатель число отказов/затраты человеко-ча- сов разработчиков определяет выход процесса тестирования, в то время как по- казатель затраты человеко-часов разработчиков/число неисправностей представ- ляет собой меру стоимости процесса тестирования. Эти значения зависят от того, какие инструментальные средства используются для построения тестов, равно как и для достижения требуемого уровня покрытия, откуда следует, что каждой компании потребуется определить базовую линию для собственного про- цесса тестирования и сбора фактических данных о его эффективности, прежде чем использовать эти значения для целей планирования. Оценка результативности процесса тестирования производится путем сбора данных на протяжении всего цикла разработки проекта. Рассмотрим случай ошибки, привнесенной в приложение во время разработки. Чем скорее этот де- фект будет обнаружен, тем более результативным является процесс тестирова- • Конструкция «солнечного дня» — это ожидаемый результат, не принимающий во внимание оши- бочные состояния, которые могут вызвать исключительную ситуацию или возврат кода ошибки. 5*
Главе 3. Планироввнив работ по твстированию ния. Эффективность этого процесса тестирования измеряется длительностью ин- тервала времени от момента, когда эта неисправность была внесена в программный продукт, и до стадии, на которой она была обнаружена во всех ее проявлениях. По- настоящему эффективный процесс тестирования обнаруживает каждый дефект на той же стадии разработки, на какой она была внесена. Если же дефекты, привнесен- ные во время разработки, остаются необнаруженными до стадии тестирования про- граммных кодов, то методы тестирования, применяемые во время разработки систе- мы; должны быть модифицированы таким образом, чтобы они могли отыскивать и те типы неисправностей, которые ранее вовремя не обнаруживались. Резюме В мире, пронизанном духом конкуренции, в котором сложное программное обеспечение продается по цене каких-то 99.95 долл. США, а компании выпла- чивают поощрительные премии любому служащему, который привлечет к работе нового специалиста по программного обеспечению, планирование становится исключительно важным видом деятельности. Основная трудность заключается в том, чтобы найти правильное сочетание между затратами времени на разработку планов и их документирование и затратами времени на производство программ- ного продукта. Мы описали последовательность шаблонов, которые позволяют сократить зат- раты времени на процесс планирования. Эти документы образуют иерархичес- кую структуру, благодаря которой становится возможным дальнейшее сокраще- ние объемов документации, необходимой для выполнения адекватной работы. Процесс планирования позволяет добиться успеха, если разработчики, являю- щиеся движущейся силой проекта, находят планирование полезным. Планиро- вание становится в лучшем случае формальным, если разработчики будут вы- полнять его лишь «для проформы». Упражнения 3-1 Назовите документы и модели, которые необходимо подготовить для проектируе- мой системы. Продумайте способы выдачи необходимой информации об отсутству- ющих компонентах. 3-2 Расставьте по приоритетам требования к своему проекту и к результирующему программному продукту. 3-3 Продолжите разработку плана тестирования, построив таблицу, в которой содер- жится список всех «продуктов», доступных для тестирования. Вторая колонке таб- лицы должна быть отведена под список методов тестирования, которые планиру- ется задействовать при тестировании программного продукта. На текущий момент эта колонка остается пустой. Ваш проект итеративный? Он инкрементальный? Если он итеративный, следует предусмотреть колонку, в которой обобщаются данные применения различных методов во время каждой итерации. Если процесс инкре- ментальный, должны существовать отдельные таблицы для каждого комплектующе- го модуля системы, поскольку они разрабатываются независимо друг от друга. 3-4 Какой уровень риска вы присвоите классам Sprite, PuckSupIy, Puck и Velocity игры «Кирпичики»?
Глава Тестирование аналитических и проектных моделей ► Вы хотите научиться проверять семвнтику моделей, нвписанных нв языке UML? См. раздел «Основные понятия целенаправленной проверки» ► Ввм нужно инициировать сеанс проверки? См. рвздел «Организация целенаправленной инспекционной деятельности» ► Вам нужен метод тестирования модели на расширяемость? См. рвздел «Модели для тестирования дополнительных свойств» Разработчики обычно моделируют создаваемые программные продукты, по- скольку модель помогает им глубже понять решаемую задачу, а также в силу того, что они помогают успешно справляться со сложностью разрабатываемых ими сис- тем. Модели аналитической и проектной информации в конечном итоге будут использованы для того, чтобы направить усилия по реализации проекта в пра- вильное русло. Если модели обладают высоким качеством, они вносят неоцени- мый вклад в реализацию проекта, но если в них содержатся ошибки, они настоль же вредны. В этой главе рассматривается понятие целенаправленной проверки, представляющей собой метод расширенной проверки моделей по мере их созда- ния и контроля этих моделей на предмет соответствия требованиям проекта. Ос- новной недостаток стандартных ревизий заключаются в том, что центр тяжести проверки они переносят главным образом на то, что уже имеется (в модели), а не на то, что в ней должно быть. В ревизиях не предусмотрены средства системати- ческого обнаружения того, чего не хватает в программном продукте. Даже провер- ки Фагана (Fagan) [Faga86], в которых применяются контрольные списки для обеспечения дальнейшей детализации процесса, не предусматривают средств для определения, чего в модели не хватает. 133
Глава 4. Тастирование аналитических и проектных моделей Целенаправленная проверка применяет перспективу тестирования на очень ранних стадиях процесса разработки. Традиционно тестирование начиналось на уровне реализации модулей и продолжалось по мере того, как сегменты программ- ных кодов объединялись во фрагменты большего размера до тех пор, пока не вся система не оказывалась готовой к тестированию. В этой главе мы начнем «систем- ное тестирование» на стадии, когда «система» представлена в виде аналитической или проектной информации. Новая версия традиционной V-образной модели те- стирования, показанная на рис. 4.1, соотносит повторяющиеся применения це- ленаправленных проверок с различными уровнями тестирования. Целенаправленная проверка требует затрат дорогостоящих ресурсов и внима- ния со стороны персонала, работающего над проектом. Есть ли практический смысл в ее проведении? Исследования показывают, что отношение затрат на об- наружение и устранение неисправностей на ранней стадии процесса разработки к затратам по их обнаружению и устранению на этапе компиляции или системных испытаний колеблется в широких пределах. Например, устранение ошибки, обна- руженной во время системных испытаний, может стоить на два порядка дороже, нежели устранение той же ошибки на этапе анализа. Таким образом, даже умерен- ные затраты усилий на тестирование модели могут принести большую экономию. Обзор Рассмотрим простой пример использования метода целенаправленной провер- ки. Прежде всего, потребуется определиться с декорациями — мы находимся на исходных стадиях разработки игры «Кирпичики». Коллектив разработчиков пост- роил диаграмму классов для этапа проектирования, показанную на рис. 2.18, и ряд других диаграмм, таких как, например, диаграмма состояний (см. рис. 2.19) и диаграмма последовательности сообщений (см. рис. 2.20). Мы уже готовы начать написание программных кодов, однако хотим убедиться в корректности проект- ной модели и не тратить уйму времени на кодирование неправильных определе- ний.
Глава 4. Тестирование аналитических и проектных моделей Начнем с того, что назначим группу проверки программ. В эту группу входят оба автора этой книги, работавшие над моделью, системный тестировщик нашей компании и координатор, в роли которого выступает специалист, знакомый с тех- нологическим процессом компании. Тестировщик будет разрабатывать набор тес- товых случаев по диаграмме случаев использования. Будучи разработчиками, мы покажем, как классы в модели проекта манипулируют каждым случаем использо- вания. Координатор будет определять границы проверки, составлять расписание сеансов целенаправленных проверок, распределять материалы, обеспечивать про- движение сеансов и составлять сводный отчет. Подготавливая сеанс, координатор определяет границы проверки, выбирая область проверки и глубину тестируемой информации. Область проверки определяется набо- ром случаев использования. В рассматриваемом случае область проверки покрывает все случаи использования, следовательно, и все приложение. Глубина охвата опреде- ляется выбором уровней включения в иерархиях композиции. В случае игры «Кир- пичики» мы не проверяем объектов, которые агрегированы в объекте BricklesView. Вместо этого внимание будет сосредоточено на тех из них, которые представляют со- стояние сеанса игры в любой заданный момент времени в объекте BricklesDoc. Тестировщик пишет тестовые случаи на базе случаев использования, представ- ленных на рис. 2.11. Остановим свой выбор на одном из тестовых случаев, пока- занном на рис. 4.2. Перед тем как встретиться на рабочем совещании, проекти- ровщики завершают построение контрольной таблицы проектной модели, показанной на рис. 4.3. Эта задача решается каждым разработчиком индивидуаль- но. При этом требуется, чтобы каждый разработчик сравнивал диаграмму классов из модели анализа, представленной на рис. 2.13, с диаграммой классов проектной модели. В завершение координатор посылает уведомление о предстоящем совеща- нии вместе с копиями модели на бумаге или с URL-адресом ее Web-версии. В протоколе испытаний, поступающем от группы целенаправленной проверки, отражены все проблемы, обнаруженные во время символического выполнения те- стовых случаев. Что касается рассматриваемого здесь тестового случая, то предпо- лагается, что на данной стадии проект еще не прошел испытаний. Протокол испытаний отразит факт невозможности отыскания способа завершения символичес- кого выполнения тестового случая в данной точке алгоритма. Мы не хотим смеши- вать тестирование с отладкой, тем не менее, поскольку известно, в какой точке сим- волическое выполнение было остановлено, это должно найти свое отражение в протоколе. В протокол испытаний включена диаграмма последовательности сообще- ний, использованная для регистрации выполненных тестов (см. рис. 4.4). Случай использования: Игрок завершает сеанс игры «Кирпичики», выбирая в меню File элемент Quit. Предусловия: Игрок начал сеанс игры «Кирпичики», перемещал лопатку и даже умудрился разбить несколько кирпичей. Входные данные теста: Игрок выбрал элемент Quit. Ожидаемые выходные данные: Все игровые действия замораживаются, а окно игры исчезает с экрана. Рисунок 4.2, Тестовый случай №1
Глава 4. Тестирование аналитических и проактных моделей Перечень вопросов, касающихся проектирования, которые сформулированы на языке UML Да Нет Проблемы преобразования модели анализа в проектную модель. Выходят ли все классы, содержащиеся в модели анализа, но не представленные в проектной модели, за пределы области действия приложения? у/ Являются ли все состояния, представленные в картах состояний модели анализа, также и состояниями, представленными в диаграммах состояний проектной модели? Vх Одинаковы ли последовательности сообщений во всех диаграммах последовательностей сообщений на проектном уровне даже при наличии возможности включения дополнительных сообщений в набор сообщений на уровне анализа? у/ Внутренние проблемы проектных моделей. Все ли ассоциации, представленные без информации о навигационных свойствах, являются двунаправленными? у/ Все ли композиционные отношения являются однонаправленными? Является ли каждая диаграмма последовательности сообщений подмножеством какой-либо диаграммы одного из видов деятельности? Появляется ли каждое сообщение, переданное в диаграмме взаимодействия, в виде метода в общедоступном интерфейсе класса объекта-получателя? Поступает ли каждое сообщение, передаваемое в диаграмме взаимодействий, на логически соответствующий объект? Являются ли все переходы из диаграммы состояний взаимно исключающими? Содержат ли все конечные автоматы, за исключением постоянных объектов, начальные и конечные состояния? Все ли методы общедоступного модификатора представлены как переходы в любое состояние, если даже единственный результат состоит в том, что они сохраняют одно и то же состояние? Существует ли для каждого пункта постусловий каждого метода диаграмма последовательностей сообщений, которая соответствует случаям использования, достигающим порога отношения частота использования/ критичность? Все ли сообщения корректно представлены как синхронные или асинхронные? Совпадает ли количество разветвлений с количеством объединений для каждого вида деятельности, представленного на диаграмме? Рисунок 4.3. Пример контрольной таблицы на стадии проектирования
Quit OnNewDocument() -.CBrickles- :CBrickles- :Pu Doc View Timer Paddle Sup ck- iply | :Puck | | :Mouse | | :PlayField new Paddk 3(&aMouse) 1 1 1 . 1— J * i new PuckSupply(4) — ◄ new Mouse — get() — yField() ◄ attach(&aP uck) &aPuck new Pla ◄ L- enable() ► Quit Stop Рисунок 4.4. Часть диаграммы последовательности сообщений для тестового случая №1
Глава 4. Тестирование аналитических и проектных моделей ОТРЫВОК ИЗ СТЕНОГРАММЫ МЕЛИССА [координатор]: Итак, давайте начнем. У каждого была возможность оз- накомиться с моделью, в Джон и Дейв закончили свои контрольные таблицы. Раз- решите вам напомнить, что мы переходим к работе над локально разработанными классами и пока не будем уделять внимание стандартным классам пользовательского интерфейса, таким как, например, меню. Начнем с первого тестового случая, Джейсон. ДЖЕЙСОН (тестировщик): Вот первый тестовый случай (вручает ей первый тестовый случай). С помощью Джона я набросал начало последовательности сообщений для этого тестового случая на базе его предусловий (см. рис. 4.4]. Таким образом, игрок выбирает а меню Quit и ... ДЖОН (разработчик): Мой класс :BricklasViaw получает сообщение Quit. А мой объект пошлат сообщение Quit объекту Дейва aBricklaDoc. (Наносит эти объекты на диаграмму последовательностей сообщений.) ДЕЙВ (разработчик): Когда :BricklasViaw получит сообщение Quit, он отправит сообщение Stop объекту aTimar. (Начинает наносить его на диаграмму последова- тельностей сообщений.) ДЖЕЙСОН: Подожди минутку! Насколько я могу судить по диаграмма классов, между этими двумя классами нет ни одной ассоциации. ДЕЙВ: Джон, куда должно быть отправлено это сообщение Stop? Мне показалось, что ты собирался реализовать этот метод. ДЖОН: (Смотрит с недоумением попеременно то на диаграмму последовательности сообщений, то на диаграмму классов.) Ошибка! МЕЛИССА: Похоже, мы уже созрели для рассмотрения следующего тестового случая. Место в процессе разработки Последним видом деятельности каждой стадии процесса разработки должна быть проверка того факта, что работа, проделанная на этой стадии, оправдывает все наши ожидания. Эта работа принимает форму либо модели на языке UML, либо кода на языке программирования. (См. раздел «Обзор процесса разработ- ки»). Структура этого процесса такова, что каждый этап разработки продвигает программный продукт на один шаг вперед по направлению к окончательному ва- рианту системы, что приводит к появлению последовательности моделей, в кото- рой каждая модель, полученная на некотором этапе разработки, обладает большей степенью детализации и полноты, нежели модель, полученная на предшествую- щем этапе. Например, на стадии анализа приложения модель создается путем от- фильтровки информации аналитической модели предметной области и модели требований от информации, которая не имеет непосредственного отношения к разрабатываемому приложению. Двумя основными различиями между моделями в этой последовательности являются масштаб содержимого и уровень абстракции. Модель требований осуществляют фильтрацию модели предметной области с та- ким расчетом, чтобы никакая информация, не востребованная для непосредствен- ного применения, не была включена в модель анализа приложения. По мере того как возрастает информативность каждой модели этой последовательности, про- верка каждой модели становится все более специфичной и конкретной.
Главв 4. Тестироввние внвлитических и проектных моделей Этап/модель Содержимое Получено из... Анализ предметной области Концепции предметной области, стандартные алгоритмы Мнения экспертов по предметной области Анализ приложений Концепции, необходимые для постановки конкретных задач; стандартные алгоритмы области Модель для анализа предметной и модель требований Проектирование на уровне архитектуры Базовая структура интерфейсов и их взаимодействие Стандартные архитектурные шаблоны и изобретательность разработчиков Детализированное проектирование Каждый интерфейс архитектуры реализован одним или большим числом компонентов Проектная модель на уровне архитектуры и стандартные проектные образцы и алгоритмы Рисунок 4.5. Модели и этапы Последовательность моделей, описание которой представлено на рис. 4.5 (по существу, это одна и та же модель, подвергающаяся преобразованиям в режиме приращений), предоставляет нам возможность установить «цепь качества», в ко- торой каждая модель подвергается проверке на правильность прежде чем станет возможным переход к следующему этапу. Основные понятия целенаправленной проверки Метод целенаправленной проверки обеспечивает средства объективного и систе- матического просмотра рабочего продукта с целью выявления неисправностей с использованием специальных тестовых случаев. Такая перспектива тестирования означает, что ревизии рассматриваются как сеансы тестирования. При тестирова- нии выполняются следующие основные действия: 1. Определение пространства тестирования. 2. Выбор значений из пространства тестирования с применением специальной стратегии. 3. Применение экспериментальных значений к тестируемому программному продукту. 4. Оценка результатов и покрытия модели (в процентном отношении) тестами (на базе некоторого критерия). Перечисленные действия подвергаются дальнейшей конкретизации (каждое из них будет уточняться на протяжении настоящей главы): 1. Определить области и глубину проверки. Область проверки будет опреде- ляться путем описания большого числа материальных или специфических наборов случаев использования. Для небольших проектов в область провер- ки может попасть все приложение. Глубину проверки мы определим путем описания охватываемого уровня детализации. Она может быть также опре-
Глава 4. Тестирование аналитических и проектных моделей делена при помощи спецификации уровней в иерархии агрегирования на некоторых UML-диаграммах тестируемой модели, или MUT-модели (Model Under Test — тестируемая модель). 2. Определить основу, послужившую источником создания MUT-моделей. Основой всех моделей, за исключением исходной, является набор моделей предыдущего этапа разработок. Например, основой модели приложения, построенной на стадии анализа, служит модель анализа предметной области и модель случая использования. Основой исходных моделей служат знания избранных групп специалистов. 3. Разработать тестовые случаи для каждого критерия оценки, при прогоне ко- торых компоненты базовой модели используется как входные данные (см. раздел «Отбор тестовых случаев для проверки»). Сценарии из модели слу- чая использования могут стать хорошей отправной точкой при построении тестовых случаев для проверки многих моделей. 4. Установить критерии для измерения покрытия неисправностей тестами. Например, диаграмма классов может быть надежно покрыта, только если каждый класс затронут каким-либо тестовым случаем. 5. Выполнить статический анализ, воспользовавшись соответствующим конт- рольным списком. MUT-модель сравнивается с базовой моделью с целью определения непротиворечивости этих диаграмм. 6. «Выполнить» тестовые случаи. Фактический сеанс тестирования подробно описывается ниже в главе. 7. Оценить эффективность тестирования, измерив покрытие тестами. Вычис- лите покрытие в процентах. Например, если в диаграмме, содержащей 18 классов, 12 классов были «затронуты» тестовыми случаями, то в этой ситу- ации покрытие тестами составит 75%. Необходимый уровень тестирования модели анализа или проектной модели настолько высок, что для получения хороших результатов потребуется обеспечить их 100%-ное покрытие теста- ми. 8. Если покрытие неэффективно, расширьте набор тестов и выполните допол- нительные тесты, в противном случае просто прекратите тестирование. Обычно дополнительные тесты написать во время сеанса проверки невоз- можно. Тестировщики определяют, где покрытие недостаточно, и взаимо- действуют с разработчиками, дабы выявить потенциальные тестовые случаи, которые способны затронуть неохваченные тестами элементы модели. Затем тестировщики создают полноценные тестовые случаи, после чего выполня- ется следующий сеанс тестирования.
Глава 4. Тастирование аналитических и проектных модвлвй ПОКРЫТИЕ В МОДЕЛЯХ Элементами UML-модвлвй, которыми мы пользуамся, являются объвктно-ориенти- рованные понятия: классы, отношения, объекты и сообщения. Конкретный тестовый случвй «покрывает» один из этих эламентов, если он использует этот элемент кек часть некоторого тестового случая. Разумеется, какой-то один тестовый случай, использующий конкретный элемент, по-видимому нв исчерпает всах возможных значений атрибутов этого элемента. Например, использование объекта некоторого класса для получения одного сообщения нв обвспечивавт тестирования других ме- тодов того жв класса. По мврв все более глубокого погружения в разработку жизненного цикла, детализация модели возрастает, возрастает также и детализация покрытого фрагмента модели. Что касается модели анализа предметной области, то создания одного объекта достаточно для того, чтобы считать, что этот класс охвачен покрытием. Покрытие модели этого уровня может быть выражено процентом покрытых классов и отношений. На уровне проектирования хотелось бы задействовать каждый метод в интерфейсе, прежде чвм отввтстввнно утверждать, что покрытие класса достигнуто. Покрытие этого уровня, скорее, будет получено путем пересчета всех методов, отображенных в модали, но нв путем подсчете всех методов соответствующих классов. Чем выше уровень абстракции классов, тем выше требуемый уровень покрытия. Выпадение какого-либо абстрактного класса из покрытия тестовыми случаями оз- начает, что тв дефекты, которые потенциально могли бы быть выявлены во всех конкретных классах, являющихся в конечном итоге производными от абстрактного класса, остаются необнаруженными. При тестировании нв уровне конкретных классов, пропуск класса приводит лишь к пропуску дефектов, содержащихся только в этом клвссе и нигде болев. Чем выше уровень абстракции модали, тем выше требуемый уровень покрытия. Ревизии обычно предусматривают обсуждения роли каждого фрагмента высо- коуровневой модели. Отношения между фрагментами также выражаются в терми- нах специализированных интерфейсов на уровне формальных параметров. Тесто- вые случаи, построенные с использованием этого метода, позволяют проводить исследование на более конкретном уровне, на котором атрибутам объектов при- сваиваются конкретные значения. Тестовые случаи должны быть реализованы на уровне, достаточно конкретном для обеспечения поддержки отслеживания точно- го пути выполнения в логике алгоритма, но не настолько конкретном, чтобы сна- чала нужно было писать программные коды. ВОПРОС Нужно ли предоставлять тестовые случаи в распоряжение разработчиков до начала сеанса проверки? Нужно поддерживать баланс, позволяя разработчикам, с одной стороны, програм- мировать тестовые случаи, а с другой стороны, дублировать усилия тестировщиков, выступая с собственными сценариями использования. Если тестировщики намере- вались разрабатывать все возможные сценарии использования, то их передачу проектировщикам с последующим выбором из них конкретных образцов для те- стирования моделей, можно считать вполне приемлемым вариантом. Поскольку тестировщики обычно создают лишь набольшую часть возможных сцвнаривв, ма- ловероятно, чтобы они дублировали работу разработчиков, которые независимо от них (как мы надеемся] будут обнаруживать другие сценарии. Таким образом, наш базовый подход заключается в том, чтобы нв разрешать резрвботчиквм работать над сцвнериями использования до тех пор, пока не начнется сеанс проварки.
Глава 4. Тестирование аналитических и проектных моделей Многие методы разработки объектно-ориентированного программного обеспе- чения ставят под сомнение целесообразность применения одной или большего числа диаграмм в рамках одной модели с целью дать оценку другим диаграммам. Например, диаграмма последовательности сообщений отслеживает путь на диаг- рамме классов, причем стрелки на диаграмме последовательности соответствуют ассоциациям, обнаруженным на диаграмме классов. Тем не менее, все эти методы разработки не обеспечивают систематического покрытия модели тестовыми случа- ями. Одно из действий целенаправленной проверки проверяет внутреннюю не- противоречивость и целостность диаграмм, используя для этой цели диаграммы, полученные во время выполнения тестовых случаев. ВОПРОС Должны ли тестировщики использовать в сеансе проверки тестовые случаи только текущего комплектующего модуля? Нет, не должны. Прогон тестового сценария, использованного для проверки пре- дыдущего комплектующего модуля, в рамках сванса рагрессионного тестирования, может сослужить неплохую службу. Регрессионные сценарии должны быть выбраны так, чтобы включать те из случаев, прогон которых во время включения предыдущих комплектующих модулай завершился неудачей, а также те из них, которые покры- вают области, где с большой вероятностью будут вноситься изменения, обуслов- ленные включением в систему функциональных средств текущего комплектующего модуля. Критерии оценки При проверке MUT-модели мы стремимся получить ответы, прежде всего, на следующих три вопроса: Корректна ли тестируемая модель? Является ли эта модель полным представлением соответствующей инфор- мации? Является тестируемая модель внутренне непротиворечивой и совместима ли она с базовой моделью? Корректность есть мера точности модели. На уровне анализа это точность опи- сания задачи. На уровне разработки это описание того, насколь точно модель представляет решение задачи. На обоих уровнях модель должна аккуратно пользоваться нотацией. Степень точности определяется относительно стандарта, который считается непогрешимым оракулом (и рассматривается как «непреложная истина»), хотя так бывает редко. В роли оракула довольно часто выступает специ- алист, знания которого достаточны для того, чтобы рассматриваться в качестве стандарта. Специалист определяет ожидаемые результаты для каждого тестового случая. Тестирование определяет, что модель корректна в отношении конкретного тес- тового случая, если результат его выполнения является ожидаемым. (Очень важно, чтобы каждый тестовый случай приводил к ожидаемому результату, явно сформу-
Главе 4. Тастирование аналитических и лроактных моралей лированному еще до того, как программист, написавший его, ощутит на себе вли- яние результатов проверки.) Модель считается корректной по отношению к набо- ру тестовых случаев, если выполнение каждого тестового случая завершается ожи- даемым результатом. На практике мы должны допустить, что оракул может время от времени оши- баться. Мы довольно часто в рамках одной организации разделяем специалистов по предметной области на две группы, представляющие различные перспективы или подходы. Одна группа строит модель, а другая группа в то же самое время занята разработкой соответствующих тестовых случаев. Такой контроль и баланс еще не гарантирует правильности оценки, тем не менее, он повышает ее вероят- ность. Это справедливо для каждого тестового случая. Каждый из тестовых случа- ев может определить неправильный ожидаемый результат. Тестировщики и разра- ботчики должны сотрудничать, дабы выявлять подобного рода случаи. Полнота есть мера наличия в модели необходимых элементов. Включены ли в модель необходимые или, по меньшей мере, полезные элементы? Тестирование показывает, существуют ли тестовые случаи, предлагающие сценарии, которые не могут быть представлены элементами, входящими в состав модели. В итератив- ных инкрементальных процессах полнота может служить мерой того, насколь раз- витым обещает быть текущий комплектующий модуль. Этот критерий становится более строгим по мере совершенствования комплектующего модуля в результате последовательно выполненных итераций. Одним из факторов, оказывающих непосредственное влияние на полноту кри- терия, является качество покрытия модели тестовыми случаями. Модель считает- ся полной, если результаты выполнения тестовых случаев могут быть адекватно представлены содержимым самой модели. Например, диаграмма последовательно- сти сообщений может быть построена так, чтобы представлять некоторый сцена- рий. Все объекты, необходимые для построения диаграммы последовательности сообщений, должны быть взяты из классов, представленных на диаграмме клас- сов, в противном случае она будет рассматриваться как неполная. Однако, если будет выполнен прогон всего лишь нескольких тестовых случаев, то факт отсут- ствия тех или иных классов может остаться незамеченным. Для моделей, постро- енных на ранних стадиях разработки, такая проверка проводится на достаточно высоком уровне, для которого стопроцентное покрытие всех случаев использова- ния необходимо. Непротиворечивость есть мера присутствия противоречий внутри модели или между текущей моделью и моделью, на базе которой была построена текущая мо- дель. Тестирование выявляет такие противоречия, находя в модели различные представления подобных тестовых случаев. Несоответствия могут быть выявлены и во время выполнения тестового случая, когда текущая MUT-модель сравнивает- ся со своей базовой моделью или когда сравниваются две диаграммы одной и той же модели. В рамках инкрементального подхода непротиворечивость оценивается локально до тех пор, пока текущий комплектующий модуль не будет интегриро- ван в систему большего размера. Процесс интегрирования должен принимать
Глава 4. Тестирование аналитических и проектных моделей меры против того, чтобы новый комплектующий модуль не вносил несовместимо- сти в интегрированную модель. Проверка непротиворечивости позволяет определить, имеют ли место проти- воречия и конфликты в какой-то одной диаграмме или между двумя какими-либо диаграммами. Например, диаграмма последовательности сообщений может потре- бовать указать отношения между двумя конкретными классами, в то время как на диаграмме класса не показано ни одного отношения. Противоречивость сначала проявляются в виде некорректных результатов в контексте одной из двух этих диаграмм и корректных результатов в контексте другой. Противоречия выявляют- ся путем тщательного изучения диаграмм, используемых в моделях, во время мо- делируемого выполнения. Дополнительные качества определяют системные атрибуты, которые коллектив разработчиков намеревается проверить. Например, модели на уровне архитектуры должны соответствовать требуемым рабочим характеристикам. Тестовые случаи целенаправленной проверки могут быть использованы в качестве сценариев про- цесса тестирования. Структурные модели, используемые для вычисления значе- ний рабочих характеристик, могут применяться к этим сценариям, которые, в свою очередь, отбираются на основе профиля использования для вычисления об- щей производительности и для выявления потенциальных узких мест. Если архитектура выбрана с условием упрощения изменений, для оценки сте- пени успеха в достижении этой цели должны задействоваться тестовые случаи, построенные на базе случаев изменения (см. раздел «Тестовые модели для допол- нительных качеств»). Организация целенаправленной инспекционной деятельности Основные роли В деятельности, обеспечивающей целенаправленную проверку, существуют три ключевых роли, которые должен взять на себя персонал, выполняющий тести- рование. 1. Специалист в предметной области. Персонал, выступающий в этой роли, рассматривается как источник истины (или, по меньшей мере, ожидаемых результатов). Они определяют ожидаемый отклик системы на конкретный входной сценарий. Во многих предметных областях квалифицированные разработчики являются экспертами в своих предметных областях. Они мо- гут составить первую линию проверки правильности. Однако основную роль в процессе проверки все же играют дополнительные, внешние источ- ники экспертной оценки. 2. Тестировщик. Специалисты, выступающие в этой роли, проводят анализ, необходимый для выбора эффективных тестовых случаев. Роль тестиров- щиков часто берут на себя создатели базовой модели. Когда масштаб про- верки выходит на общесистемный уровень, авторами тестовых случаев час-
Глава 4. Твстированив аналитических и проектных моделей то оказываются члены коллектива системных тестировщиков. Они выпол- няют построение входного сценария, беря за его основу предусловия случая использования, тестирующих действий, какими они определены в сцена- рии, альтернативных путей или раздела исключений случая использования, а также ожидаемых результатов, как их определили специалисты в предмет- ной области. 3. Разработчик. Создатели MUT-модели исполняют роль «разработчиков». Они поставляют информацию, которая не отражена в модели. За исключе- нием проектов, в которых программные коды генерируются непосредствен- но на базе информации, содержащейся в модели, большинство разработчи- ков упускают в модели множество деталей, следовательно, необходимую информацию можно получить только от разработчиков. Коллектив разра- ботчиков как бы проводит весь проверяющий персонал через модель, отме- чая действия на диаграммах, раскрывая отношения между диаграммами и демонстрируя истинную реакцию системы на уровне, соответствующем те- кущему состоянию процесса разработки системы. Индивидуальная проверка Как и любой традиционный метод проверки, целенаправленная проверка на- чинается с проверки за столом. Каждый тестировщик составляет контрольный список, характерный для типа тестируемой модели. Определенные виды ошибок, обусловленные неполнотой и противоречивостью, легко выявляются во время индивидуальной проверки. Оказывается, что это задача проще других поддается автоматизации. Некоторые инструментальные средства делают возможным прове- дение ограниченного числа статических проверок, которые по большей части от- носятся к категории синтаксических проверок. У нас имеется опыт успешного расширения этих возможностей в некоторых проектных средах за счет примене- ния языков подготовки сценариев. Мы здесь не приводим подробностей, ибо об- становка меняется чуть ли не каждый день, однако учтите эту особенность, при- нимая решение о покупке очередного инструментального средства. Подготовка проверки Описание проверки Когда планируется целенаправленная проверка, должна быть указана область и глубина материала, подвергаемого проверке. Модели, которые строятся на ранних стадиях разработки проекта, подобные модели требований или модели предмет- ной области, могут подвергаться проверке во всей своей полноте в пределах одно- го сеанса. Более поздние модели слишком велики, чтобы позволить это. В разделе «Практических моделей» (см. ниже) речь пойдет о способах создания модульных диаграмм, которые можно сгруппировать во фрагменты разных размеров. Наличие модульных моделей позволяет свести проверку до работы одной группы модулей или даже до специфической иерархии классов.
Глава 4. Тестирование аналитических и проектных моделей Область проверки определяется описанием набора случаев использования, на- бора пакетов или абстрактных классов/интерфейсов. Область проверки определя- ет исходные точки для запуска сценариев, в то же время другие классы втягивают- ся в область проверки по мере возникновения необходимости в поддержке сценариев с их стороны. Глубина проверки определяется путем описания слоев в иерархиях агрегирования, ниже которых сообщения не пересылаются. Классы нижнего слоя просто возвращают значения, не указывая при этом, каким образом эти значения были вычислены. Реалистичные модели Обычно невозможно или нежелательно отображать все подробности программ промышленного применения в нескольких комплексных диаграммах одной моде- ли. Возникает необходимость в создании множества диаграмм классов, диаграмм состояний и, естественно, в большом числе диаграмм последовательностей сооб- щений. В процессе подготовки целенаправленной проверки разработчики должны организовать модель с расчетом упростить обзор за счет построения дополнитель- ных диаграмм, которые соединят между собой существующие диаграммы, или пе- ресмотреть диаграммы так, чтобы они соответствовали области проверки. Одной из базовых технологий, которые делают модель более понятной, являет- ся разбиение диаграмм по слоям. В результате применения этого метода получа- ются более индивидуализированные диаграммы, в то же время каждая такая диаг- рамма обладает достаточной модульностью, чтобы оказаться в области конкретной проверки. Упомянутые диаграммы проще создавать, ибо они придерживаются оп- ределенных шаблонов. Рисунок 4.6 служит иллюстрацией одного из видов разбиения по слоям диаг- рамм классов, в условиях которого классы объединяются в пакеты, а пакеты, в свою очередь, могут заключаться в другой пакет. Помимо этого, мы часто отобра- жаем все специализации абстрактного класса в виде одной диаграммы (см. рис. 4.7), а все отношения агрегирования конкретного класса — на другой диаграмме. На рис. 4.8 продемонстрирована технология связывания диаграмм классов. Один коллектив разработчиков использует плоды трудов других коллективов раз- работчиков. Это можно показать, если поместить на краю диаграммы классов, раз- работанных одной группой, прямоугольник, обозначающий класс, разработанный другой группой, а после этого отобразить отношения между всеми классами. Про- верка затрагивает только классы, представленные на диаграмме первой группы разработчиков. Сообщения, направляемые объектам «пограничных классов», в дальнейшем не отслеживаются. Возвращаемые значения, если таковые имеются, просто фиксируются. На рис. 4.9 показано разбиение по слоям диаграмм последовательностей сооб- щений. На каждом уровне диаграмма заканчивается на одном из интерфейсов или абстрактном классе. Таким образом, конкретная диаграмма последовательности сообщений создается для каждого класса, который реализует интерфейс или кон- кретизирует абстрактный класс.
Главе 4. Тестирование енвлитических и проектных моделей Рисунок 4.В. Диаграмма классов, разбитая по слоям в пакеты Рисунок 4.7. Разделение отношений
Глава 4. Тестирование аналитических и проектных моделей РисуНОК 4.8. Связи между диаграммами классов
Глввв 4. Тестироввнив внвлитичвских и проектных моделей Рисунок 4.9. Диаграмма последовательности сообщений для каждой реализации интерфейса Отбор тестовых случаев для целей проверки Для любого конкретного случая использования обычно существует много воз- можностей построения тестовых случаев. Традиционные технологии тестирования используют такие методы отбора эффективных тестовых случаев, как классы эк- вивалентности или логические пути в программе. Тестовые случаи можно выби- рать с таким расчетом, чтобы стало возможным осуществление специальных видов покрытий или обнаружение некоторых конкретных типов ошибок. Чтобы упрос- тить задачу выбора тестовых случаев, для которых вероятность обнаружения оши- бок путем покрытия различных категорий системных действий, служащих источ- никами этих ошибок, выше, нежели для любых других, применяется классификация ODC (Orthogonal Defect Classification — ортогональная классифика- ция дефектов). Профиль случая использования служит для отбора тестовых случа- ев, которые обеспечат уверенность в надежности программного продукта. Этот профиль показывает, какая часть программы используется наиболее интенсивно. Ортогональная классификация дефектов как селектор тестовых случаев Классификация ODC (Orthogonal Defect Classification — ортогональная класси- фикация дефектов) [Chill92] — это схема, разработанная компанией IBM на осно- ве результатов анализа больших объемов данных. Виды деятельности, которые обеспечили возможность выявления ошибок, получили название «триггеров отка- зов». Триггеры отказов разделяются на группы в зависимости от того, когда они выполняются, например, во время ревизий или проверок. На рис. 4.10 представ- лен список атрибутов, которые способны провоцировать проявление ошибок во время ревизий и проверок. Метод целенаправленной проверки использует не- сколько таких триггеров в качестве ориентиров для выбора тестовых случаев. На протяжении книги будет дано краткое описание нескольких таких триггеров, а некоторые из них мы рассмотрим прямо сейчас.
Глава 4. Тестирование аналитических и проектных моделей 1. Соответствие проекта определяется путем сравнения тестируемой модели с базовой моделью или проверкой тестируемой модели на соответствие тре- бованиям. Это сравнение есть прямой результат выполнения тестового слу- чая. 2. Параллельность представляет собой триггер, который можно обнаружить в проектной модели, при этом появится возможность составлять сценарии, которые непосредственно исследуют взаимодействие потоков. Первичным исходным кодом для символического выполнения служит UML-диаграмма конкретного вида деятельности. 3. Горизонтальная совместимость активизируется отслеживанием сценариев между объектами на диаграмме последовательности сообщений. Структурируя процесс целенаправленной проверки таким образом, чтобы можно было задействовать по возможности максимальное число упомянутых триггеров, есть шанс добиться такой ситуации, когда руководящие проверкой тес- ты оказываются способными провоцировать максимально возможное количество проявлений ошибок. Соответствие проекта - соответствие базовой и текущей моделей. Операционная семантика - отслеживание логики. Параллелизм - исследование синхронизации между потоками/процессвми. Обратная совместимость - сравнение с предшествующим программным продуктом. Горизонтальная совместимость - сравнение с интерфейсвми, которые используют данный интерфейс. Редкая ситуация - исследование непредусмотренного поведения. Побочные эффекты - исследование поведения, выходящее за рамки контекста текущего программного продукта. Непротиворечивость/полнота документации - проверка на непротиворечивость/ полноту. Зависимость от конкретных языков - проверке специфических деталей конк- ретных языков. Рисунок 4.1 □. OCD-классификация триггеров при проведении ревизий и проверок Профили использования как селекторы тестовых случаев Профиль использования (см. раздел «Профили использования») системы - это упорядочение индивидуальных случаев использования, в основу которого поло- жено некоторое сочетание значений частоты использования и критичности для отдельных случаев использования. Традиционный функциональный разрез, при- меняемый в процедурно-ориентированных системах, базируется строго на данных о частоте использования. Комбинация рейтингов частоты использования и кри- тичности, применяемая для упорядочивания случаев использования, обеспечива- ет получение осмысленного критерия обеспечения качества. Например, можно нарисовать эмблему в правом нижнем углу каждого окна. Это повторяется доволь-
Глава 4. Тестирование аналитических и проектных моралей СВОДКА ДАННЫХ О МЕТОДАХ - ПОСТРОЕНИЕ ТЕСТОВЫХ СЛУЧАЕВ ПО ДАННЫМ О СЛУЧАЯХ ИСПОЛЬЗОВАНИЯ Тестовый случай состоит из некоторого набора предусловий, стимулирующего воз- действия (входные данные] и ожидаемого отклика. Случай использования содержит некоторую последоватальность сценариев: нормальный случай, расширения и исклю- чительные ситуации. Каждый сценарий предусматривает дайствия, предпринимаемые действующим субъектом, и требует от системы отклика, который соответствует основной части тестового случая. Чтобы построить тестовый случай по тому или иному сценарию, каждая часть этого сценария конкретизируется путам установки точных значений для всех атрибутов и объектов. Это требует координации мажду диаграммой случая использования и другими диаграммами. «Вещи», на которые имеются ссылки в сценарии, должны быть преобразованы в соответствующий объект или объекты из диаграмм классов. Каждый из этих объектов должен находиться в состоянии, опре- деленном диаграммами состояний соответствующих классов. Действия, предусмат- риваемые случаем использования, должны соответствовать сообщениям, направля- емым этим объектам. Каждый сценарий может стать источником множества тестовых случаев, если вы- бирать различные значения (т.е. состояния) для объектов, используемых в конкретном случае использования. Та часть тестового случая, которая соответствует ожидаемому результату, получается из части случая использования, которая соответствует сце- нарию, и конкретных значений, представленных в сценарии ввода. Далее следует сценарий случая использования и соответствующий тестовый случай. • Случай использования подсистемы: Объект movablaPiaca (движущийся предмет) получает сообщение tick() (соударение), посла чего он должен проверить, было ли столкновение с неподвижным предметом stationaryPiaca. • Предусловие теста: Кирпич находится в пределах одного удара шайбы, которая напраалена в его сторону. • Тестовый случай: Вход — шайба получает сообщение с требованием ударения о кирпич. • Ожидаемый результат: Шайба изменила направление движения, а кирпич изме- нил своа состояние с активного на состояние «капут», посла чего он ломается и исчезает из игрового поля. но часто, но если это не получится, система все еще способна выполнять наиболее важные функции для пользователя. Аналогично, соединение с сервером локаль- ной базы данных происходит крайне редко, однако неудача этой операции сделает невозможным успешное выполнение множества других функций. Количество те- стовых случаев, приходящихся на один случай использования, выбирается в за- висимости от положения этого случая использования в рейтинговой таблице. Риск как селектор тестовых случаев В некоторых методах тестирования риск используется в качестве базы для оп- ределения, какие объемы тестирования являются достаточными. Это полезно во время разработки, когда выполняется активный поиск дефектов. В то же время это нецелесообразно по окончании разработок, когда предпринимаются попытки достичь некоторого уровня надежности. На этом этапе метод профиля использо- вания поддерживает проведение тестирования приложения в таком режиме, в ка- ком оно будет использовано на практике.
Глааа 4. Тестирование аналитических и проектных моделей Предлагаемый нами шаблон случая использования фиксирует информацию, необходимую для применения каждого из этих методов, чтобы ими можно было воспользоваться на протяжении полного жизненного цикла. Для целенаправлен- ной проверки применяется показатель частота использования/критичность вместо информации о рисках, поскольку мы пытаемся заручиться такой же перспекти- вой, как и тестирование системы по завершении ее создания. В ситуациях, когда проверка охватывает только часть проекта, использование информации о рисках может оказаться в равной степени полезным. Построение тестовых случаев Тестовые случаи, предназначенные для целенаправленной проверки, представ- ляют собой сценарии, которые должны входить в состав тестируемой модели. До подтверждения корректности модели требований источником сценариев служит коллектив специалистов в предметной области, в задачу которых не входит фор- мулирование требований. Ниже будет показано, как это делается. А сейчас мы остановимся на тестовых случаях, в основе которых лежат системные требования. У применяемого нами шаблона случая использования (его сокращенный вари- ант показан на рис. 4.11) существуют три источника сценариев. Сценарий исполь- зования представляет собой оптимистический сценарий, который выбирается чаще других. В раздел Альтернативные пути могут быть включены несколько сценариев, которые отличаются от сценария использования в различных аспектах, оставаясь, однако, полноценными путями исполнения. В раздел Исключительные пути попа- дают те сценарии, которые приводят к возникновению ситуации ошибки. Случай использования №1 Действующий субъект (актар): Игрок Сценарий использования: Пользоветель выбиреет из меню элемент Pley (игреть). В ответ система начинает сеанс игры. Альтврнативныа пути: Если сеанс игры а самом разгаре, этот еыбор игнорируется. Исключительные пути: Если сеанс игры не способен вывести изображение не экран, появляется сообщение об ошибке, и программа завершается аварийно. Частота использования: Низкая. Критичность: Высокая. Риск: Средний. Рисунок 4.11. Пример случая использования Завершение построения контрольной таблицы Прежде чем начинать интерактивный сеанс проверки, проверяющие исследуют модели на предмет наличия определенного вида синтаксической информации, чтобы дать ей оценку, используя для этой цели информацию, содержащуюся в самой модели. Эта часть метода не касается содержимого модели, она касается только ее формы. На рис. 4.12 показана контрольная таблица, которая использу-
Глава 4. Тестирование аналитических и проектных моделей Перечень вопросов, касающихся проектирования, которые сформулированы на языке UML Да Нет Проблемы преобразования модели анализа в проектную модель. Выходят ли все классы, содержащиеся в модели анализа, но не представленные в проектной модели, за пределы области действия приложения? Являются ли все состояния, представленные в картах состояний модели анализа, также и состояниями, представленными в диаграммах состояний проектной модели? Одинаковы ли последовательности сообщений во всех диаграммах последовательностей сообщений на проектном уровне даже при наличии возможности включения дополнительных сообщений в набор сообщений на уровне анализа? Внутренние проблемы проектных моделей. Все ли ассоциации, представленные без информации о навигационных свойствах, являются двунаправленными? Все ли композиционные отношения являются однонаправленными? Является ли каждая диаграмма последовательности сообщений подмножеством какой-либо диаграммы одного из видов деятельности? Появляется ли каждое сообщение, переданное в диаграмме взаимодействия, в виде метода в общедоступном интерфейсе класса объекта-получателя? Поступает ли каждое сообщение, передаваемое в диаграмме взаимодействий, на логически соответствующий объект? Являются ли все переходы из диаграммы состояний взаимно исключающими? Содержат ли все конечные автоматы, за исключением постоянных объектов, начальные и конечные состояния? Все ли методы общедоступного модификатора представлены как переходы в любое состояние, если даже единственный результат состоит в том, что они сохраняют одно и то же состояние? Существует ли для каждого пункта постусловий каждого метода диаграмма последовательностей сообщений, которая соответствует случаям использования, достигающим порога отношения частота использования/ критичность? Все ли сообщения корректно представлены как синхронные или асинхронные? Совпадает ли количество разветвлений с количеством объединений для каждого вида деятельности, представленного на диаграмме? Рисунок 4.12. Контрольная таблица стадии проектирования
Глава 4. Тестирование аналитических и проектных моделей ется на стадии проектирования. Таблица разделена на две части. В первой части содержится информация, касающаяся сравнения тестируемой модели с моделью анализа. Например, контрольная таблица напоминает проверяющему о том, что требуется выяснить, нужно ли было классы, которые уже удалены, удалять по причине отличия данных анализа от данных проектирования. Вторая часть имеет дело с проблемами, возникающими в тестируемой модели. Контрольная таблица подсказывает проверяющему, позволяет ли правильное использование синтаксиса получить соответствующую информацию. Например, она побуждает проверяюще- го рассмотреть вопрос о навигационных свойствах соответствующих ассоциаций и о том, корректно ли они представлены. Сеанс интерактивной проверки Та часть целенаправленной проверки, которая осуществляет тестирование, организована одним из двух способов в зависимости от того, автоматизирована рассматриваемая модель или нет. Если создается прототип или какой-либо другой вид рабочей модели, эта процедура немногим отличается от обычной процедуры тестирования программных кодов. Тестовые случаи, которые предлагают тести- ровщики, обычно реализованы на некотором сценарном языке и выполняются с привлечением средств моделирования прототипа модели. Такие тестовые случаи должны быть описаны с большей строгостью, нежели тестовые случаи, предназна- ченные для использования в интерактивных сеансах с символическим выполне- нием. Результаты выполнения подвергаются оценке, и группа самостоятельно ре- шает, прошла эта модель испытания или нет. Если прототип модели отсутствует, процедура тестирования сводится к инте- рактивному сеансу, в котором участвуют как тестировщики, так и разработчики. Разработчики принимают в этом участие с тем, чтобы осуществить символическое выполнение, моделирующее обработку, которая будет выполняться, когда будут готовы фактические программные коды. Другими словами, они проводят тести- ровщиков по сценариям, предложенным тестовыми случаями. На время проведения процедуры интерактивного тестирования персоналу мо- гут быть назначены указанные ниже роли. Одно и то же лицо может выступать в нескольких ролях одновременно. Координатор — Координатор управляет процедурой тестирования и не по- зволяет ей отклонятся от заданного сценария. Процедура тестирования не предназначена ни для отладки, которую заинтересованы выполнить разра- ботчики, ни для расширения требований, в котором заинтересованы специ- алисты в предметной области. Координатор удерживает выполнение про- цедуры тестирования в правильном русле и не позволяет ей уступать пожеланиям ни той, ни другой стороны.
Глава 4. Тестирование вналитических и проектных моделей ПРОФИЛИ ИСПОЛЬЗОВАНИЯ Один из методов распределения ресурсов тестирования определяет, какая часть приложения используется наиболее интенсивно с тем, чтобы подвергнуть эту часть наиболее тщательному тестированию. Данный принцип можно сформулировать сле- дующим обрезом: «подвергать тестированию неиболее често используемые и наи- более аажные части прогреммы а более широком диапазоне наборов входных данных, нежели реже используемые и менее вежные ее чести с целью максимельного удовлетворения потребностей пользователя». Профиль использовения — это рейтинг случаев использования, упорядоченный на основе некоторого сочетания значений частоты использовения/критичности. Это следует рассматривать кек двойную сор- тировку случеев использовения по числу использовения некоторой функции конечного пользователя (методе интерфейса] или по ожидаемому числу использования при фактической реботе программы, а также по критичности каждого случая использо- вания. Критичность есть значение, присвоенное экспертеми в предметной области. Информация о частоте использования может быть получене двумя различными путями. В рамках первого подхода соответствующие данные можно получить либо при фак- тическом использовании, либо во аремя тестировения используемости, ревно как и в процессе фактического функционирования, во время тестирования будущих версий программного продукта. Это даст нам грубый профиль подсчете. Количество обра- щений к кеждому поведению делится на общее число вызовов, в результате чего получеется процентное отношение. Второй подход предусматривеет изучение сути и функций системного интерфейсе с последующим подсчетом относительного количе- ства использования каждого метода. В результате получается упорядочивение ме- тодов конечного пользоветеля, а не точный подсчет частоты использовения. Подсчи- танное количество вызовов каждого поведения делится на общее количество вызовов, в результате чего получается процентное отношение. Процентное отношение, вычис- ленное для кеждого случея использовения, определяет процентное отношение тес- товых случеев в наборе, который предназначается для тестирования данного случая использовения. В качестве примера можно привести функцию Exit игры «Кирпичики», которая будет успешно выполняться в точности один рез за каждый вызов прогреммы, в то же время метод NewGame может быть использован несколько раз. Вполне вероятно, что функция Help вообще окежется невостребованной на протяжении всего периода использования системы. Эте ситуация отобразится и в профиле, в котором определен порядок следования функций Exit, NewGame или Help. Мы можем присвоить им весе, отражающие ожидеемые частоты использования. Если мы подсчитали, что игрок сыграет в среднем 10 игр, прежде чем покинуть систему, веса, соответственно, принимеют значения 10, 1, 1. Функция NewGame будет выполняться в В2.5% тестовых случеев (10 из 12 случаев), в то время как не функции Exit и Help приходится по В.5%.
Глава 4. Твстированив анвлитических и проектных моделей Регистратор - Лицо (обычно это тестировщик), которое делает пометки на эталонных моделях, когда группа приходит к заключению, что обнаружена ошибка. Регистратор следит за тем, чтобы обнаруженные ошибки учитыва- лись в последующих частях сценария с тем, чтобы не тратить времени на многократное выявление одной и той же ошибки. Регистратор ведет список проблем, которые не были решены за время сеанса тестирования. Это могут быть не только ошибки. Возможно, возникнет необходимость получить ин- формацию от группы, работающей с другой частью проекта или от члена рабочей группы, который отсутствовал во время проведения проверки. Чертежник — Этот человек строит диаграмму последовательности сообще- ний по мере выполнения сценария. В задачу чертежника входит отображе- ние на диаграмме всех необходимых деталей, таких как, например, возврат из сообщений или изменение состояний. Чертежник может также снабжать диаграмму последовательности сообщений комментариями, помещая их между стрелкой сообщения и стрелкой возврата. Процедура целенаправленной проверки легко может превратиться в сеанс ин- терактивной разработки. Ее участники, особенно разработчики, во время тестиро- вания проявляют желание изменить модель по мере возникновения все новых задач. Сдерживайте их натиск. Это классический пример путаницы, когда смеши- ваются задачи тестирования и отладки, которые отвлекают внимания от других обнаруженных дефектов проекта. Регистратор фиксирует отказы, обнаруженные проверкой, с тем чтобы к ним можно было вернуться позднее. Благодаря этому рабочая группа не отвлекается от поисков дефектов и не делает поспешных выво- дов относительно истинных причин отказов. Если обнаружено слишком большое число проблем, требующих решения, завершайте процедуру тестирования и дайте разработчикам возможность продолжить работу над моделью. Тестирование специальных типов моделей Основной метод целенаправленной проверки не меняется при переходе от од- ной фазы проекта к другой, однако некоторые характеристики содержимого моде- ли и некоторые аспекты группы проверки претерпевают изменения. Уровень детализации модели возрастает по мере продвижения разработки. Объемы информации также возрастают по мере продвижения разработки. Для специальных моделей точную интерпретацию критерия оценки можно сделать более специфичной. Персональный состав группы, проводящей проверку, меняется, причем он подбирается в зависимости от типа модели. Далее мы обсудим модели в различные моменты жизненного цикла.
Глава 4. Тастированив аналитичаских и проектных моделей Модели требований Требования к приложению сводятся в одно место при построении модели ви- дов использования системы. Конструкция языка UML, используемая для этой моде- ли, представляет случай использования, разработанный Якобсоном (Jacobson) [JCJO92], который рассматривается в главе 2. На рис. 4.13 показана сокращенная версия текстового формата, употребляемого для конкретного случая использова- ния. На рис. 4.14 в качестве примера приводится диаграмма случаев использова- ния игры «Кирпичики» на языке UML, а на рис. 4.16—4.21 показаны образцы тек- стового описания случаев использования. На диаграмме случая использования отображены отношения между случаями использования. Отдельные случаи ис- пользования разбиваются на «подслучаи использования» при помощи отношений uses (использует) и extends (расширяет). Ниже, в главе 9, мы воспользуемся эти- ми отношениями для структурирования системных тестовых случаев. Текстовые описания содержат основную часть информации о каждом случае использования. Если указанные выше отношения служат для структурирования тестов, то тексто- вые описания применяются для выражения основной порции информации о слу- чае использования. Приемочные испытания часто обнаруживает ошибки, источником которых яв- ляется неправильная формулировка требований. Одна из типичных проблем обусловлена отсутствием необходимых требований (неполная модель требова- ний), требованиями, которые противоречат друг другу (противоречивая модель), и сценариями, в условиях которых система функционирует не так, как требуется клиенту (некорректная модель). Многие из этих проблем могут быть выявлены на более ранних, нежели приемочные испытания, стадиях путем целенаправленной проверки. Критерий оценки моделей интерпретируется применительно к модели требо- ваний, представленных на рис. 4.15. Случай использования №5 Действующий субъакт: Игрок Сценарий использования: Пользователь приостанавливает сеанс игры, нажимая соотватствующую кнопку мыши. Система отвечает тем, что прерывает движение шайбы и не отвечает на движение мыши. Альтарнативныа пути: Исключительные пути: Если сванс игры приостановлен, нажатия на кнопку мыши игнорируется. Частота использования: Низкая. Критичность: Низкая. Риск: Средний. Рисунок 4.13. Пример случая использования
Глава 4. Тестирование аналитических и проектных моделей Игрок «uses» Начало игры «uses» Игрок проигрывает «uses» Игрок приостанавливает игру, вызывая окно About Игрок выигрывает Игрок выходит из игры «uses» «uses» «uses» «extends» Игрок приостанавливает игру посредством левой кнопки мыши Игрок приостанавливает игру Рисунок 4.14. Модель случаев использования игры «Кирпичики» Шайба ударяется в Paddle (лопатка), Bricks (кирпич), Wall (стена)__________< «extends» Критерий Интерпретация применительно к требованиям Полноте Случаи использования представляют собой все функцио- нальные средства, необходимые для обеспечения удовлетво- рительного качества программного продукта. Не включается ни один случай использования, представляющий собой не- используемые функциональные средства. Корректность Каждый случай использования представляет собой в точно- сти одно требованив. Непротиворечивость Все системные функциональные средства определяются одинаково, где бы они не описывались. Рисунок 4.15. Критерии, используемые при проверке требований Случай использования №1 Действующий субъект: Игрок Сценарий использования: Пользователь инициирует выполнение исполнительно- го модуля игры «Кирпичики», проводит сеанс игры, а затем выбирает функцию Exit из меню File. Альтернативные пути: Исключительные пути: Честота использования: Низкая. Критичность: Высокая. Риск: Низкий. Рисунок 4.1 В. Пример случая использования № I
Глава 4. Тестирование аналитических и проектных модалай Случай использования №2 Действующий субъект: Игрок Сценарий использования: Игрок инициирует исполнитальный модуль игры «Кирпичики», игравт сеанс, разбивает все кирпичи и побеждает. Альтернативные пути: Исключительные пути: Частота использования: Средняя. Критичность: Высокая. Риск: Средний. РисуНОК 4.17. Пример случая использования №2 Случай использования №3 Действующий субъект: Игрок Сценарий использования: Игрок инициирует исполнитальный модуль игры «Кирпичики», играет саанс, расходует все шайбы и проигрывает игру. Альтернативные пути: Исключительные пути: Частота использования: Средняя. Критичность: Высокая. Риск: Средний. РисуНОК 4.18. Пример случая использования №3 Случай использования №4 Действующий субъект: Игрок Сценарий использования: Шайба ударяется в лопатку, разбивает несколько кирпичей, ударяется в стену. Альтернативные пути: Исключительные пути: Частота использования: Высокая. Критичность: Высокая. Риск: Средний. РисуНОК 4.19. Пример случая использования №4
Глава 4. Тестирование аналитических и проектных моделей Случай использования №5 Действующий субъект: Игрок приостенавливает игру, нажимая лееую кнопку мыши. Сценарий использоаания: Игрок Альтернативные пути: Исключительные пути: Частота использования: Низкая. Критичность: Низкая. Риск: Средний. Рисунок 4.20. Пример случая использования Ns5 Случай использоаания №В Действующий субъект: Игрок приостанавливает игру, выбирая из меню пункт About. Сценарий использоаания: Игрок Альтернативные пути. Исключительные пути: Частота использования: Низкая. Критичность: Низкая. Риск: Средний. Рисунок 4.21. Пример случая использования Ns6 Специалист а Специалист в предметной области, клиент, персонал, предметной области разрабатывающий технические требования Тестировщик Системный тестировщик, специалист в предметной области Разработчик Системотехник Рисунок 4.22. Роли при проверке требований Полнота — это типичная проблема, обусловленная неправильной формулиров- кой требований, для которой модель итеративного инкрементального процесса представляет собой частичное решение. Целенаправленная проверка может ока- зать дальнейшую помощь, потребовав подробного исследования силами независи- мой группы специалистов в предметной области и программистов, разрабатываю- щих технические требования. Такие исследования выявляют нехватку конкретных требований намного раньше, чем типовые процессы тестирования. В задачу подробного исследования входит также выявление ошибок, обуслов- ленных нарушением корректности. Сам акт написания тестовых случаев для целе- направленной проверки выявляет многие неточно сформулированные требова- ния, не позволяющие записывать тестовые случаи. Прогон тестовых случаев
Глава 4. Твстироввнив вналитичвских и проектных модвлвй создает предпосылки для выявления группой, проводящей тестирование, расхож- дений между ожидаемыми результатами и фактическим содержанием модели тре- бований. Чем крупнее система, тем больше проблем с достижением непротиворечивости требований. Ко всем противоречиям довольно часто добавляется необходимость выявлять места, в которых один случай использования заменяет другой. Напри- мер, один случай использования предусматривает выполнение некоторого дей- ствия по истечении по меньшей мере десяти секунд, в то время как другой случай использования ожидает выполнение того же действия через семь секунд. Исполь- зование сквозных сценариев, которые полностью отслеживают это действие, по- могает выявить упомянутые несоответствия. Одной из особенностей модели требований, которая влияет на организацию проверки, является то обстоятельство, что не существует UML-модели, служащей основой для этих требований. Таким образом, все сравнения с базовой моделью сводятся к ссылкам на документы, составленные на уровне маркетинговых иссле- дований, системотехники и клиентских организаций. Поскольку они давно при- обрели печальную славу источников ошибок, стоит предпринять дополнительные усилия по проверке корректности модели требований. Роли в такой проверке распределены так, как показано на рис. 4.22. Вам при- дется приспособить их для собственной ситуации. Специалисты в предметной области дадут «правильные» ответы на тестовые случаи. В данном случае это озна- чает согласиться или не согласиться с тем, что случай использования адекватно представляет необходимые функциональные возможности. Использование экс- пертов по системным испытаниям в роли тестировщиков позволяет этим специ- алистам получить информацию об источниках тестовых случаев для тестирования системы на ранних стадиях разработки проекта и возможность внести свой вклад в усовершенствование этих случаев использования. Мы также используем вторую группу экспертов в предметной области и специалистов, разрабатывающих техни- ческие требования. Все это образует источник сценариев, независимый от людей, сформулировавших требования. Некоторые организации предпочитают иметь дело со случаями использования, написанными разработчиками, а не специалис- тами по системам из третьих организаций. Именно перед этими разработчиками ставится задача прогона тестовых случаев. СОВЕТ Если перед вами будет поставлена задвча разделить специалистов в предметной области на две группы, нв выбирайте в качвствв руководящего принципе идеологию. Это только ввергнет коллектив в творвтичвскив дебеты. Рвэдвлитв их твким об- разом, чтобы в каждую группу входило как можно больше првдстввитвлвй различ- ных «твчвний». Основные принципы «тестирования» модели требований, наряду с примерами из игры «Кирпичики», сведены в приводимый ниже перечень. Текст, касающийся примеров, выделен курсивом. 6 2-7!
Глава 4. Твстированиа аналитичаских и проактных модалвй 1. Разработать классификацию случаев использования путем вычисления обобщенных показателей частоты использования и критичности случаев ис- пользования. На рис. 4.23 такой перечень показан для игры «Кирпичики». 2. Определить общее число тестовых случаев, которые можно построить в ус- ловиях доступных ресурсов. Это можно сделать на базе имеющихся статис- тических данных. Мы полагаем, что у нас есть временной ресурс для прогона 15 тестовых случаев. 3. Распределить тестовые случаи на основании полученной классификации. Обратите внимание на тот факт, что на рис. 4.23 распределены только 14 тестов из 15, поскольку невозможно поровну распределить нечетное количество тестов. 15-й тест мы относим к категории, в которой будет зарегистрирова- но максимальное число отказов на начальном цикле тестирования. 4. Написать сценарии только на основании знаний сценариев в роли специа- листа по предметной области. Количество сценариев определяется исходя из значений, полученных путем вычислений на шаге 3. Игрок начинает игру, перемещает лопатку и ломает несколько кирпичей в тот момент, когда теряет шайбу. Система отвечает тем, что снабжает игрока новой шайбой. 5. На совещании специалистов, формулирующих требования, и программис- тов, составляющих сценарии, автор сценариев ставит на обсуждение каж- дый сценарий, и специалисты, разрабатывающие модели требований, выяв- ляют такой случай использования, в который тестовый сценарий входит либо как главный сценарий, как расширение или как исключительная ситу- ация, либо альтернативный путь, представляющий этот сценарий. Если не удается отыскать ничего подобного, фиксируется дефект неполноты. Если такой сценарий представлен двумя или большим числом случаев использо- вания (на том же уровне абстракции), возникает дефект противоречивости. В обоих этих случаях первый возникающий вопрос можно сформулировать Случай использования Частота использования Критичность Комбинированное значение Ранг К-во тестовых случаев Инициировать игру «Кирпичики» Средний Высокий Высокий 1 3 Приостановить игру «Кирпичики» Низкий Низкий Низкий 3 1 Останов игры «Кирпичики» Средний Низкий Средний 2 2 Разбить кирпич Высокий Высокий Высокий 1 3 Выигрывает Средний Высокий Высокий 1 3 Проигрывает Средний Низкий Средний 2 2 Рисунок 5.23. Случаи использования игру «Кирпичики»
Глава 4. Твстироввнив аналитических и проектных моделей следующим образом: заключается ли дефект некорректности в самой фор- мулировке случая использования, которая после устранения дефекта уже не будет противоречить сценарию. В сценарии, представленном на шаге 4, ничего не сказано об ограниченном запасе шайб. Система не сможет предоставить но- вую шайбу, если запас шайб исчерпан. Требования должны четко определиться в отношении фиксированного числа шайб. Большая часть описанных выше действий может быть выполнена повторно при тестировании других моделей. Как классификация случаев использования, так и построение тестовых случаев содействуют обретению тестовыми случаями свой- ства многократного использования. Модель требований служит основой для тес- тирования других моделей, и в силу этого обстоятельства такие тестовые случаи допускают многократное использование. Модели анализа Для нас особый интерес представляют два типа моделей анализа: модель ана- лиза предметной области и модель анализа приложения. Оба типа моделей моде- лируют имеющиеся знания. Одна из них моделирует знания предметной области, в то время как другая моделирует знания о программном продукте. Модель анализа предметной области Модель анализа предметной области отображает информацию об области зна- ний, имеющей отношение к разрабатываемому приложению. Как таковые, эти знания можно получить из литературы и из сведений о предметной области, в отличие от другой UML-модели. И хотя для многих проектов вполне достаточно построения модели предметной области, которая представляет собой простую ди- аграмму классов, большинство предметных областей охватывают стандартные ал- горитмы, а многие из них практикуют ссылки на состояния, которые служат ха- рактеристиками отображаемых моделью понятий. На рис. 4.24 показана интерпретация критерия оценки применительно к модели предметной области. Модель предметной области есть представление знаний, сосредоточенных в конкретной предметной области, какими они предстают в глазах специалистов в Критерий Интерпретация применительно к моделированию предметной области Полнота Существующих понятий достаточно, чтобы охватить описан- ное содвржимов. Используются дополнительные детали для описания понятий нв необходимую глубину. Корректность Даются точные описания понятий предметной области; при- меняемые алгоритмы обеспечивают ожидаемые результаты. Непротиворечивость Элемвнты модвли не должны противоречить определениям и понятиям, используемым компвнией, реализующей проект. Рисунок 4.24. Критерии, используемые при проверке модели предметной области 6*
Глава 4. Тастированив аналитических и проектных моделей этой предметной области. Следует иметь в виду, что часто мнения этих специали- стов не совпадают. Данное обстоятельство привело нас к мысли о том, что целесо- образно разделить имеющийся коллектив специалистов упомянутого профиля на две группы. Одна группа, большая по размеру, создает модель предметной облас- ти, в то время как вторую группу образуют тестировщики этой модели. На рис. 4.25 первая группа обозначена как разработчики, а вторая группа — как тестиров- щики и как специалисты в предметной области. Соответствующий подбор состава обеих групп обеспечивает всестороннее исследование модели. Роль в проверке модели Роль в проекте Спациалист в предметной области Специалист в предметной области Тастировщик Специалист по системным испытаниям, спациалист в предметной области Разработчик Специалист в прадматной области РисуНОК 4.25. Роли, исполняемые при проверке модели предметной области Рисунок 4.26 соотносит различные части диаграммы классов из модели пред- метной области игры «Кирпичики» с ее моделью анализа приложения. Обратите внимание на тот факт, что на нем представлены две предметных области, область Интерактивной графики и область Игр. Тестовые случаи для этой модели поступа- ют от второй группы специалистов в предметной области. Они изучают, как эти концепции используются в типовых приложениях, при работе с которыми накоп- лен богатый опыт. Тестовые случаи должны быть написаны группой, в которую входят специалисты по системному тестированию, знающие, как это делается, и второй группой специалистов в предметной области. Тестовый случай всего лишь формулирует детали на уровне понятий предметной области и выше. Любые дей- ствия суть алгоритмы предметной области. Тестовые случаи для модели предметной области Интерактивной графики бу- дут выглядеть следующим образом: Предположим, что полотно готово и затребовано для отображения некоторого об- раза. Как полотно узнает, куда поместить этот образ? Предполагается, что объект mouseEvent вычисляет координаты, на которые указывает пользователь. Модель анализа приложения В рамках крупного проекта существуют множество моделей предметной облас- ти. Все они привносят свой вклад в единственную модель анализа приложения. Некоторые части модели предметной области будут отброшены, поскольку они не входят в область действия данного конкретного проекта. Некоторые фрагменты моделей предметной области объединяются, образуя единый элемент в модели приложения. Это обстоятельство усложняет оценку полноты во время проверки, поскольку невозможно прямое отображение одной модели на другую. Критерии и роли показаны на рис. 4.27 и 4.28.
Главе 4. Твстироввнив внвлитичаских и проектных моралей Критерий Полнота Корректность Интерпретация применительно к модели анализа приложения Идеи, вырвжавмыв каждым случаем использования, могут быть представлены понятиями и влгоритмами в этой модели. В эту модель не включается проектная информация. Эксперты на отвергают втрибуты и поведения, назначенные каждому понятию, они согласны с шагами каждого алгорит- ма и с основными состояниями каждого концептуального логического объекта.. Напротиаоречиаость Там где существует несколько способов представить поня- тие некоторого действия, всв эти способы эквивалентны. РисуНОК 4.27. Критерии, используемые при проверке модели анализа приложения Роль а проаерке Роль а проекте Специалист а предметной области Специалист в прадметной области, инжанар-системотехник Тастироащик Системный тестировщик Разработчик Разработчик приложения Рисунок 4.28. Роли при проверке модели анализа приложения
Глава 4. Тастироааниа аналитических и проактных медалей Модель анализа может оказаться чрезмерно полной. То есть, она содержит про- ектную информацию, которую коллектив проектировщиков ошибочно сделал ча- стью требований. Это приводит к чрезмерным ограничениям проекта, который может оказаться не таким гибким, каким мог бы быть. Поскольку группа проверя- ющих измеряет покрытие модели тестами, она проверяет фрагменты, которые не покрыты тестовыми случаями, с целью определения, а нельзя ли их вообще уда- лить из модели. На рис. 2.13 показана диаграмма класса для модели анализа приложения игры «Кирпичики». Тестовый случай для модели анализа приложения выглядит следующим обра- зом: Предположим, что сеанс игры начат и игровое поле построено. Как лопатка может помешать шайбе удариться в границу пола? Ожидается, что лопатка пересечет траекторию движения шайбы и столкнется с шайбой. Это столкно- вение заставит шайбу изменить направление движения, при этом, если она по- падет в среднюю часть лопатки, угол ее отражения из точки удара будет ра- вен углу падения. Проектные модели В объектно-ориентированном проекте существуют три уровня проектирова- ния: архитектурный, механистический и детальный. Мы подробно рассмотрим два вида проектных моделей, которые охватывают перечисленные три уровня: модель архитектурного проектирования и подробная модель проектирования классов. Архитектурная модель представляет собой базовую структуру приложе- ния, поскольку в ней дается описание того, в каких отношениях между собой на- ходится множество интерфейсов. Она также определяет точное содержание каждо- го интерфейса. Подробная модель классов содержит точную семантику каждого класса и определяет архитектурный интерфейс, которому соответствует конкрет- ный класс. Архитектурная модель Архитектурная модель представляет собой каркас всего приложения. Некото- рые считают ее моделью, имеющей наиболее важное значение для приложения, в силу чего в данном разделе мы будем рассматривать различные вопросы на более высоком уровне детализации. В этой модели нефункциональные требования пе- ремешаны с функциональными. Данное обстоятельство создает предпосылки для использования сценариев в качестве базы для повышения эффективности моде- лирования и других важных архитектурных ограничений. Тестовый случай в условиях проектирования на уровне архитектуры принима- ет такой вид:
Глааа 4. Тестирование аналитических и проектных моделей Предположим, что объекты BricklesDoc и BricklesView уже построены. Сооб- щение о соударении передается каждому объекту MovablePiece. Как объект BricklesView получает информацию, необходимую для модификации битового образа на экране? Предполагается, что объект BricklesDoc вычислит новое по- ложение каждого битового образа, прежде чем он уведомит BricklesView о том, что изменение имело место. Объект BricklesView вызовет методы объекта BricklesDoc, чтобы получить всю информацию, необходимую для обновления изображения на экране. Мы воспользуемся архитектурой структуры нашей игры, чтобы проиллюстри- ровать все разнообразие методов, рассматриваемых в этом разделе. Впервые мы прибегли к этой структуре в C++ при работе с классами MFC, которые формиру- ют архитектуру, известную под названием архитектуры Document/View (доку- мент/представление) и являющуюся вариантом канонической архитектуры MVC (Model/View/Controller — модель/представление/контроллер) [Gold89], Эта струк- тура затем была реализована в Java с помощью пакета java.awt, который поддержи- вает несколько видоизмененную форму архитектуры MVC. В каждом из этих слу- чаев классы пользовательского интерфейса дают пользователю представление о состоянии игры. Чтобы это стало возможным, пользовательский интерфейс сам должен поддерживать некоторые состояния. Типичная ошибка в таких системах возникает, когда в пользовательском интерфейсе появляется состояние, отличное от состояния, поддерживаемого классами, реализующими модель. Архитектура программного обеспечения — это базовая структура, которая дает определение системы в терминах вычислительных компонентов и взаимодействий этих компонентов [ShGa96], Мы же будем пользоваться терминами компонент (component) и соединитель (connector) для описания соответствующих элементов архитектуры. В нотации языка UML компоненты архитектуры представлены как классы с интерфейсами. Если подробное описание поведения соединителей, обес- печивающих связь между компонентами, отсутствует, то такие соединители могут быть представлены простыми отношениями, устанавливаемыми между классами. Если же соединители обладают состояниями и/или поведением, допускающим ту или иную интерпретацию, то они представляются соответствующими объектами. Представления архитектур Существуют три вида информации, которые широко используются для пред- ставления архитектуры: отношения, состояния и алгоритмы. Базовый язык моде- лирования UML обладает тем преимуществом, что его можно использовать для построения всех трех типов проектных моделей, равно как и моделей анализа. Применение одной и той же нотации для всех трех уровней моделей устраняет необходимость изучения множества других нотаций. Нотации, используемые языком UML, достаточно просты в том смысле, что не требуется никаких специ- альных инструментальных средств, хотя в случае особо крупных моделей поддер- жка со стороны инструментальных средств быстро превращается в необходимость. Инструментальное средство, подобное Rational Rose, осуществляет широкий на-
Глава 4. Тастирование аналитичаских и проактных моделей бор проверок на непротиворечивость в моделях со статическими отношениями. В условиях такого типа представлений тестовые случаи выполняются вручную, с использованием рассмотренных выше методов. Однако в UML нет специального синтаксиса для описания архитектур, так что отображение типа понятие/обозна- чение между архитектурой и символами UML, должно создаваться специально. Такие инструментальные средства, как ObjectTime [Selic94] и BetterState [BetterStateOO], располагают средствами «анимации» проектных диаграмм, а также предусматривают автоматическую проверку некоторых аспектов модели. В част- ности, они поддерживают механизм имитации, который можно задействовать для выполнения сценариев. На диаграммы наносится подробная информация о сце- нариях, равно как и специальная информация, касающаяся имитации. Разработ- чик может «проигрывать» сценарии и следить за тем, как будут выявляться ошиб- ки. В условиях такого подхода построение новых сценариев (и тестовых случаев) существенно упрощается, если ввести в употребление обобщенный шаблон. Од- ним из преимуществ такого подхода является сочетание простоты построения модели с мощными средствами моделирования. Однако, обычно эти инструмен- тальные средства работают с ограниченным набором типов диаграмм. Например, основной задачей инструментального средства BetterState является построение мо- дели того или иного состояния в виде спецификации для системы. В силу этого обстоятельства незавершенными остаются те статические части системы, которые не затрагивают этого состояния. Несомненное достоинство такого подхода заклю- чается в том, что соответствующие сценарии могут выполняться автоматически. В результате упрощается прогон широкого набора сценариев ценой больших перво- начальных затрат времени и усилий на построение самой модели. Упомянутый метод наилучшим образом подходит для небольших систем с малым временем отклика или для систем, требования к которой меняются во время ее разработки очень медленно. Часто такие инструментальные средства помогают отыскать некоторые виды ошибок при вводе в него модели. Проверка на непротиворечивость позволяет воспрепятствовать установлению некоторых видов связей. Сценарии представле- ны в соответствующем формате, таком как, например, последовательный файл входных значений, который считывается в требуемые моменты времени. Действия по имитации часто представляются событиями. Может задействовать специаль- ный обработчик событий, который «захватывает» и «генерирует» события на высо- ком уровне без необходимости реализации подробных алгоритмов. Такой уровень исполнения достаточен для проверки факта наличия требуемых интерфейсов. Ес- тественно, этого явно не достаточно для проверки корректности реализации фун- кциональных возможностей. И наконец, языки для описания архитектуры обеспечивают возможность пред- ставления системы на высоком уровне абстракции. Такие языки, как Rapide [Luckham], который был разработан в Стенфордском университете, позволяет программистам, пишущим модели, выбирать необходимый им уровень детализа- ции. Поток вычислений моделируется событиями, которые образуют поток между
Глава 4. Тестироааниа аналитических и проактных моралей компонентами. Одно из достоинств упомянутого подхода заключается в возмож- ностях управления, которые этот подход предоставляет составителю модели. В таком языке имеется достаточно описательных средств, чтобы поддерживать лю- бой уровень детализации, на каком пожелает работать разработчик модели, не в пример инструментальным средствам, которые мы рассматривали выше и для ко- торых характерен фиксированный уровень представления. Недостаток заключает- ся в том, что эти модели являются программами со всеми проблемами, характер- ными для этого уровня детализации. Когда модель и тестовые случаи представлены в некотором языке программи- рования, прогон тестов можно выполнять автоматически. Языком представления может быть универсальный язык программирования, используемый для реализа- ции прототипа высокого уровня, или специализированный язык, предназначен- ный для описания архитектурных решений, такой как, скажем, Rapide, который применяется для построения стандартных моделей. Уровень детализации, пред- ставленный в прототипе, определяет, насколь специфичным может оказаться тес- тирование. Тестирование архитектуры Метод SAT (Software Architecture Testing — Тестирование архитектуры про- граммного обеспечения) [McGr96] представляет собой специальный тип целенап- равленной проверки, который при тестировании любого программного продукта требует выполнения следующих действий: (1) специально для целей тестирования строятся тестовые случаи; (2) проводится тестирование на программном продукте; (3) результаты выполнения тестов подвергаются проверке на корректность. Этот метод есть метод «тестирования», поскольку в его рамках используются весьма специфические тестовые случаи и имеет силу принцип обязательного прогона, даже если этот прогон иногда приходится выполнять вручную. Группа, которой доверено выполнить этот тип деятельности, разбивается на подгруппы, как пока- зано на рис. 4.29. Далее мы подробно рассмотрим каждый из этих шагов. Роль в проверке Роль в проекте Специалист в Специалист в предметной области, предметной области инженер-системотехник Тестировщик Системный тестировщик Разработчик Тестировщик Рисунок 4.29. Роли при проверке архитектурной модели проекта Построение тестовых случаев Построение тестовых случаев для тестирования архитектуры программного продукта производится на базе случаев использования, как уже было сказано ра- нее. Каждый случай использования описывает семейство сценариев, которые опи- сывают различные типы результатов, могущие быть получены во время конкрет- ного использования системы. Тестовые случаи для архитектуры определяются на
Глааа 4. Тестирование аналитичаских и проектных моделей более высоком уровне, нежели более подробные проектные модели. Полученные результаты применяются для оценки критериев, представленных на рис. 4.30. Критерий Интерпретация применительно к архитектурной модели проекта Полнота Чтобы приложение обладало асеми необходимыми функци- ональными средствами, дается определение достаточного набора интерфейсов. Взаимоотношения интерфейсов обес- печивают осуществление потоков управления и данных, не- обходимых для реализации всех видов использования, пред- ставленных на диаграмма использования. Корректность Архитектура удовлетворяет ее ограничаниям; использует со- ответствующие архитектурные образцы; представляет взаи- модействия интерфейсов. Непротиворечивость Каждое использование системы может быть реализовано только на одном наборе интерфейсов. Рисунок 4.30. Критерии, используемые при проверке архитектурной модели проекта Тестовые случаи по существу определяются на уровне, который реализует ин- терфейсы между подсистемами. Например, для структуры игры важнейшим явля- ется интерфейс между моделью и представлением. Модель разделена между клас- сами Puck, Paddle и BrickPile. Представление сконцентрировано в классе BricklesView. Архитектура модель/представление рассчитана на то, что большая часть взаи- модействий инициируется представлениями, при этом модель уведомляет соот- ветствующее представление о том, что в модели произошли изменения, когда последние имеют место. Поскольку игра «Кирпичики» требует анимации, мы про- водили модификацию архитектуры таким образом, что после построения BricklesView была передана последовательность сообщений, которая снабдила его дескрипторами (обработчиками) различных частей модели. Базовая архитектурная модель показана на рис. 4.31. Тестовые случаи можно выбирать по окончании стадии анализа. Двумя основными операциями являются: (1) установка системы и (2) перерисовывание окна по завершении очередного пе- ремещения. В отличие от большинства систем, построенных на базе архитектуры модель/представление, в данном случае нет необходимости обеспечивать возмож- ность добавления дополнительных представлений. Мы можем построить тестовый случай для каждой операции, однако в этом случае можно ограничиться един- ственным тестовым случаем, получившим название grand tour1 (большой тур). Обычно случаи grand tour характеризуются большими размерами и выдают недо- статочно информации, если их прогоны завершаются неудачей, однако в этом случае вторая операция не может быть выполнена без первой, таким образом, обе операции образуют естественную конъюнкцию. 1 Grand tour представляет собой тестовый случай, который обеспечивает выполнение нескольких от- дельных тестов за один прогон.
Глеве 4. Тестировение енелитических и проектных моделей Рисунок 4.31. Архитектурная модель игры «Кирпичики» Выполнение тестов Тесты выполняются в соответствии с предписаниями для каждого специально- го типа представлений. Мы воспользовались нотацией языка UML, следователь- но, это интерактивная процедура. Мы выполняем тестовый случай, который заключается в построении диаграм- мы последовательности сообщений. Диаграмма отражает предусловия для тестово- го случая. После создания объекта BricklesView создается объект BricklesGame. Как только объект BricklesGame построен, он создает объект PlayField, который, в свою очередь, создает объекты Puck, Paddle и BrickPile. Сообщения, пересекаю- щие архитектурные границы, показаны на рис. 4.32 жирным курсивом. Проверка правильности результатов Обычно при тестировании архитектур этот шаг не связан с особыми трудно- стями, хотя сама задача в условиях подробного представления функциональных средств в окончательном варианте приложения может столкнуться с немалыми трудностями. Когда результаты тестирования представляются в виде диаграмм, полученные диаграммы должны быть проверены специалистами в предметной об- ласти после каждого выполнения теста. Если же выходные данные являются ре- BricklesView Match PlayField Puck Paddle BrickPile create ► create create ► create create ► setPuck 4 setPaddle 4 setBrickPile 4 > ► Рисунок 4,32. Прогон тестового случая.
Глввв 4. Твстироввнив внвлитических и проектных модвлвй зультатом прогона тестового случая, результаты тестирования можно проверить, если заставить специалистов в предметной области построить последовательности событий, которые могла бы сгенерировать только корректно функционирующая архи- тектура. Интерпретация критериев оценки представлена в таблице на рис. 4.29. Дополнительный пример Разумеется, архитектура игры «Кирпичики» исключительно проста, поэтому нам придется рассмотреть типичную трехуровневую архитектуру. Несмотря на то что диаграмма на рис. 4.33 предельно упрощена, с ее помощью можно определить, какие тестовые случаи являются наиболее эффективными. На клиента возложена задача взаимодействия с пользователем, выполнения вычислений, необходимых для представления данных в соответствующем формате, и взаимодействие с биз- нес-моделью, которая находится на сервере приложений. По замыслу сервер при- ложений должен быть основным вычислительным органом, он также ведет обра- ботку всех взаимодействий с клиентом и с компонентами базы данных. И наконец, компонент базы данных обеспечивает постоянное присутствие бизнес- объектов из сервера приложений. В число наиболее важных сценариев для данного типа архитектуры входят сце- нарии множественный клиент/одиночный сервер и множественный клиент/мно- жественный сервер. В следующем разделе мы рассмотрим методы структури- рования тестовых случаев этого типа с таким расчетом, чтобы они были представительными и пригодными для многократного использования. Весьма по- лезными покрытиями такой архитектуры можно считать применение различных сочетаний потоков. Поскольку обычно подобные системы являются распределен- ными, отложим их дальнейшее рассмотрение до главы 8. Оценка эффективности и расширяемости Архитектура системы должна быть оценена не только в плане корректности, полноты и непротиворечивости. С большинством архитектур связан определен- ный набор атрибутов качества, которые также должны быть подвергнуты оценке. Рисунок 4.33. Трехуровневая архитектура
Глава 4. Твстирование внелитичвских и проектных модвлай Система, которая представляет анимацию так, как это делает, например, игра «Кирпичики», должна обладать соответствующими функциональными возможно- стями. Сценарии, применяемые в рамках базовой проверки в качестве тестовых случаев, также могут использоваться для оценки ожидаемой эффективности рас- сматриваемой архитектуры. В подходе SAAM [Kazman94] для оценки эффектив- ности задействуется метод анализа свободной формы. Метод SAT (Software Architecture Testing — Тестирование архитектуры программного обеспечения) [McGr96] использует перспективу тестирования, чтобы получить гарантию того, что исследованию подвергались все важные архитектурные характеристики. Тестовые случаи выполняются символически, а диаграммы последовательнос- тей сообщений можно анализировать с позиций перспективы достижения требуе- мой эффективности. Для целей анализа каждой связи, соединяющей компоненты архитектуры, назначается «цена», которая отражает тип связи, используемый кон- кретным соединением. Количество сообщений, передаваемых в каждом сценарии, позволяет получить представление об относительной эффективности, хотя сам по себе этот метод дает разве что представление о порядке соответствующей количе- ственной величины, а не ее точное значение. Более точное значение эффективно- сти можно подсчитать по следующей формуле: затраты времени на вычисления = + п2с2 + ...+ nmcm, в которой через с обозначены типы соединений, а индексы представляют собой количество соединений каждого типа. Если профили использования (см. раздел «Профили использования») применяются для выбора представительного набора тестовых случаев, то можно получить достаточно точную приближенную оценку эффективности обычного пользовательского сеанса. Кроме того, несложно постро- ить и приближенную оценку для наиболее благоприятного и наименее благопри- ятного случаев. Проектным альтернативам также можно дать оценку путем сравнения диаг- рамм последовательностей сообщений и относительных количеств сообщений. Воспользовавшись тем же набором тестовых случаев, выбранных на основе дан- ных профиля использования, можно выполнить беспристрастное и реалистичное сравнение эффективности системы, какой бы она обладала, будучи построенной на базе каждой из этих альтернатив. Данный метод пригоден также и для исследования эффективности распреде- ленных систем, если снабдить соответствующими метками сообщения, которые обеспечивают межпроцессное и межпроцессорное взаимодействия. Подход, пре- дусматривающий применение тестовых случаев и профилей использования, по- зволяет получить представительный способ измерения эффективности. Диаграммы последовательностей сообщений могут использоваться для оценки расширяемости архитектуры. Приводимый ниже профиль использования показы- вает наличие нескольких типов пользователей с различными частотами использо- вания операций в каждом из них:
Главе 4. Твстироввнив внвлитичвских и проектных моделей UserType = P1s,, p2s2, pnsn useProfile = q^t,, q2ut2, qmutm в которой p и q суть вероятности того, что будет выбран, соответственно, конк- ретный сценарий и тип пользователя. Тестовый случай, предназначенный для проверки расширяемости, представля- ет собой гипотетическую смесь действующих субъектов, которая отличается от текущего профиля использования, т.е. набор значений q в уравнении для useProfile. Обычно различные типы пользователей остаются неизменными, однако относительные числа меняются. Вычисления по указанным выше формулам при- меняется для каждого сценария и для каждого типа пользователей. Затем число пользователей каждого типа используется для дальнейшего агрегирования. Полу- ченные значения могут характеризовать интенсивность использования каждого конкретного сообщения. Подробная модель проекта класса Подробная структурная модель класса заполняет модель архитектуры классами, реализующими интерфейсы, определения которых даются этой архитектурой. Обычно такая модель включает в себя некоторое множество диаграмм классов, пред- и постусловия, сформулированные в OCL для каждого метода каждого клас- са, диаграммы видов деятельности важнейших алгоритмов и диаграммы состояний для каждого класса. Подробная модель проекта игры «Кирпичики» показана на рис. 2.18, а дополнительные детали на примере конкретного класса — на рис. 2.15. Критерии оценки модели конкретизируются на рис. 4.34. В фокусе внимания находится совместимость с архитектурой. Это служит подтверждением идеи, что архитектура является краеугольным камнем программного продукта. Она также является тем местом, в котором компоненты многократно используются и вклю- Критерий Интерпретеция применительно к модели структуры класса Полнота Клвссы определяются для квждого интерфейсе архитектуры. Предусловия для квждого метода содвржвт достеточно ин- формации, чтобы пользователь мог спокойно использоввть тот или иной метод. Постусловия методе определяют состо- яние ошибки, равно кек и обычно ожидаемый рвзультвт. Корректность Каждый клвсс в точности рввлизувт самвнтику интерфейсе. Для тах клвссов, которые соответствуют интерфейсам в архитектуре, спецификация класса должна соответствовать интерфейсу, задавввмому архитектурой. Непротиаоречиаость Поведения в интерфейсе квждого класса обеспечивают либо единственный путь решения задачи, либо если есть несколь- ко таких путей, они выбирают то жв поввданиа, но с другими предусловиями. Рисунок 4.34. Критерии, используемые при проверке модели структуры класса
Глввв 4. Твстироввнив внвлитичвских и проектных моделей чаются в систему. Спецификации компонентов должны быть включены в трасси- ровку исполнения, чтобы показать, что нет никакой потребности в средстве со- пряжения между компонентом и приложением. Назначенные роли представлены в таблице на рис. 4.35. Обратите внимание на тот факт, что архитекторы получают свою роль в тестировании диаграмм классов. Обязанность архитекторов в проекте состоит в том, чтобы «усиливать» архитекту- ру. Другими словами, архитектор следит за тем, чтобы разработчики не нарушали ограничений, накладываемых архитектурой. За счет отбора соответствующих тес- товых случаев и оценки результатов прогона этих тестов, архитекторы получают подробные сведения о реализациях, построенных проектировщиками. Подробная модель проекта класса имеет вид: Предположим, что шайба перемещается вверх и влево, но прежде чем ударить в кирпич, она ударяет в левую стену. Как изменится скорость и направление движения шайбы, когда она ударяет в стену? Ожидается, что когда шайба находится напротив левой стены, эта стена строит объект Collision, который передается шайбе. Шайба изменит скорость и начнет движение вверх и вправо. Она будет двигаться с постоянной скоростью. Тестовые случаи на этом уровне мало чем отличаются от тестовых случаев, предназначенных для проверки окончательного варианта системы. На этом уровне приходится учитывать такое большое число деталей, что тестировщики должны быть предельно внимательными, дабы отметить все элементы системы, затронутые этими тестовыми случаями. На рис. 4.36 показаны элементы диаграммы, которые должны быть согласованы во время сеанса целенаправленной проверки. По мере продвижения тестирования исполнители выбирают методы для их последующего вызова, а также проверяется модель состояний получателя с целью удостоверить- ся, что целевой объект способен получить сообщение. Эти сообщения добавляют- ся в диаграмму последовательности сообщений, а модели состояний обновляются для отражения изменений в состояниях. Если на диаграмме какое-либо состояние заштриховано, это значит, что имеется дополнительная деталь, которая должна быть учтена в таком состоянии, но в то же время эта информация не нужна для оценки текущего состояния. В диаграммах последовательностей сообщений име- ются «тупиковые» объекты, в которых тестировщики не будут предпринимать по- пыток исследовать логику, выходящую за пределы этих объектов. Роль а проаерке Специалист в проблемной облвсти Тестировщик Разработчик Роль а проекте Спвцивлист в проблемной облвсти, инжвнвр-систвмотвхник Рвзрвботчик приложения; врхитвктор Рвзрвботчик приложения Рисунок 4.35. Роли при проверке модели структуры класса
Глава 4. Тастированив аналитических и проектных моделей Рисунок 4.3В. Условия испытаний Это действие является последним шагом перед реализацией и тестированием с использованием компьютерных программ. Разработчики, выполняющие друже- ственное тестирование создаваемых ими программных кодов, выигрывают от того, что возвращаются к использованию тестовых случаев, созданных на данном уров- не тестирования, и их преобразованию в тестовые программы уровня классов. Повторное тестирование Мы полагаем, что вы, как и мы, используете итеративный (циклический) про- цесс разработки. Это означает, что соответствующие тесты должны повторяться. Мы попытались усовершенствовать этот процесс, написав формальные тестовые случаи, и тем самым отойти от практики выдумывания сценариев во время про- цедуры проверки. На второй и последующих итерациях мы обычно отдаем предпочтение повтор- ному применению тестовых случаев, которые не удалось выполнить на предыду- щем цикле проверки, а также некоторых из случаев, через которые модель прошла
Главв 4. Тестироввнив внвлитичвских и проектных модвлвй успешно. Тестовые случаи добавляются в тестовый набор по мере добавления в проект все новых и новых свойств. Если после проведения проверки были обна- ружены новые проблемы, необходимо также добавить тесты, контролирующие и эти проблемы. СОВЕТ Для пврвдвчи информации о тестируемой модели воспользуйтесь возможностями целенвпрввлвнной проверки. В проекте, который мы недавно разребетывали, один из разрвботчиков, отвечвющий за рвзработку части проекте, вынужден был оста- вить рвботу. В результате мы провели несколько свенсов проверки, чтобы звстевить других резрвботчиков ускорить реботу над их собственными честями проекте. Сведения о проекте в изложении разработчика, в отличие от проверки, освещают проект в основном так, как он вго себе представляет, а не так, как аго видят другие разрвботчики. Модели для тестирования дополнительных свойств Все чаще перед проектами ставятся все более смелые цели, такие как разработ- ка наращиваемого проекта, проектирование многократно используемых структур или систем с высокой степенью мобильности. Результаты, получаемые на стадиях анализа и проектирования, играют решающую роль в достижении упомянутых целей. В частности, ключом к успеху является архитектура. Целенаправленная проверка поможет получить ответы на вопросы о системе на метауровне. В этом режиме сценарии тестов представляют собой действия разработчиков в системе, но не действия пользователей. Вместо того чтобы спрашивать, как будут взаимо- действовать объекты в системе, следует задать вопрос: «Как должны быть измене- ны классы системы, чтобы получить поведение, потребность в котором возникла за последнее время?». Изменения, вносимые в проект или в ревизии, необходимые для построения той или структуры на базе существующего приложения, могут быть оформлены в виде случая изменения [EcDe96], Случай изменения (change case) — это случай использования, который не является требованием системы, но представляет собой ожидаемые изменения в системе. Целенаправленная проверка применяет крите- рии корректности, полноты и непротиворечивости к текущим моделям анализа и проектным моделям, рассматривая при этом случаи изменения как источник тес- товых сценариев. Например, если в задачу проекта входит построение структуры, которая может служить основой для дальнейших разработок, проверки на соответствие текущим случаям использования системы недостаточно. Рассмотрим случай изменения, представленный на рис. 4.37. Тестовые случаи дают возможность получить пред- ставление о том, что нужно сделать, чтобы расширить эту структуру, благодаря тестированию, которое позволяет определить, насколько полной является суще- ствующая модель по отношению к новым требованиям. Второй случай измене- ний, показанный на рис. 4.38, может быть применен для тестирования архитекту- ры. Он может быть задействован для определения, насколь плотно существующая архитектура покрывает новые требования.
Глааа 4. Тестированиа аналитичаских и проактных моделей Случай изменения №1 Действующий субъект: Игрок Сценерий использования: Шайба, направленная игроком, отскакивает рикошетом от нескольких кирпичей. Некоторые из кирпичей ломаются и исчезают с экрана, при этом трещины возникают только в двух кирпичах. Альтернативные пути: Исключительные пути: Честоте использовения: Низкая. Критичность: Низкая. Риск: Средний. Рисунок 4.37. Пример случая изменения для игры «Кирпичики» Случай изменения №2 Действующий субъект: Игрок Сценарий использования: Игрок напрааляет шайбу в набор неподвижных, не ло- мающихся препятствий. Игрок должен ускорить перемещение лопатки, чтобы зах- ватить шайбу после того, как она отразится от ближайшего препятствия. Альтернативные пути: Исключительные пути: Честоте использования: Низкая. Критичность: Низкая. Риск: Средний. Рисунок 4.38. Пример случая изменения для игры «Кирпичики» Метод тестирования этих объектов может быть представлен в виде последова- тельности шагов. Каждый шаг подробно описывается и сопровождается соответ- ствующим примером. Дать четкое определение цели, которую преследует рассматриваемый случай изменения. Проект должен легко расширяться и приспосабливаться к новым играм. Построить «случай изменения», содержащий специальный сценарий, кото- рый служит иллюстрацией цели. Структура предназначается для реализации игр типа пинбол, конфигурация которых выбирается пользователем. Допустимыми препятствиями при этом могут быть стойки, выпуклые участки и бамперы. Построить тестовые случаи посредством выбора из диапазона, допускаемого случаем изменения.
Глава 4. Тестирование аналитических и проектных моделей Должна быть построена игра бильярдного типа (пинбол). Рисунок 4.39 слу- жит иллюстрацией двух новых состояний, которые могут быть добавлены в конечный автомат игры «Кирпичики». Пронумеровать все виды работ, которые потребуется выполнить для дости- жения цели, путем описания различий в состоянии и поведении, необходи- мых для новой цели. Это можно сделать за счет добавления новых подклас- сов, которым следует дать определение. Класс StationarySprite переводится в категорию подклассов с тем, чтобы мож- но было построить новые препятствия. Класс Ball расширяет методы класса MovableSprite, а методы класса CollideWithBall требуются для всех спрайтов. Вводятся дополнительные атрибуты, чтобы зафиксировать конкретные значе- ния координат каждого препятствия. В подкласс Pinball класса ArcadeGame добавляет атрибут Score. Дать оценку текущему состоянию разработки относительно проекта, кото- рый ведет к достижению цели. Ответить на следующие вопросы: «Отсут- ствуют ли какие-либо фундаментальные понятия, которые необходимо до- бавить?» или «Существуют какие-либо противоречия между тем, что имеется, и тем, что будет добавлено?» Необходимые базовые классы и методы имеются. Необходимые атрибуты могут быть бесконфликтно добавлены к существующим атрибутами. Тем не менее, в класс Sprite должны быть внесены изменения. Рисунок 4.39. Диаграмма состояний игры пинбол
Глава 4. Тестироааниа аналитичаских и проактных моделей Применять эти действия к каждому дополнительному тестовому сценарию до тех пор, пока не будут проверены все предложенные изменения. Результатом этого процесса является совокупность потенциальных изменений, необходимых для достижения желаемого свойства системы, такого как, например, расширяемость. Проверка ведет поиск отсутствующих понятий и противоречий между тем, что существует в модели на текущий момент, и тем, что требуется доба- вить в модель в плане достижения новой поставленной цели. Этот метод создает предпосылки для установки обратной связи с коллективом разработчиков на ранней стадии разработок с целью устранения фундаментальных слабостей в проекте. Резюме Методы, представленные в данной главе, временами заставляют людей выкла- дываться полностью. Сценарии необходимо систематически выбирать, тем самым обеспечивая максимальную эффективность тестирования. В частности, рекомен- дуется выбирать случаи использования, которые менее понятны, или представля- ют ситуации с высоким уровнем риска. Благодаря этому повышается вероятность обнаружения ошибок и пропусков, которые могут оказать максимальное влияние на качество системы. Рисунок 4.40 служит иллюстрацией многого из того, что вы, как мы надеемся, узнали из этой главы. UML-диаграммы разрабатывались с таким расчетом, чтобы они поддерживали друг друга. Тестовые случаи, разработанные в этой главе, слу- жат руководством по ведению систематического поиска потенциальных дефектов модели. Зачем рассматривать этот метод в книге, посвященной проблеме тестирования? Во-первых, он использует перспективу тестирования для более тщательного ис- следования моделей. Во-вторых, большая часть такого тестирования проводится в масштабах всей системы, и это обстоятельство естественным образом определяет роль системных тестировщиков. Они могут принимать участие в процессе разра- ботки, начиная с самых ранних его стадий, если в их обязанности входит разра- ботка тестовых случаев на базе случаев использования. В данной главе было дано описание подхода, который выявляет дефекты на ранних стадиях процесса разра- ботки и благоприятствует участию специалистов по тестированию, специалистов по интеграции классов и системных тестировщиков на самых ранних стадиях раз- работки проекта. Сводка задач, рассмотренных в данной главе, приводится в виде контрольного списка на рис. 4.41. Контрольный список действий, выполняемых в процессе тестирования моделей Действия, указанные в этом контрольном списке, выполняются с целью убе- диться в том, что все виды деятельности, которые должны быть выполнены по условиям целенаправленной проверки, завершены. Подробное описание этого процесса дано в приложении к данной главе.
Главв 4. Твстированив вналитичвских и проактных модалвй Сообщение последовательности SD должно соответствовать отношению, указанному в модели объекта Существуют ли сценарии, которые покрывают все возможные элементы? Каждый переход из одного состояния в другое соответствуют отношению Рисунок 4.40. Непротиворечивость диаграмм (SD = диаграмма последовательности сообщений) Контрольный список действий, выполняемых в процессе целенаправленной проверки Принять рвшвнив относительно того, квк производить оценку полноты, непротиворе- чивости и коррактности конкратных случаев использования в тестируемой модели (MUT - modal under tast). Определить, какив сцвнврии сладувт выбрать из модели случая использования для последующего применения в качества тестовых случввв. Построить твстовыв случаи путем добавления сцвнвривв с конкретными данными. Выбрать модаль/нотвцию для регистрации результатов каждого прогонв. Проввсти тастированив. Двть оценку разультвтвм выполнения тастов с твм, чтобы определить, квкив тасты модель прошла успешно, в на каких тастах она потарпапа наудачу. Эврвгистрировать эти рвзультвты для последующего использования в управлении процессами исправления и тестирования на следующей итврвции. Рисунок 4.41. Контрольный список этапов процесса целенаправленной проверки
Глава 4. Тестироеание аналитических и проектных моделей Упражнения 4-1 Принимая за отправную точку случаи использования своего проекта, добавьте в них поля Частота использования и Критичность. Выполните анализ рисков для этих случаев использования и заполните упомянутые поля для каждого случая исполь- зования. Упорядочите эти случаи использования таким образом, чтобы они распо- лагались по убыванию необходимой интенсивности тестирования. Напишите один тестовый случай для наименее важного случая использования. Составьте дополни- тельные тестовые случаи для каждого из случаев использования. 4-2 Проведите сеанс целенаправленной проверки исходной модели анализа своего про- екта. Составьте отчет, в котором указаны все расхождения между моделями. 4-3 Разработайте три сценария по случаям использования, рассмотренным в данной главе. Затем, воспользовавшись моделями, описанными в главе 2 и главе 4, отыщите примеры неполноты, противоречивости и нарушения корректности. 4-4 Выберите такой этап процесса разработки своего программного продукта, на ко- тором, по вашему мнению, имеет место больше ошибок, нежели на любом другом, Составьте контрольный список для целенаправленной проверки, которая позволит разработчикам обнаружить все типы ошибок, допущенных на этом этапе. Приложение: Определение процесса для целенаправленной проверки Цель: Выявление ошибок в артефактах, созданных на стадиях анализа и разра- ботки процесса построения программного обеспечения. Этапы процесса 1. Определить масштаб и глубину целенаправленной проверки. 2. Идентифицировать базовую модель (модели), на основе который был со- здан материал, подвергающийся проверке. 3. Подобрать коллектив для проведения целенаправленной проверки. 4. Определить план выборки и критерии покрытия. 5. Построить тестовые случаи из баз. 6. Применить тестовые случаи к тестируемому материалу. 7. Собрать и выполнить анализ результатов прогона тестовых случаев. 8. Подготовить отчетные документы и установить обратную связь. Подробное описание шагов 1. Определить масштаб и глубину целенаправленной проверки. Входные данные: Место, занимаемое проектом в жизненном цикле. Материалы, которые являются результатом выполнения проекта (UML-мо- дели, планы, случаи использования).
Главв 4. Твстироввние внвлитичвских и проектных моделей Выходные данные: Конкретный набор диаграмм и документов, который будет служить основой для оценки. Метод: Определите в качестве области целенаправленной проверки набор комплек- тующих элементов, полученных на этапе разработки. Воспользуйтесь ин- формацией, касающейся процесса разработки, для выявления комплектую- щих элементов, полученных на этапе выявления интересов. Пример: Проект только что прошел фазу анализа предметной области. Процесс раз- работки определяет комплектующие элементы на этой фазе как UML-mo- дель, содержащую случаи использования на уровне предметной области, статическую информацию, такую как, например, диаграммы классов, и ди- намическую информацию, подобную диаграммам последовательностей со- общений и диаграммам состояний. Целенаправленная проверка обеспечит оценку этой модели. 2. Идентифицировать базовую модель (модели), на основе который был создан материал, подвергающийся проверке. Входные данные: Области применения целенаправленной проверки. Место, занимаемое проектом в жизненном цикле. Выходные данные: Материал, на базе которого будут построены тестовые случаи (тестируемая модель — MUT). Метод: Проанализируйте описание процесса разработки с целью определить вход- ные данные для текущей фазы. В качестве входных данных для текущей фазы следует рассматривать базовую модель (модели). Пример: Входными данными фазы анализа предметной области являются «знания экспертов, знакомых с предметной областью». Такие мысленные модели и будут базовыми моделями для целенаправленной проверки. 3. Подобрать коллектив для проведения целенаправленной проверки. Входные данные: Область применения целенаправленной проверки. Имеющийся персонал. Выходные данные: Группа участников и их роли.
Глава 4. Тестированиа аналитических и проектных моделей Метод: Назначьте персонал для заполнения одной из трех категорий ролей: Адми- нистратор, Участник построения тестируемой модели и Объективный на- блюдатель процесса тестирования модели. Выбирайте объективных наблю- дателей из числа заказчиков модели, которая в настоящий момент тестируется, а участников - во время создания базовой модели. Пример: Поскольку тестируемыми моделями являются модель анализа предметной области и мысленная модель для специалистов по предметной области, объективные наблюдатели могут быть выбраны из числа других специалис- тов в предметной области и/или из числа аналитиков приложений. Адми- нистративный персонал, по-видимому, можно пригласить из числа других заинтересованных лиц или организаций, которые оказывают поддержку проведению целенаправленных проверок. 4. Определить план выборки и критерии покрытия. Входные данные: План обеспечения качества проекта. Выходные данные: План работ по отбору тестовых случаев. Описание того, какие части тестируемой модели будут покрыты тестовыми случаями. Метод: Выделите наиболее важные элементы тестируемой модели. Дайте оценку трудозатрат, необходимых для их включения в область целенаправленной проверки. Если таких элементов окажется слишком много, воспользуйтесь информацией из раздела рисков или мнением экспертов, чтобы расставить эти элементы по приоритетам. Пример: В состав модели предметной области входят статические и динамические модели, равно как и случаи использования. Должно существовать достаточ- ное количество тестовых случаев, чтобы провести каждый «крупный» эле- мент предметной области через все его видимые состояния. 5. Построить тестовые случаи из баз. Входные данные: План по отбору тестовых случаев. Тестируемая модель. Выходные данные: Набор тестовых случаев.
Глава 4. Твстироввнив вналитичвских и проектных моделей Метод: Возьмите сценарий из базовой модели. Определите предусловия и входные данные, которые необходимы для перевода системы в соответствующее со- стояние, и приступите к тестированию. Представьте сценарий «авторитету», чтобы тот определил ожидаемые результаты прогона тестового сценария. Завершите описания каждого тестового случая. Примеры: Предложите разным специалистам в предметной области, а не только тому, который поддерживал построение данной модели, представить сценарии, которые соответствуют случаям использования системы. Эксперты предла- гают также свои версии того, что они считают приемлемым ответом. 6. Применить тестовые случаи к тестируемому материалу. Входные данные: Набор тестовых случаев. Тестируемая модель. Выходные данные: Совокупность результатов тестирования. Метод: Примените тестовые случаи для тестирования модели, используя для этой цели максимально специализированные методы. Для UML-моделей в ста- тической среде, например, Rational Rose, наилучшим подходом будет сеанс интерактивного моделирования, в котором Создатели играют роль элемен- тов модели. Если тестируемая модель представлена исполняемым прототи- пом, тестовые случаи отображаются на эту систему, после чего выполняют- ся. Пример: Модель предметной области представляет собой статическую UML-модель. Сеанс моделирования проводится таким образом, что Наблюдатели подают тестовые случаи Создателям. Создатели дают описания того, какой обработ- ке должен быть подвергнут тестовый сценарий при прохождении через мо- дель. Диаграммы последовательностей сообщений документируют выполне- ние каждого тестового случая. Воспользуйтесь специальными символы или цветами для маркировки каждого элемента модели, затронутого конкретным тестовым случаем. 7. Собрать и выполнить анализ результатов прогона тестовых случаев. Входные данные: Результаты тестирования в виде диаграмм последовательностей сообщений и заключений относительно того, прошла или не прошла модель тот или иной тест.
Глава 4. Твстированиа аналитичаских и проектных моделей Маркированная модель. Выходные данные: Статистические данные в виде процентных отношений удачных и неудач- ных исходов тестирования. Разбиение результатов тестирования по категориям. Каталоги ошибок и отчеты с описанием ошибок. Оценки качества тестируемой модели и тестов. Метод: Начните с подсчета тестовых случаев, которые прошли испытания, и тех, которые испытаний не прошли. Сравните полученное процентное отноше- ние с аналогичными показателями других целенаправленных проверок, ко- торые проводились в этой организации. Вычислите это процентное отноше- ние для каждого типа элементов, которые были задействованы во время прогона тестовых случаев. Воспользуйтесь маркированной моделью в каче- стве источника упомянутых данных. Обновите информацию в описи оши- бок, дополнив ее информацией об ошибках, зафиксированных на протяже- нии данного сеанса тестирования. Разбейте на категории тестовые случаи, для которых были обнаружены ошибки. Эта задача может решаться вместе с двумя предыдущими задачами путем соответствующего маркирования бу- мажных копий модели. На диаграмме последовательности сообщений про- следите продвижение каждого тестового случая, при прогоне которого обна- ружены ошибки, а также пометьте каждое сообщение, класс и атрибут, затронутые неудачным тестовым случаем. Пример: Для модели анализа предметной области необходимо добиться того, чтобы каждый случай использования был источником, по меньшей мере, одного тестового случая, и каждый класс на диаграмме классов использован, по меньшей мере, один раз. Обычно на первом проходе некоторые важные со- стояния отсутствуют. Это обстоятельство следует иметь в виду при анализе покрытия. 8. Подготовить отчетные документы и установить обратную связь. Входные данные: Результаты тестирования. Информация о покрытии. Выходные данные: Информация о том, для проверки каких элементов будут строиться новые тесты. Протокол испытаний.
Главе 4. Тестирование аналитических и проектных моделей Метод: Придерживайтесь стандартного формата протокола испытаний, принятого вашей организацией для документирования результатов испытаний. Если поставленные цели для покрытия тестовыми случаями достигнуты, то про- цесс тестирования можно считать завершенным. Если нет, воспользуйтесь этим протоколом для возврата к шагу 5 и проследуйте вниз по соответству- ющим шагам с целью повышения плотности покрытия. Пример: Было выявлено отсутствие некоторых элементов модели перед выполнени- ем тестов с целью анализа предметной области. Тесты, окончившиеся не- удачей, можно повторить после соответствующей корректировки модели. Роли в процессе тестирования Администратор В обязанности администратора входит проведение сеансов целенаправлен- ной проверки, сбор и распространение результатов проверки, а также агре- гирование различных показателей с целью измерения качества ревизии. В нашем примере административная работа может быть проделана персона- лом центрального управления вашей организации. Создатель Группа специалистов, построивших тестируемую модель. В зависимости от формы, которую принимает модель, создатели могут «прогонять» символи- ческую модель на тестовых случаях либо могут оказать помощь при преоб- разовании тестовых случаев в некоторую форму, в которой случаи смогут выполняться независимо от выбранного представления модели. В нашем примере создателями считаются специалисты по моделированию, построив- шие модель предметной области. Наблюдатель Лица, выступающие в этой роли, создают тестовые случаи, которые исполь- зуются в рамках целенаправленной проверки. В нашем примере это специ- алисты в предметной области, предпочтительно, из числа тех, кто не был источником информации, которая применялась для построения начальной версии модели.
Глава Основы тестирования классов к Вы хотите знать, на что обращать главное внимание во время тестирования классов? См. раздел "Тестирование классов" ► Вы хотита знать, как отбирать тестовые случаи для тестирования классов? См. раздел "Построение тестовых случаев" ► Вы хотите ознакомиться с хорошим способом реализации тестового драйвера для тестирования классов? См. раздел "Построение тестового драйвера" В данной главе рассматривается методика тестирования отдельного класса. Методы, которые описываются в данной главе, будут применяться в последующих главах во время обсуждения тестирования взаимодействия объектов и тестирова- ния классов в иерархии наследования. Для целей этого обсуждения предположим, что программный код для класса написан и теперь требуется выполнить его тести- рованиз. Основное внимание будет уделяться классам, экземпляры которых слабо взаимодействуют с другими экземплярами. В качестве иллюстрации рассмотрим классы Velocity и PuckSupply игры "Кирпичики". Тестирование более сложных классов обсуждается в двух следующих главах. Тестирование классов Основным элементом объектно-ориентированной программы является класс. Тестирование классов охватывает виды деятельности, ассоциированные с провер- кой реализации класса на точное соответствие спецификации этого класса. Если реализация корректна, то каждый экземпляр этого класса ведет себя подобающим образом. Тестирование классов в первом приближении аналогично тестированию моду- лей в традиционных процессах тестирования — и для того, и для другого харак- терны одни и те же задачи, требующие решения (см. замечание ниже). Тестирова- ние классов должно решать некоторые вопросы комплексных испытаний, 188
Главв 5. Основы тестирования классов поскольку каждый объект определяет уровень области действия, в которой проис- ходит взаимодействие множества методов на множестве атрибутов экземпляров. Некоторые из наиболее важных проблем будут обсуждаться в контексте сопут- ствующих проблем в главе 8. Основное внимание в этой главе уделяется тестиро- ванию классов в режиме прогона тестовых случаев. Основная цель заключается в том, чтобы описать базовые элементы и стратегии тестирования классов, при этом мы сосредоточимся на тестировании относительно простых классов. Тестировани- ем более сложных классов мы займемся в двух следующих главах. Мы полагаем, что тестируемый класс обладает полной и корректной специфи- кацией, и что он успешно прошел тестирование в контексте моделей.' Мы также полагаем, что спецификация сформулирована на языке спецификаций, таком как OCL (Object Constraint Language — язык объектных ограничений) [WK99], либо на естественном языке и/или в виде диаграммы состояний. Если спецификация представлена в нескольких формах, то мы полагаем, что эти формы согласуются между собой, и требуемую информацию можно брать из той формы, которая ока- жется наиболее полезной в качестве основы для разработки тестовых случаев для конкретного класса. Для построения тестовых случаев мы отдаем предпочтение максимально формализованным спецификациям. Способы тестирования классов Эффективное тестирование программных кодов конкретного класса достигает- ся при помощи ревизий или выполнения тестовых случаев. В ряде ситуаций ре- визия может оказаться вполне жизнеспособной альтернативой тестированию в режиме исполнения тестовых случаев, однако по сравнению с этим способом тес- тирования для ревизии характерны два следующих недостатка: При проведении ревизии возможны ошибки, обусловленные человеческим фактором. Ревизии требуют значительно больших затрат усилий, нежели регрессион- ное тестирование, при этом нередко для ее проведения требуются почти такие же затраты ресурсов, что и на полномасштабное тестирование. В то время как тестирование в режиме прогона тестовых случаев может пре- одолеть указанные выше недостатки, существенные усилия могут потребоваться для отбора подходящих тестовых случаев и для разработки тестовых драйверов. В некоторых ситуациях трудозатраты, необходимые для построения тестового драй- вера для конкретного класса, на несколько порядков превышают трудозатраты на разработку самого класса. В этом случае потребуется дать оценку затрат и выгоды, полученной от тестирования этого класса "за пределами" системы, в которой он будет применяться. Непротиворечивость — это прежде всего понятие, связанное с проектированием. К моменту начала тестирования классов проектирование классов должно быть завершено, во всяком случае, если речь идет о текущей итерации процесса разработки.
Глааа 5. Основы тастироаания классов Такая ситуация характерна не только для объектно-ориентированного програм- мирования. Подобная ситуация возникает в рамках традиционной процедурно- ориентированной разработки по отношению ко многим подпрограммам, вызы- ваемым программами, находящимися на более высоком уровне структурной диаграммы. После идентификации тестовых случаев для класса мы должны реализовать тестовый драйвер, обеспечивающий прогон каждого тестового случая, и запрото- колировать результаты каждого такого прогона. Тестовый драйвер создает один или большее число экземпляров класса, осуществляющего прогон тестовых случа- ев. Важно не упускать из виду, что тестирование классов производится путем построения экземпляров этих классов и тестирования поведения построенных эк- земпляров. Тестовый драйвер может принимать множество форм, описание кото- рых приводится ниже в данной главе. Мы отдаем предпочтение форме автоном- ного "тестирующего" класса перед всеми другими, поскольку он предлагает удобную организацию управления драйверами и наследованием и с успехом мо- жет использоваться для улавливания общих для них свойств. Как будет показано в главе 7, еще большая польза от такого выбора получается при тестировании иерархий классов. Оцениваемые факторы тестирования классов Прежде чем приступать к тестированию того или иного класса, потребуется определить, тестировать ли его в автономном режиме как модуль или каким-то другим способом, как более крупный компонент системы. Мы принимаем реше- ние на основе следующих факторов: Роль этого класса в системе, в частности, степень связанного с ним риска. Сложность класса, измеряемая количеством состояний, операций и связей с другими классами. Объем трудозатрат, связанных с разработкой тестового драйвера для тести- рования этого класса. Если какой-либо класс должен стать частью некоторой библиотеки классов, целесообразно выполнять всестороннее тестирование классов, даже если затраты на разработку тестового драйвера окажутся высокими, поскольку очень важным является его корректное функционирование. В контексте игры "Кирпичики" мы связываем высокий риск с большей частью базовых классов, таких как, например, Velocity и PuckSupply. Если они реализованы неправильно, то программа этой игры работать не будет. Написание программ тестирования этих классов не влечет за собой принципиальных трудностей, поскольку по замыслу системы, они не должны взаимодействовать с другими классами игры "Кирпичики". Мы связываем высокие риски с другими классами, такими как Puck (шайба), но в то же время признаем, что задача написания тестовых драйверов для них может оказаться до- статочно сложной.
Глава 5. Основы тестирования классов ТРАДИЦИОННОЕ ТЕСТИРОВАНИЕ ПРОГРАММНЫХ МОДУЛЕЙ Цель тестирования программных модулей состоит в том, чтобы удостовариться, что каждый модуль соответствуат своей спацификации. Если каждый модуль соответству- ет своей спвцификвции, то причиной любых ошибок, которые возниквют при их объединении в единое целое, скорее всаго, являются нвпрввильнвя стыковка модулай, е не собственно реализация этих модулей. Основные усилия по отладке можно теперь сосредоточить на интерфейсах, а не на самих модулях. Тестироввнив модулей осуществляется по мере продвижения их резработки. В про- цедурно-ориентированном программировании модулем является процедура (или фун- кция], а иногда это группе процедур, которые реализуют абстрактный тип данных. Тестироввнив модулей обычно представляют собой некоторое сочетание проверок программных кодов и прогонов тестовых случаев, при этом основное внимение уделяется последнему. Можно составить простой план тестирования конкретного модуля, в котором указаны необходимые тестовые случаи, посла чего приступить непосредственно к построению тестового драйвера. Все это хорошо выглядит в теории, однако на практика может возникнуть множество камней преткновения. Обычно только отладка простых модулей — тах, которые на структурной схема изображены в вида заключительных узлов — протекает достаточно легко, баз существенных затрат. Выбор тестовых случаев для таких модулей на представляет каких-либо трудностей, в тестовые драйверы достаточно легко пост- роить, асли только параметры не обладают сложной структурой. Даже та модули, которые имеют параметры достаточно сложной структуры, допускают модульное тастирование баз больших усилий, если эти драйверы могут инициализи- ровать фактические параметры через сравнительно небольшое количество операций присваивания и считывания. Обратите, однвко, внимание на то обстоятельство, что это приводит к увеличению связей между тестируемым модулем и его тестовым драйвером, в результате чего можат повыситься стоимость сопровождения, если структура параметров будет со временам меняться. В то время как модули нижних уровней структурной схемы допускают прямое те- стирование модулей, в некоторых точках (это могут быть два или три уровня, начиная с нижнего) взаимодействие модулей становится настолько запутанным, что модульное тестирование теряет всякий смысл. Трудозатраты, необходимые для создания тес- тового драйвера, могут намного превзойти стоимость тестирования больших комп- лектующих модулей. В ряда случаев программный код тестового драйвера по объему можат намного превосходить программный код тестируемого модуля. При этом возникает проблема тестирования модулей самого тестового драйвера. По замыслу они вступают в связи со множеством других классов, главным об- разом в силу того, что поведение класса Puck является графическим. Puck уста- навливает связь с классом PlayField и с любым видом спрайтов на игровом поле. Мы полагаем, что для написания тестового драйвера для класса Puck придется затратить существенные усилия, поскольку все тестовые случаи потребуют экзем- пляр класса PlayField, а некоторые из случаев, необходимые для тестирования об- работки соударений, потребуют экземпляры классов Brick, BrickPile и Paddle. По- этому тестирование класса Puck основано на предположении, что все другие классы функционируют корректно. (Исследования будут продолжены в главе 6.) Мы можем принять решение выполнять тестирование класса Puck с использова- нием некоторых и даже всех его экземпляров в контексте кластерного тестирова-
Глава 5. Основы твстироввния клвссов ния, поскольку при помощи экземпляров других классов потребуется построить такое окружение вокруг каждой шайбы, которое способствовало бы тестированию конкретного класса. Рассмотрим теперь пять оцениваемых факторов тестирования в контексте тес- тирования классов. Кто выполняет тестирование Тестирование классов обычно проводят разработчики, подобно тому как тести- рование подпрограмм традиционно выполняется их разработчиками в режиме те- стирования модулей. Если разработчик класса выступает в роли тестировщика своего класса, то в силу этого обстоятельства количество программистов, хорошо изучивших спецификацию этого класса, сводится к минимуму. При этом упро- щается тестирование реализации, поскольку такой тестировщик знаком со всеми подробностями программного кода. И, наконец, разработчик может воспользо- ваться соответствующим тестовым драйвером для отладки программного кода после его написания.2 Основной недостаток того, что тестовые драйверы и программные коды разра- батываются одним и тем же персоналом, заключается в том, что неправильное понимание спецификаций разработчиками распространяется на тестовые наборы и на тестовые драйверы. Потенциальных проблем подобного рода можно избежать за счет формальной ревизии программных кодов и/или если потребовать, чтобы план тестирования был написан другим разработчиком классов, либо чтобы реви- зия программных кодов выполнялась независимыми тестировщиками. Независимые тестировщики довольно часто обнаруживают ошибки в специфи- кациях классов, так что на их устранение в процессе тестирования должно выде- ляться определенное время. Что тестировать Прежде всего, мы хотим удостовериться, что программный код класса в точно- сти отвечает требованиям, сформулированным в его спецификации. Уровень внимания, которое уделяется тестированию того или иного класса с целью выяс- нения, что он не делает ничего больше того, для чего он предназначен, зависит от риска внесения этим классом дополнительных поведенческих аспектов. Неполное покрытие программного кода после того, как для этого класса был выполнен про- гон широкого набора тестовых случаев, может послужить свидетельством того, что этот класс содержит лишние, незадокументированные поведенческие характерис- тики. Либо же этот факт заставляет просто предположить, что реализация классов должна быть проверена на большем числе тестовых случаев. 2 Целью тестирования является обнаружение ошибок, но не их исправление. Тем не менее, полезной функцией тестирования класса окажется его помощь в локализации ошибок программного кода.
Глава 5. Основы твстироаания классов Когда тестировать План тестирования — или, по меньшей мере, некоторая форма идентифика- ции тестовых случаев — должен разрабатываться непосредственно после составле- ния полной спецификации класса, а сам класс должен быть готов для кодирова- ния. Это утверждение тем более справедливо, если разработчик класса отвечает также и за тестирование, поскольку ранний отбор тестовых случаев помогает раз- работчику понять спецификацию и, как уже отмечалось ранее, получить обратную связь от независимой ревизии. Разработчик классов, который обнаруживает не- корректные или неполные тестовые случаи, может построить такую реализацию, которая успешно пройдет все тестовые случаи, но при этом станет источником сложных проблем во время интеграции этого класса в более крупную часть систе- мы. Тестирование класса может проводиться на разных этапах его разработки. В условиях инкрементального, итеративного процесса разработки, спецификация и/или реализация класса может подвергаться изменениям на протяжении всей разработки конкретного проекта. Тестирование класса должно проводиться до того, как возникнет необходимость использования этого класса в других компо- нентах программного обеспечения. Регрессионное тестирование класса должно выполняться всякий раз, когда меняется реализация класса. Если эти изменения вызваны обнаружением ошибки в программном коде класса, необходимо провести ревизию плана тестирования и добавить либо изменить тестовые случаи с тем, чтобы они позволяли выявлять эти ошибки в будущих сеансах тестирования. Каким образом тестировать Тестирование классов обычно выполняется путем разработки тестового драйве- ра, который создает экземпляры классов и окружает эти экземпляры соответству- ющей средой, чтобы стал возможен прогон соответствующего тестового случая. Драйвер посылает одно или большее количество сообщений экземпляру класса в соответствии со спецификацией тестового случая, затем проверяет исход этих со- общений на базе значений ответа, изменения экземпляра и/или один или боль- шее число параметров сообщения. В обязанности тестового драйвера обычно вхо- дит удаление любого созданного им экземпляра, если в языке программирования (например, C++) имеет место управляемое программистом распределение памяти. Если для конкретного класса характерны статические элементы данных и/или операции, то их также необходимо тестировать. Такие элементы данных и методы принадлежат самому классу, но не каждому экземпляру этого класса. Класс можно рассматривать как объект — например, в Java это экземпляр класса Class — и те- стировать в соответствии с материалом, изложенным в данной главе. Если поведение экземпляров класса базируется на значениях атрибутов уровня класса, то тестовые случаи, предназначенные для тестирования этих атрибутов уровня класса, должны рассматриваться как расширение состояний этих экземп- ляров.
Главе 5. Основы тестирования классов В каких объемах тестировать Адекватность может быть измерена полнотой охвата тестами спецификации или реализации. Что касается тестирования классов, мы будем рассматривать воз- можность использования обоих способов. Мы хотим тестировать операции и пе- реходы состояний в различных сочетаниях. Напомним, что объекты находятся в одном из возможных состояний, и обычно такие состояния объекта определяют значимость операций. Тем не менее, потребуется определить, целесообразно ли проводить исчерпывающее тестирование или же оно просто необходимо. Если в нем нет необходимости, то эффективными могут оказаться попарные выборочные комбинации, особенно если они выполняются в сочетании с анализом рисков; при этом используются наиболее важные тестовые случаи, а менее важные тесто- вые случаи будут применяться выборочно. Построение тестовых случаев Рассмотрим, как производится выявление и построение тестовых случаев, предназначенных для тестирования классов. Во-первых, мы выясним, как произ- водится идентификация тестовых случаев на базе спецификации класса, сформу- лированной на языке OCL. Затем мы посмотрим на процесс построения тестового случая, взяв за основу диаграмму переходов. Отбор необходимых тестовых случаев обычно производится на основании спе- цификации класса, причем сами спецификации могут быть сформулированы раз- личными способами. Для этой цели применяется язык OCL, естественный язык и/или диаграммы переходов. Тестовые случаи могут быть определены на базе реа- лизации класса, однако использование только этого подхода может привести к распространению ошибок, которые разработчик класса допустил при интерпрета- ции спецификации класса в процессе реализации программного обеспечения. Мы предпочитаем разрабатывать тестовые случаи, сначала беря за их основу специфи- кацию, а затем добавляя к ним тестовые случаи, необходимые для тестирования границ, привнесенных реализацией. Если в тестируемом классе спецификация отсутствует, до начала тестирования потребуется прибегнуть к его "возвратному проектированию" и передать его на ревизию разработчикам. По большей части примеры, приводимые в этой главе, основаны на тестирова- нии классов Velocity и PuckSupply игры "Кирпичики". Запас шайб (puck supply) - это совокупность шайб, которые еще не использовались в игре. Velocity (векторная скорость) отображает движение подвижного спрайта на игровом поле; ее составля- ющими являются speed (скорость) (выражается в единицах расстояния игрового поля за единицу времени) и направление (угол, выраженный в градусах, при этом О означает движение на восток, или вправо, 90 означает движение на север, или вверх, и т.д.) (см. рис. 5.1). Атрибут speed разделяется на две составляющих: speed* — скорость в направлении оси х (слева направо) и speed. — скорость в на- правлении оси у (сверху вниз). В то время как атрибут speed принимает только неотрицательные значения, составляющие скорости могут быть отрицательными.
Глава 5. Основы тестирования классов Рисунок 5.1. Векторная скорость представляет собой вектор, составляющими которого являются скалярная скорость и направление 0 Значение speed* принимает отрицательное значение, если вектор скорости на- правлен влево. Значение speedy принимает отрицательное значение, если вектор скорости направлен вниз. Speed (скорость) и Direction (направление) суть абстрак- тные типы, которые в конечном счете определяются как целочисленные значения. Модель класса Velocity (векторная скорость) показана на рис. 5.2. Его специ- фикация, сформулированная на языке OCL, приведена на рис. 5.3. Инварианты этого класса служат ограничениями значений направления и скорости, а также отношений значений этих атрибутов, включая составляющие speed* и speedy. По- скольку эти атрибуты принимают целочисленные значения, инварианты несколь- ко ослабляют идеальное отношение между ними, выраженное теоремой Пифагора. Структура класса Velocity содержит модификаторы setSpeedQ и setDirection(), которые позволяют улучшить рабочие характеристики программы, устранив необ- ходимость создания новых экземпляров всякий раз, когда значения одного или обоих этих атрибутов изменяются. Velocity speed Speed direction : Direction Velocity() Velocity(speed Speed, direction Direction) getSpeed() Speed getSpeedX() Speed getSpeedY() Speed getDirection() Direction setSpeed(speed Speed); setDirection(direction Direction); reverse(); reverseY(); reverseX(); Pl/ICyHOK 5.2. Класс Velocity, описанный в виде UML-модели 7*
Глава 5. Основы тестирования классов Velocity::Velocity О <= direction and direction < 360 and speed >= 0 and speedX = ( (2 * PI * direction / 360.0). cos * speed), floor and speedY = ( (2 * PI * direction / 360.0). sin * speed), floor and speedX*speedX + speedY*speedY <= speed*speed Velocity::Velocity(); pre : true post: self.speed = 0 and self.direction = 0 Velocity::Velocity(speed : Speed, direction : Direction); pre : speed >= 0 and (0 <= direction and direction < 360) post: self.speed = speed and self.direction = direction Velocity:;getSpeed() : Speed pre: true post: result = self.speed Velocity::getSpeedX() : Speed pre: true post: result = speedX Velocity::getSpeedY( ) : Speed pre: true post: result = speedY Velocity::getDirection() : Direction pre: true post: result = direction Velocity::setSpeed(speed : Speed) pre: speed >= 0 post: self.speed = speed and direction = direction@pre Velocity;:setDirection(dir : Direction) pre: 0 <= dir and dir < 360 post: direction = dir and speed = speed@pre Velocity::reverse() pre: true post: self.speed = speedSpre and speedX = -speedXSpre and speedY = -speedY@pre Velocity::reverseY() pre: true post: self.speed — speedSpre and speedY = -speedY@pre and direction = (360 - direction@pre).mod(360) Velocity::reverseX() pre : true post: speed = speedSpre and direction — if direction@pre <= 180 then (180 - direction@pre) else (540 - direction@pre).mod(360) Рисунок 5.3, Спецификация класса Velocity на языке OCL
Гпаев 5. Основы тестирования классов Построение тестовых случаев по пред- и постусловиям Общая идея отбора тестовых случаев с использованием для этой цели предус- ловий и постусловий той или иной операции заключается в определении требова- ний к тестовым случаям во всех возможных сочетаниях ситуаций, в которых пре- дусловия могут выполняться, а выполнение постусловий может быть достигнуто. После этого нужно построить тестовые случаи, которые соответствуют этим требо- ваниям. Учитывая эти требования, постройте тестовые случаи с конкретными входными значениями, включая обычные и граничные значения, и определите корректные выходные значения. В завершение добавьте тестовые случаи, предназ- наченные для проверки того, что произойдет, если постусловия будут нарушены. Для того чтобы определить общие требования к тестовым случаям по пред- и постусловиям, можно проанализировать каждую логическую связку в условии, сформулированном на ОСЬ, и составить список тестовых случаев, которые следу- ют из структуры этого условия. На рис. 5.4 и 5.5 представлены требования к тес- товым случаям, которые проистекают из различных форм логических выражений, соответственно, в предусловиях и постусловиях. На рис. 5.4 показаны дополни- тельные тестовые случаи, которые вытекают из неявного применения принципов за- щитного программирования. Обратите внимание на существенное увеличение коли- чества требований к тестовым случаям по сравнению с контрактным подходом. Воспользуйтесь этими двумя рисунками, дабы выявить минимальное количе- ство тестовых случаев3, требуемых для тестирования заданной операции, для чего необходимо (согласно спецификации) использование всевозможных сочетаний предусловий и постусловий. Выполните следующие действия: 1. Составить список тестовых случаев, включение которых в тестовый набор обусловлено предусловиями, отобрав из таблицы на рис. 5.4 вхождения, со- ответствующие форме заданного предусловия. 2. Составить список тестовых случаев, включение которых в тестовый набор обусловлено постусловиями, отобрав из таблицы на рис. 5.5 вхождения, со- ответствующие форме заданного постусловия. 3. Сформулировать требования к тестовым случаям, беря всевозможные соче- тания вхождений из полученных в п.п. 1 и 2 списков тестовых случаев. Один из способов заключается в подстановке каждого входного ограниче- ния из первого списка на место каждого вхождения Pre во втором списке. 4. Удалить все не имеющие смысла условия, порожденные таблицей. Напри- мер, предусловие, подобное, скажем, {color = red) or (color = blue) порождает тестовый случай с предусловием (color = red) and (color = blue), которое удовлетворить невозможно.4 ’ Минимальным, поскольку обычно они не принимают во внимание тестовые случаи из эквивалентных классов значений. * Можно утверждать, что более точной формулировкой условия является (color = red) xor (color - blue), которая означает, что должно быть выполнено одно или другое условие, но не оба сразу. В подобно- го рода ситуациях тестировщик может рекомендовать разработчикам внести соответствующие изме- нения в спецификацию с целью ее улучшения.
Глава 5. Основы тестирования классов Логическое выражение Включение тестовых случаев true Ф (true, Post] (Ф, Post) (not Ф, Exception) ® not Ф (not Ф, Post) (Ф, Exception) ® Ф and ® (Ф and ®, Post) (not Ф and ®, Exception) ® (Ф and not ®, Exception) ® (not Ф and not ®, Exception) ® Фог ® (Ф, Post) (®, Post) (Ф and ®, Post) (not Ф and not ®, Exception) ® Фхог ® (Ф and not ®, Post) (not Ф and ®, Post) (Ф and ®, Exception) ® (not Ф and not ®, Exception) ® Ф impliee @ (not Ф, Post] (®, Post) (not Ф and ®, Post] (Ф and not ®, Exception] ® if Ф then ® elae ® endif (Ф and ®, Post) (not Ф and ®, Post] (Ф and not ®, Exception] ® (not Ф and not ®, Exception) ® Примечания 1. Ф, ® и ® представляют компоненты в OCL-выражении. 2. Если в спецификации неявно учитываются принципы защитного программирования, то должное внимание потребуется уделить тестовым случаям, помеченным значком ®, Если защитное программирование представлено в спецификации в явном виде, то такие тестовые случаи должны включаться в тестовый набор. Рисунок 5.4. Включение тестовых случаев в тестовый набор по предусловиям
Глава 5. Основы тестирования классов ТЕСТОВЫЕ СЛУЧАИ ДЛЯ ВЫЯВЛЕНИЯ НЕВЫПОЛНЕННЫХ ПРЕДУСЛОВИЙ В обсуждениях спацификации класса, провадвнных в главе 2, было дано описание защитного программирования и контрактного подхода. В условиях защитного про- граммирования в реализации класса в каждый метод включаются программные коды, которые проверяют, выполняются ли ассоциированные предусловия. В условиях контрактного подхода такие программные коды на включаются, поскольку предпо- лагается, что каждый клиент, запрашивающий выполнение той или иной операции, самостоятельно убедился в удовлетворении этих предусловий. Во многих случаях принцип защитного программирования присутствует в специфи- кации класса неявно — другими словами, спецификация класса составлена с соблю- дением тах же предусловий, постусловий и инаариантов, какие были бы использованы при контрактном подходе. Общепринятым является понимание того, что каждое нарушение того или иного предусловия влечат за собой выполнение некоторого стандартного действия, подобного аварийному завершению программы, выдаче стан- дартного сообщения об ошибка или фиксации ошибки в журнала регистрации ошибок. Тестировщики должны учитывать асе возможности неявной обработки ситуаций нарушения условий. Если неявная обработка таких ситуаций уже заложена в специ- фикацию класса, тестировщики должны построить тестовые случаи, обеспечивающие проверку этой неявной части спецификации. Например, при тестировании метода Velocity::eetDirection() потребуется включить в тестовый набор дополнительные тестовые случаи с целью охвата проваркой ситуации, когда напраалвние получает отрицательное значение или его значение превышает 359. Если проектировщики принимают для некоторого класса перспективу контрактного программирования, то программисты, реализующие код для этого класса, могут для целей отладки включить программные коды, выполняющие в рабочем цикле проварку предусловий и постус- ловий. Если понадобятся тестовые случаи для проварки этих отладочных кодов, позаботьтесь о том, чтобы тестовый драйвер мог идентифицировать такие тестовые случаи, дабы их можно было заблокировать при отключении отладочного кода. Постусловие Включение тестовых случаев О (Pre, О) О and © (Pre, О and ©) О or © (Pre, О) (Pre, ©) (Pre, О and ©) О хог © (Pre, О and not ©) (Pre, not О and ©) О impliee © (Pre, not О or ©) if О then © else ©endif (Pre and O, ©) (Pre and not O, ©) Примечания 1.0, © и © представляют компоненты в OCL-выражении. 2. Для постусловия if О then © else © endif, если выражение О не зависит от результатов применения соответствующего тестового случая, то О = О иначе О есть условие, которое присваивает О значение true в момент применения соответствующего постусловия. Рисунок 5.5. Включение тестовых случаев в тестовый набор по постусловиям
Глава 5. Основы тестирования классов Если предусловия и постусловия принимают более сложную форму, нежели показанная в описанной выше таблице — например, форма содержит три дизъюн- ктивных члена — то процессы, описанные в пунктах 1 и 2 должны применяться к этим условиям рекурсивно. Иначе говоря, указанные выше условия разбиваются на более мелкие фрагменты, к каждому фрагменту применяются упомянутые правила, после чего полученные списки объединяются. К счастью, получившие широкое признание принципы объектно-ориентированного проектирования в большинстве случаев позволяют получать достаточно простые выражения для предусловий и постусловий На рис. 5.6 и 5.7 показано, как следует пользоваться этими таблицами приме- нительно к двум операциям класса Velocity. Построение тестовых случаев по диаграммам переходов Диаграммы переходов служат графической иллюстрацией поведения, ассоции- рованного с экземплярами класса. Такие диаграммы могут быть дополнением письменных спецификаций либо полностью отображать спецификацию. Диаграм- ма перехода класса PuckSupply (запас шайб) показана на рис. 5.8. В запасе шайб содержатся шайбы, которые еще не вступали в игру во время сеанса игры "Кирпи- чики". Требования, предъявляемые к этому классу и сформулированные на языке OCL, также присутствуют на этом рисунке, так что можете самостоятельно срав- нить оба вида спецификаций. Можно воспользоваться тем же общим подходом для построения тестовых слу- чаев, которые, согласно данному выше описанию, используют пред- и постусло- вия. Каждый переход в диаграмме означает требование одного или большего ко- личества тестовых случаев. На диаграмме, представленной на рис. 5.8, показаны шесть переходов из одного состояния в другое, а также один переход, представля- ющий построение объекта, и два перехода, представляющие уничтожение объек- тов — в сумме девять переходов.5 Следовательно, получается девять требований к тестовым случаям. Мы удовлетворим эти требования, выбирая соответствующие представительные и граничные значения для каждой стороны перехода. Если пе- реход защищенный, то вы также должны выбрать граничные значения для защит- ного условия. Граничные значения состояний определяются на базе диапазона значений ат- рибутов, ассоциированных с этим состоянием. Каждое состояние определяется соответствующими значениями атрибутов. В классе PuckSupply состояние Empty (Пустой) ассоциируется со значением атрибута size (Размер), равным нулю. Со- стояние Not Empty (Не пустой) ассоциируется со значением атрибута size, не рав- ным нулю. Мы обязательно должны зарезервировать тестовый случай, дабы про- верить, что экземпляр класса PuckSupply не ведет себя как пустой, в то время как значение его атрибута size есть единица. 5 Переход в суперсостояние объекта PuckSupply распадается на два подсостояния, что дает два пе- рехода.
Глааа 5. Основы тестирования классов Velocity::setDirection(dir : Direction) pre: 0 <= dir and dir < 360 post : direction = dir and speed = speedgpre В eatDiraction() Ф представляет выражаниа О <= dir, ® представляет выражаниа dir < 360, О представляет direction = dir, a © представляет spaed = speed@pra. Вхождения таблицы для предусловий вида Ф and ® и постусловий вида О and © объединяются с целью получить требования, предъявляемые к тестовым случаям. (Ф and ®, О and ©] (not Ф and ®, Exception} (Ф and not ®, Exception] (О <= dir and dir < 360, direction = dir and speed = speed@pra) ® (not (0 <= dir) and dir < 360, Exception] © © (0 <= dir and not (dir < 360), Exception) © (not Ф and not ®, Exception) ® ((not (0<—dir) and not (dir<360), Exception) Мы удаляем последнае условие, поскольку значение dir не может быть одновременно меньше нуля и больше или равно 360. Мы сохраняем второе и третье условие, поскольку, например, специалисты по кодированию проверяют во время разработки предусловия с утверждениями, и мы хотим проварить, корректно ли работают эти утверждения. Теперь можно удовлетворить требования тестовых случаев, устанавливая значения для dir, direction и speadtSpre. С позиций перспективы тестирования необходимо проследить, как программист может воспользоваться готовыми таблицами и/или тригонометрическими тождествами для ускорения вычислений значений синуса и косинуса при вычислении составляющих скорости. Следовательно, мы принимаем решение выполнять исчерпывающее тестирование, поскольку изменание направления приводит к изменению обоих составляющих скорости. Мы используем 1ООО в качестве значения скорости для всех значений направления от О до 359 включи- тельно. Несмотря на то что маловероятно, чтобы шайбы а игре «Кирпичики» пере- мещались с текой скоростью, значение 1000 даат нам точность а три цифры при вычислении синуса или косинуса как целочисленных значений, благодаря чему уве- личиваются возможности по проверке реализации. Что касаатся второго тестового случея, то мы выбираам для скорости значение 1000 и для направления значение -1. Для третьего случая использования мы принимаем значение 360 (граничное значение] и 540 как произвольное значение, превышающее 360. В условиях ис- черпывающего тестирования потребуется испробовать 363 тестовых случая при проверке aatDiraction(). В то врамя как граничные значения могут быть введены и для скорости, мы считаем, что с ними связан низкий уровень риска и на будем строить тестовые случаи для нулевого или максимально возможного положительного целочисленного значения скорости. В перспективе тестирования, если программный код работает корректно для скорости 1000, он с высокой степенью вероятности будет работать и для любого другого значения скорости. Можно проанализировать покрытие программного кода этого метода, чтобы получить подтверждение нашей точки зрения. Если каждый оператор программного коде будет выполняться во время прогона всех тестовых случаев хотя бы раз, и прогон тестовых случаев заеаршится успешно, то е такой ситуации мы можем быть практически уварены в корректности тестируемого про- граммного кода. Рисунок 5.В. Выбор тестовых случаев для проверки метода Velocity::setDirection()
Глава 5, Основы тестирования классов Velocity::reverseX() pre: true post: speed = speed@pre and direction = if direction@pre <= 180 then (180 - direction@pre) else (540 - direction@pre).mod(360) Поскольку постусловия содержат and и if-then-else, потребуется применить тебличное расширение рекурсивно. Не первом уровне О предстевляет вырежение speed = speed@pre и О предстевляет еырежение if directiontSpre <= 180 then (180 - direction@pre) else (540 - direction@pre).mod(36O). He втором уровне О представляет direction@pre <= 180, © представляет (180 - direction@pre) и О предстевляет (54O-direction@pre).mod(36O). Воспользуемся индексеми для обознвчения уровней. Вхождения в теблице, в которых содержатся предусловия, предстевленные в виде true, и постусловия в виде О, and ©2, применяются в первую очередь, е результете чего имеем: (Pre, О, and ©,) (true, speed = speed@pre end ©,) Использовение теблицы для ресширения оператора if О then © else © и объединение с результатом, полученным еыше, позволяют сформулироееть требовения к тестовым случеям (Pre end О2, ©2] (Pre and not О2, ©2] (true and direction@pre <= 180, speed = speed@pre and (180 - direction) (true and not direction@pre <= 180, speed = speed@pre and (540 - direction@pre).mod(360)J Обретите внимение не то обстоятельство, что в условии Pre = true end О2 = direction@pre <= 180 используется только одне постояннея величине и только зне- чения атрибуте @рге. Теперь можно удовлетворить эти требовения, предъявляемые к тестовым случеям, присваивая соответствующие знечения етрибутем direction@pre и speed@pre. По- скольку было решено выполнить проверку setDirection() в режиме исчерпывающего тестирования, будем тестироееть метод reverseX() на граничных значениях и на некоторых промежуточных значениях. Для этой операции греницы проходят по непрев- лению О (вправо], 90 (вверх), 180 (влево) и 270 (вниз), кроме того, выберем также по одному значению между греничными знечениями, скежем 30, 135, 188 и 275. Можно выбреть произвольную скорость. Мы выберем 10 для кеждого тестового случая. Теким образом, первый обобщенный тестовый случей порождеет тестовые случаи, для которых зедена скорость 10 и непрввления О, 30, 90, 135 и 180. Второй обобщенный тестовый случай порождеет тестовые случаи, для которых звдене ско- рость 10 и напревпения 188, 270 и 275. Рисунок 5.7. Выбор тестовых случаев для проверки метода Velocity::reverseX()
Глава 5. Основы тестироввния клвссов ОПИСАНИЕ ТЕСТОВЫХ СЛУЧАЕВ В то врамя как звдвть тестовый случай в видв пары (ввод, вывод] нетрудно, то далвко не просто в твкой лвконичной форма описать, что собой првдстввляют ввод и вывод. Ввод првдполагввт нвличие тестируемого объекта, или OUT-объакта (Objact Undar Test — тестируемый объект), в звдвнном состоянии, при этом конкрвтныв знвчвния присвоены всем вго втрибутвм; всем атрибутам нулевого или большего количества объектов в звданном состоянии, которые поддврживвют связи с тестируемым объек- том (возможно, помогвющим определить конкретное состояние тестируемого объек- та); всем втрибутвм сообщений, образующих послвдоввтельности, состоящие из одного или большего числе сообщений (или других событий], пересылввмых OUT-объекту; всем атрибутам нулевого или большего количества объектов (значений), которые используются в квчвстве пврвматров сообщений. Вывод првдусмвтривват итоговое состояние OUT-объектв, итоговые состояния любых объектов, вссоциироввнных с ОиТ-объактом, результат, полученный в ответ на последнее сообщение, поступившее в качестве ввода, и итоговые состояния объектов, парадвнных объектам через па- раметры сообщений. Обрвтита внимвние на то обстоятельство, что класс OUT- объектв может оказвться клвссом одного из объектов, ассоциированных с тестиру- емым. Для описвния тестовых случаев применяется текстовая нотация. Мы воспользуемся таблицей, в которой один из столбцов отводится для вводов, другой столбец отводится для выводов. Каждый столбец рвзбиввется на двв дальнейших столбце, как показано ниже. Такст каждого столбце, зв исключением столбце События, суть выражения на языка OCL. В столбце События используется нотвция языка программирования. К событиям относятся сообщения и создвние новых объектов. Ввод Вывод Состояние События Состояние Сгенерированные исключительные ситуации Нет OUT = new Velocity; OUT.speed = 0 and OUT.direction = 0 and OUT.speedX = 0 and OUT.speedY = 0 Нет OUT.Velocity [speed=100, direction=90] OUT.setDirection(45) OUT.speed=1000, OUT.direction=45, OUT.speedX=707, OUT.speedY=707 Нет Первый тестовый случай, включенный в список, предназначай для тестироввния конструктора по умолчанию. Второй предназначай для тестироввния метода setDirection(). В соответствии с соглашением, для ссылки на тестируемый объект используется имя OUT. Обознвчение OUT:Velocity[speeds100, direction=90] оз- начает, что OUT-объект является экземпляром классе Velocity, втрибуты которого принимают значения, указанные в кввдратных скобквх. Если знвчения втрибутов не указаны, то это означает, что для данного тестового случая они не существенны. Мы можам генерировать программные коды тастового случая непосредственно. Для каждого тестового случая следует записать прогрвммныа коды, устанавливающие состояние ввода, звтем звписать коды, порождающие события, после чего записвть коды, осуществляющие проверку результатов.
Глввв 5. Основы тестироввния классов Для большинства из нас построение тестовых случаев на базе диаграммы со- стояний носит более содержательный характер, нежели их построение по пред- и постусловиям. Поведение, ассоциированное с тем или иным классом, на диаграм- мах представляется более наглядно. Благодаря диаграммам проще определить все требования к тестовым случаям, поскольку они проистекают непосредственно из переходов. Тем не менее, мы должны хорошо понимать, каким образом состояния выражаются через соответствующие значения атрибутов и как события отражают- ся на отдельных значениях атрибутов в условиях конкретного состояния. В каче- стве случая рассмотрим состояние Not Empty объекта класса Pucksupply. Если пользоваться только диаграммой состояний, то остается только догадываться, что всякий раз, когда шайба удаляется, размер запаса уменьшается на единицу. Зато это наглядно видно из спецификации на OCL. PuckSupply size >= О PuckSupply::PuckSupply(); pre: true post: size = 3 and Puck->size() = Puck@pre->size() + 3 and pucks->forAll(puck: Puck | not puck.inPlay() ) PuckSupply::-PuckSupply(); pre: true post: Puck->size() = Puck@pre->size () - size@pre void PuckSupply::size() const; pre: true post: result = size Puck * PuckSupply: :get() pre: not self .isEmpty () post: result = pucks->asSequence->first and size = size@pre bool PuckSupply::isEmpty() const; pre: true result = (size = 0) 1 Рисунок 5.8. Диаграмма переходов класса PuckSupply и спецификация на языке OCL
Главв 5. Основы тестирования классов С другой стороны, Velocity может служить примером класса, в котором опреде- лено только одно состояние и двенадцать переходов. Трудно выявить все необхо- димые тестовые случаи, исходя только из этой простой диаграммы. Выполняя те- стирование, взяв за основу диаграммы переходов, следите за тем, чтобы разрабатываемые тестовые случаи исследовали все граничные значения. Адекватность тестовых наборов, предназначенных для тестирования класса В идеальном случае можно выполнять исчерпывающее тестирование каждого класса, т.е. тестирование на всех возможных значениях, чтобы убедиться в том, что каждый класс соответствует предъявленным к нему требованиям. На практике исчерпывающее тестирование либо вообще невозможно, либо требует значитель- ных трудозатрат. Тем не менее, некоторые классы целесообразно подвергнуть ис- черпывающему тестированию. Рассмотрим, например, класс Velocity в игре "Кир- пичики". Если он работает некорректно, то правильная работа всей системы невозможна. Выгода, получаемая в данной ситуации от исчерпывающего тестиро- вания, перевешивает затраты на написание тестового драйвера, обеспечивающего прогон большого количества тестовых случаев. Обычно исчерпывающее тестирование теряет смысл в условиях временных и ресурсных ограничений, поэтому потребуется предпринимать достаточное тести- рование классов. Без исчерпывающего тестирования мы не можем быть уверены в том, что каждый аспект класса соответствует его спецификации, но в то же время можно воспользоваться некоторой мерой адекватности, которая придаст большую уверенность в том, что тестовый набор обладает необходимыми качествами. Тремя широко используемыми мерами адекватности являются покрытие, ориентирован- ное на состояния, покрытие, ориентированное на ограничения и покрытие, ориентиро- ванное на программные коды. Минимальное соответствие каждой из этих мер приводит к различным тесто- вым наборам. Применение всех трех мер для формирования тестового набора со- ответственно повышает уровень доверия к результатам тестирования. Покрытие, ориентированное на состояния Покрытие, ориентированное на состояние, основано на том, сколько переходов на диаграмме переходов покрывается тестовым набором. Если хотя бы одно или большее число переходов не покрыто, это значит, что данный класс не прошел адекватного тестирования, и требуется построить дополнительные тестовые слу- чаи, дабы покрыть эти переходы. Если тестовые случаи построены на базе диаг- раммы состояний в соответствии с изложенным выше, то эти тестовые случаи до- стигают упомянутой меры. Если тестовые случаи были построены на основании пред- и постусловий, то исследование тестовых случаев с целью выяснить, какие переходы они покрывают, полезно при выяснении, каких тестовых случаев не хватает.
Глава 5, Основы тестирования классов Даже если все переходы покрыты хотя бы раз, вряд ли такое тестирование мож- но рассматривать как адекватное, ибо состояния обычно охватывают диапазоны значений различных атрибутов объекта. Нам нужно выполнить тестирование на значениях, выходящих за пределы соответствующих диапазонов. Мы должны вы- полнять тестирование как на обычных, так и на граничных значениях. Мы также должны выяснить, как протекает взаимодействие операций в усло- виях переходов. Если имеют место два перехода Т и Т2 в некоторое состояние и один переход Т; из соответствующего состояния, то может случиться так, что тес- товые случаи для перехода Т5 могут быть выполнены успешно в ситуации, когда это входное состояние было установлено посредством перехода Т и не пройдут, если это состояние было установлено через переход Т2. Упомянутую проблему можно решить, применяя меру адекватности, в основу которой положено покры- тие всех пар переходов на диаграмме переходов. В нашем примере потребуется подвергнуть тестированию сочетания Т(-Т2 и Т(-Тг Покрытие, ориентированное на ограничения Параллельно оценке адекватности тестового набора, основанной на переходах из одного состояния в другое, можно выразить такую адекватность через количе- ство покрытых пар пред- и постусловий. Если, например, предусловиями некото- рой операции являются pret или рге2, а постусловиями — post) или post? то мы должны убедиться в том, что тестовый набор содержит тестовые случаи для про- верки всех правильных комбинаций - pret = true, pre2 = false, postj = true, post2 = false; pret = false, pre2 = true, post) = true, post2 = false и т.д. Вспомните описанные выше действия, которые предпринимались с целью вы- явления требований, предъявляемых к тестовым случаям при их построении по пред- и постусловиям. Если создавать по одному тестовому примеру для провер- ки каждого требования, то тестовый набор соответствует заданной мере адекват- ности. Аналогично тому, что говорилось выше относительно использования попарных последовательностей переходов при реализации покрытия, ориентированного на состояния, можно воспользоваться последовательностью операций, основанной на анализе пред- и постусловий. Для каждой операции ор, не являющейся средством доступа, вводятся операторы орр ор2 и т.д., для которых выполняются предусло- вия, когда выполняются постусловия для ор. В таких случаях выполняйте тесто- вые случаи для ор-орр ор-ор2 и т.д. Покрытие, ориентированное на программные коды Третья мера адекватности тестового набора может быть основана на том, какая часть программного кода, реализующего класс, выполняется на всех тестовых слу- чаях тестового набора. Идея состоит в том, что каждая строка (или каждый путь) программного кода, реализующего класс, была выполнена хотя бы раз после того, как выполнен прогон всех тестовых случаев.
Глава 5. Осноаы тестирования классов ГРАНИЧНЫЕ УСЛОВИЯ В процессе тестирования того или иного компонента часто случается так, что не- большое изманвнив входной величины влвчвт за собой значительные изменения а реакции программного обеспечения. Входное значение, которое вызывввт такие большие изменения, называется граничным. Слвдувт учитывать грвничныв значения при выборе тестовых случаев. Необходимо построить тестовые случаи, которые выполняют проверку на входных значениях, близких к каждой границе. Отклики системы на входные значения, выбранные а промежутке между двумя смежными границами, в общем случав эквивалентны. Для того чтобы провести адекватное тестирование, достаточно сравнитально небольшого набора вводов для тестовых случаев, но при этом необходимо построить тестовый случай, принимающий значения по каждую сторону (и возможно] для каждой границы. Некоторые границы нетрудно обнаружить на диаграмме переходов по защитным условиям, установленным на пврвходв из одного состояния в другое - один тестовый случай для значения true условия и другой тестовый случвй для значения falsa. Другие границы нв так четко выражены на диаграмме переходов, поскольку они нв столько оказывают аоздвйствия на состояние, сколько влияют на реакцию системы. Возьмем, напримвр, метод вычисления даты по юлианскому календарю. Вполне понятно, что значение, соотввтствующвв первому марта, зависит от того, является ли год висо- косным. Некоторые границы могут быть выявлены только по программному коду, поскольку они порождаются алгоритмом, используемым для реализации спецификации, но нв следуют из самой спецификации. Типичным примером можвт служить функция сор- тировки массива целых чисел а неубывающем [возрастающем] порядке. Что касается размеров такого массиве, то граничными условиями являются: • Массив, нв содержащий ни одного элвмвнтв • Массив, содержащий только один элемент • Массив, содержащий только два злвмвнтв - наименьший мвссив, который можвт быть подвергнут сортировке • Массив, содержащий большое количество элементов. Что касается порядке, в каком сортируемые элементы располагаются в исходном массиве, то возможны следующие варианты: • Элементы расположены в произвольном порядке • Элементы имеют одно и то жв знвчвнив • Элементы отсортированы в указанном выше порядке • Элементы отсортированы в противоположном порядке. И наконец, можно учитывать фактичвскив значения, принимаемые элементами сор- тируемого массива; это могут быть уникальные знвчвния, одни и тв жв значения либо частично уникальные или чвстично одинаковые. Элементы массива принимают значения в промежутке от наименьшего граничного значения до наибольшего граничного значения. Эти три аспекта приводят к пост- роению достаточно большого числа тестовых случаев.
Глава 5. Основы тестирования классов ПОТРЕБНОСТЬ В ТЕСТИРОВАНИИ, ОРИЕНТИРОВАННОМ НА ПРОГРАММНЫЕ КОДЫ Во время тестирования функции, выполняющей сортировку массива целых чисел (см. раздел «Граничные условия»), возможно, потребуется произвести отбор тестовых случеее с целью уменьшения зетрат не тестировение. (Попарный отбор будет рас- сматриваться в следующей главе.) Предлагаемый ниже перечень тестовых случаев предстевляет собой достеточно полный небор возможностей в этом плене: • Массив из нулевого числе элементов • Массив, содержащий только один элемент • Мессив, содержащий в точности два неупорядоченных элементе • Массив из 100 элементов, принимеющих 100 попарно различных положитель- ных и отрицательных знечений, расположенных в произвольном порядке • Мессиа из 101 элементе, которые принимеют одно и то же знечение • Массив из 50 элементов, содержащий уже отсортироеенные энечения • Мессив из 72 элементов, содержащий значения, отсортированные в обратном порядке. Значения размеров массивов были еыбрены в произвольном порядке. Вполне воз- можен любой другой реционельный выбор размеров массива. Мы решили восполь- зоваться резличными размерами мессивое в целях удобстве отборе. Одни из входных мессивов упорядочены произвольным обрезом, другие уже отсортированы или отсор- тированы е обратном порядке. По-видимому, эти тестовые случен образуют доста- точное покрытие спецификации. Если функция успешно проходит эти тестовые случаи, то вполне можно рессчитыветь не то, что она способна отсортировать любой мессив. Разумеется, исчерпывающее тестирование придвст еще большую уверенность. Тем не менае, мы никогда не можем быть полностью уверены в том, что компонент соответствует своей спецификации только не осноеении успешного прогоне тестовых случаев, построенных не безе спецификации. Рассмотрим сценарий, по условиям которого эте функция сортировки реализована таким обрезом, что для упорядочения массивов, размер которых меньше 1024, применяется алгоритм пузырьковой сор- тировки, а для сортировки более крупных массивов используется алгоритм быстрой сортировки. Тогде описенный выше тестовый набор не обеспечивеет едекватного тестирования прогреммных кодов этой функции. Резмер 1024 предстевляет собой греничное знечение, которое устеновлено реализацией и о котором ничего не сказано е спецификации. Следоветельно, возникеет необходимость в тестовых случеях, в которых используются мессиеы, содержещие 1024 и большее количество элементов. Инструментальные средства для соответствующих измерений предлагаются ря- дом поставщиков на коммерческой основе. Если какие-либо строки (или пути) не были задействованы, то соответствующий тестовый набор необходимо пополнить тестовыми случаями, которые заставляют эти строки (пути) работать, в противном случае тестируемый программный код следует скорректировать с целью удаления недостижимых строк. Даже если полное покрытие программного кода возможно, соответствующий тестовый набор может оказаться неадекватным для рассматриваемого класса в силу того, что он не инициирует взаимодействий методов, о чем шла речь при обсуждении покрытий, ориентированных на состояния и ограничения. Использо- вание других оценок для измерения адекватности тестовых наборов имеет боль-
Глава 5. Основы тастирования классов шое значение. Но не менее важным является измерение покрытия программных кодов (см. примечание). Один из методов измерения адекватности тестового на- бора, используемый на уровне реализации, предусматривает измерение покрытия последовательностей операций. Если выполнены не все операторы, необходимо построить новые тестовые случаи, которые могли бы заставить их работать. Построение тестового драйвера Тестовый драйвер представляет собой программу, которая осуществляет прогон тестовых случаев и сбор полученных при этом результатов. Мы дадим описание трех основных подходов к построению тестовых драйверов. Вполне возможно, что существуют и другие подходы, и, несомненно, существуют различные вариан- ты подходов, предлагаемых нами. Из этих трех подходов мы выделим один и рас- смотрим его во всех подробностях.6 Рассмотрим три способа реализации тестового драйвера для тестирования класса Velocity(). Чтобы проиллюстрировать структуру схемы тестового драйвера, воспользуемся языком C++. 1. Реализовать функцию main(), которую можно подвергнуть условной компи- ляции (с использованием #define TEST) при компиляции определений функций-членов (файл Velocity.срр), с последующим ее выполнением (см. рис. 5.9). 2. Реализовать функцию-член типа static в рамках класса, который может быть вызван для выполнения и сбора результатов прогона каждого тестового случая (см. рис. 5.10).7 3. Реализовать отдельный класс, в обязанности которого входит выполнение и сбор результатов для каждого тестового случая (см. рис.5.11). Функция main() порождает экземпляры этого класса и передает ему сообщение с тре- бованием прогона всех тестовых случаев. Примечание: в среде Java main() может быть статическим методом класса VelocityTester. Все три схемы эквивалентны в том смысле, что они поддерживают прогон од- них и тех же тестовых случаев и уведомляют о результатах прогона. Информация о некоторых сильных и слабых сторонах каждой схемы сведена в таблицу на рис. 5.12. Вторая и третья схемы привлекательны прежде всего, тем, что они могут быть реализованы с применением стандартных средств многих языков объектно-ориен- тированного программирования. 6 Если поведение класса предусматривает останов программы в качестве постусловия — например, когда реализация, в основу которой положен принцип защитного программирования, использует для проверки предусловий библиотечную функцию assert(), то могут потребоваться более одного тесто- вого драйвера, либо тестовый драйвер должен поддерживать некоторые способы прогона отдельных тестовых случаев. 7 В среде Java это может быть метод класса с именем main(), благодаря чему выполнение тестового драйвера становится столь же простым, как и выполнение файла класса на виртуальной машине Java.
Глава 5. Основы тестирования классов // Файл: Velocity.h fifndef VELOCITY H // Файл: Velocity, cpp finclude "Velocity.h" #define VELOCITY_H Velocity: :Velocity() finclude "direction.h" finclude "speed.h" _speed(0) , _direction(0) , ... { } Velocity: : Velocity (Speed speed, class Velocity { public: Direction dir) speed (speed) , direction (dir) , ... { } Velocity () ; Velocity(Speed speed, Direction dir) ; fifdef TEST // Программный код тесяового драйвера private: int main() { Speed _speed; // Прогон тестовых случаев и Direction direction; // уведомление о результатах }; // ... } #endif Рисунок 5.9. Условно компилируемый тестовый драйвер для класса Velocity, вложенный в исходный класс // Файл: Velocity.h #ifndef VELOCITY H // Файл: Velocity.cpp finclude "Velocity.h" #define VELOCITY_H Velocity: :Velocity() finclude "direction.h" finclude "speed.h" speed(0), —direction(0), ...{ } class Velocity { public: Velocity::Velocity(Speed speed, Direction dir) : _speed (speed) , —direction (dir) , ...{ } Velocity(); Velocity(Speed speed, Direction dir) ; int Velocity: :main() ( // Прогон тестовых случаев и private: // уведомление о ревультатах Speed _speed; Direction —direction; // ... } // }; Рисунок 5.10. Тестовый драйвер как встроенная операция класса Velocity
Главв 5. Основы тестирования классов // Файл: VelocityTester,h Uifndef VELOCITYTESTER_H fide fine VELOCITYTESTER_H # include "Velocity.h" class VelocityTester ( public: VelocityTester (} ; void runTestSuite (} ; private: ); // Файл: VelocityTester.cpp #include "VelocityTester. h" VelocityTester: : VelocityTester (} : ( } void VelocityTester: :runTestSuite (} { // Программный код для прогона тестовых // случаев и уведомления о ревультатах } // Файл: VelocityTesterMain.срр #include "VelocityTester.h" int main(} VelocityTester tester; tester. runTestSuite (} ; } Рисунок 5.11. Тестовый драйвер для тестирования класса Velocity, реализованный в виде отдельного «тестирующего» класса Метод Сильные стороны Слабые стороны 1. Условно компилируемые драйверы Программный код драйвера находится в непосредственной близости (в том же файле) к программному коду класса. Трудности многократного использования драйверов для тестирования подклассов без клонирования. Требуется поддержка условной компиляции. 2. В качестве тестового драйвера используется статический метод. Программный код драйвера находится в непосредственной близости к программному коду класса. Упрощается многократное использование кода драйвера (в силу наследования) для тестирования подклассов. Необходимость соблюдения осторожности при отделении программного кода драйвера от поставляемого программного обеспечения. 3. Отдельный «тестирующий» класс Легко осуществляется многократное использование драйвера при тестировании подклассов. Достигается максимально компактный рабочий код. Достигается максимальное быстродействие рабочего кода. Необходимость построения нового класса. Необходимость соблюдения осторожности при отображения изменений в классе на изменения в тестах. Рисунок. 5.12. Сильные и слабые стороны структур тестовых драйверов
.212 Глава 5. Основы тестироввния классов Мы отдаем предпочтение третьей схеме.8 И хотя в ней тестовый программный код отделен от рабочего программного кода, отношения между классом и драйве- ром, предназначенным для его тестирования, нетрудно запомнить — для каждого класса С имеется тестовый класс с именем CTester. Использование отдельного класса не следует рассматривать только как недостаток. Близость программного кода драйвера к программному коду класса, который он тестирует, можно считать полезным, если и тот и другой коды разрабатывались одним и тем же лицом. Во всех других случаях это недостаток. Подобная структура тестового класса придает ему некоторую гибкость, поскольку в большинстве языков программирования оба эти класса могут быть определены как в одном, так и в различных файлах. Мы сосредоточим свои усилия на изучении структуры тестового класса, хотя многие аспекты разработки такого драйвера могут быть непосредственно перенесе- ны на другие структуры. Требования, предъявляемые к тестовым классам Прежде чем переходить к более подробному изучению тестовых классов, рас- смотрим требования, предъявляемые к тестовому драйверу, ориентированному на тестирование класса в режиме прогона тестовых случаев. Основное назначение тестового драйвера заключается в прогоне тестовых слу- чаев и получении результатов их выполнения. Тестовый драйвер должен иметь сравнительно простую структуру, поскольку в нашем распоряжении редко бывает время и ресурсы для выполнения тестирования программ драйверов в режиме прогона тестовых случаев. Мы в основном полагаемся на ревизию программных кодов самих драйверов. Для поддержки ревизии и упрощения сопровождения следует быть готовым перенести требования к тестированию из плана тестирова- ния в программные коды драйвера. Тестовый драйвер должен быть удобным в со- провождении и легко приспосабливаемым в ответ на изменения в инкременталь- ной спецификации класса, для тестирования которого он и предназначен. В идеальном случае при создании новых драйверов должна быть возможность по- вторного использования программных кодов драйверов, используемых в целях тестирования существующих классов. На рис. 5.13. показана модель класса Tester, которая удовлетворяет перечис- ленным требованиям. Общедоступный интерфейс содержит операции, позволяю- щие выполнять прогоны различных тестовых наборов, или даже все из них. Тес- товые случаи сгруппированы в наборы на основе их происхождения — в функциональный набор, если они построены на базе спецификации, структурный набор, если они построены на базе программных кодов, и набор для тестирования взаимодействий, если они тестируют корректность функционирования последовательности собы- тий, происходящих на объекте, таких как, скажем, пара переходов ввод/вывод. Мы выделяем эти категории с целью упрощения сопровождения тестов. ’ Дополнительные преимущества этого подхода проявляются в случаях тестирования иерархий наследо- вания, о чем пойдет речь в главе 7.
Глава 5. Основы тестирования классов Tester #enum Result {Fail, TBD, Pass} +Tester(logFileName: String) +~Tester() +runAHSuites() +runFunctionalSuite() +runStructuralSuite() +run Interactions uite() +totalTally(): int +passTally(): int +failTally(): int +TBDTally(): int #runBaselineSuite(): Boolean #CUTinvariantHolds(): Boolean #logTestCaseStart(testlD: String) #logTestCaseResult(result: Result) #logComment(comment: String) Тестируемый объект Тестируемый класс Object Class РисуНОК 5.13. Модель класса, отображающая требования, предъявляемые к классу Tester Иногда достаточно трудно провести какие-то разграничительные линии между этими категориями, однако общий критерий включения тестового случая в соот- ветствующую категорию зависит от того, как первоначально был идентифициро- ван этот тестовый случай, и от того, какое воздействие оказали на данный тесто- вый случай изменения, которым подвергся класс. Тестовые случаи из набора для тестирования взаимодействий обычно строятся, чтобы повысить возможности ос- тальных тестовых случаев набора в плане достижения некоторого уровня покры- тия. Тестовые случаи, ориентированные на тестирование программных кодов, ге- нерируются с целью тестирования тех или иных поведений программного кода, которые берут начало от реализации, а не от спецификации. Если изменяется ре- ализация класса, но не его спецификация, мы должны иметь возможность обнов- ления программных кодов драйвера за счет внесения в него изменений, обеспе- чивающих прогон тестовых случаев, ориентированных на тестирование программных кодов. Мы называем такие наборы тестовых случаев конкретной ка- тегории тестовыми наборами этой категории. В соответствии с этим, будем разли- чать функциональный (ориентированный на спецификации) тестовый набор, структурный (ориентированный на реализации) тестовый набор и тестовый набор для тестирования взаимодействий. Тестировщик может воспользоваться специальной операцией подсчета для сле- жения за тем, сколько тестовых случаев выполнено на текущий момент. Драйвер ведет специальный журнал, в котором регистрируются все прогоны тестовых слу-
Глава 5. Осноаы тестироавния классов чаев и полученные при этом результаты, в файле, чье имя определяется в момент его создания. Защищенные операции TestCaseStart(), IogTestCaseResuIt() и logComment() помещают информацию в файл этого журнала. Защищенная опера- ция runBaselineSuit() проверяет правильность методов CUT-класса (Class Under Test — тестируемый класс), которые используются тестовым драйвером при про- верке результатов прогона тестовых случаев. Такие методы, как средство доступа или модификатор, обычно тестируются в рамках прогона базовой части тестового набора тестируемого класса. Операция CUTinvariantHoIds() производит оценку инвариантов CUT-класса, используя для этой цели текущий OUT-объект (Object Under Test — тестируемый объект). Класс Tester представляет собой абстрактный класс. Программный код этого класса обеспечивает реализацию по умолчанию для операций, которыми пользу- ются все (конкретные) тестировщики. В число этих операций входят операции регистрации результатов прогона конкретных тестовых случаев, а также функции, общие для всех тестовых драйверов, такие как измерение памяти, выделенной для использования драйвером в процессе его выполнения, и обеспечение поддержки синхронизации прогона отдельных тестовых случаев. Методы, обеспечивающие прогон тестовых наборов и проверку инвариантов класса, должны быть реализова- ны для каждого конкретного CUT-класса. Теперь рассмотрим типичную структуру класса Tester. Структура класса VelocityTester показана на рис. 5.14. Эта диаграмма содержит несколько больше деталей, касающихся класса Tester, нежели рис. 5.13, в том числе некоторые опе- рации, манипулирующие OUT-объектами, и библиотечные методы построения экземпляров CUT-класса. Мы остановимся на этих вопросах в следующем разде- ле. Основной задачей конкретного класса Tester является применение методов к конкретным тестовым случаям и их прогон как часть тестового набора. Структура класса Tester В связи с тем, что класс Tester обеспечивает выполнение операций, упро- щающих сбор результатов прогона тестовых случаев, основные обязанности конк- ретных классов Tester, подобных VelocityTester, заключается в прогоне тестовых случаев и уведомлении о результатах прогонов. Основными компонентами интер- фейсов классов являются операции по настройке тестовых случаев, анализ ре- зультатов прогона тестовых случаев, выполнение тестовых случаев и создание эк- земпляров CUT-классов, которые используются тестовыми случаями во время прогона. Предлагаемая нами схема зарекомендовала себя как достаточно гибкая и удобная в работе. В следующей главе мы покажем, что она особенно полезна в ситуациях, когда экземпляры одного класса требуются для тестирования другого класса. В рамках конкретного тестового класса мы даем определение одного метода для каждого тестового случая. Будем называть их методами тестовых случаев. Легко прослеживается их связь с планом тестирования — один метод соответствует од- ному или группе тестовых случаев, тесно связанных между собой.
Главв 5. Основы тестироввния клвссов Tester VelocityTester #enum Result {Fail, TBD, Pass) #out: Object +VBlocityTBStBr(logFilBNamB: String) +~VBlocityTBStBr{) +TBSter(logFileNamB: String) +~TBStBr{) +runAIISuitBS() +runAIISuitBS{) +runFunctionalSuitB() +runStructuralSuitB() +runlntBractionSuitB{) — +runFunctionalSuitB() +runStructuralSuitB{) +runlntBractionSuitB{) +totalTally(): int +passTally(): int +failTally(): int +TBDTally{): int #runBasBlineSuitB(): Boolean #CUTinvariantHolds{): Boolean +nBwCUT(): Object #runBaSBlinBSuitB(): Boolean #CUTinvariantHolds{): Boolean +nBwCUT(s: Speed , d : Direction): Velocity +nBwCUT(obj: Object): Object +getOut{): Object +nBwCUT(obj: Object): Object +dispoSBOUT{) #runTBStCaSBl() #runTBStCaSB2() #runTBStCaseN() #logTBStCasBStart(tBStlD: String) #logTBStCaSBRBSult(rBSult: Result) #logComment(comment: String) Рисунок 5.14. Модель класса VelocityTester Цель метода тестового случая состоит в том, чтобы выполнять тестовый случай путем установки состояния ввода, построения последовательности событий и контроля состояния вывода. Методы тестовых случаев В классе Tester каждый тестовый случай представлен одним методом. Имя ме- тода должно в той или иной мере отражать соответствующий тестовый случай. Если число тестовых случаев невелико, можно последовательно пронумеровать тестовые случаи, указанные в плане тестирования, и присвоить соответствующим операциям имена runTestCase01(), runTestCaseO2() и т.д. Последовательная нуме- рация отличается простотой, но при этом могут возникать проблемы, когда тесто- вые случаи каким-то образом упорядочены в тестовом плане, а из тестового набо- ра удаляются одни тестовые случаи и добавляются новые. Обычно достигается определенное соглашение по именованию тестовых случаев, в основе которого лежит их происхождение (см. примечание далее). В обязанности методов тестовых случаев входит построение состояния ввода для конкретного тестового случая, например, создание экземпляра OUT-объекта и
Главе 5. Основы тастирования классов любых других объектов, которые будут передаваться как параметры, с последую- щим построением событий, задаваемых этим тестовым примером. Метод тестово- го случая сообщает о состоянии результата — pass (прошел), fail (не прошел) или TBD9, это состояние показывает, что для определения состояния результата необ- ходимо выполнить дополнительные действия. Метод тестового случая проверяет, выполняются ли инварианты CUT-класса для OUT-объекта. В рассматриваемой нами ситуации метод тестового случая обладает структу- рой, представленной псевдокодом на рис. 5.15. СОВЕТ При создании клвссв Tester с цалью тестирования классов, для которых имаатся множество тестовых случаев, предназначенных для проварки взвимодайствий, постройте метод тестового сценерия для каждого тестового случая. В обязан- ности метода тестовых сценариев входит создание OUT-объактов, которые исполь- зуются методом тестовых случаев, вызов метода тестовых случаев с последующей проваркой постусловий и инаариантов классов. Он также сообщает результаты прогона тестовых случаев. Метод тестовых случаев опарируат только последова- тельностью событий в OUT-объекта. Тестовый случай, проверяющий взаимодей- ствие, можат быть запрограммирован как единый метод тестового сценария, ко- торый вызывает некоторую последовательность методов тестовых случаев, после чаго проводит проверку результатоа тастирования и ганарируат отчет о результатах тастирования. void tc_testCaseID () { сообщить о начале прогона тестового случая; построить новый экземпляр I тестируемого класса с примеиеииеи библиотечного метода; установить экземпляр I в соответствующее состояние ввода; setOOT(I); построить предписанную последовательность событий для тестируемого класса; выполнить проверку постусловий и инвариантов класса применительно к тестируемому классу; сообщить о результатах проверки; disposeOUT() } РисуНОК 5.15. Псевдокод типичного метода тестового случая TBD означает to be determined — подлежит определению. Некоторые результаты требуют внимания со стороны персонала, проводящего тестирование, например, проверка происхождения звука или про- верка изменений, происходящих на экране монитора. В частности, тестирование перегруженного оператора вставки в поток данных для некоторого класса в C++ может потребовать, чтобы тести- ровщик открыл файл и проверил, правильно ли выполнена распечатка данных. В подобных случаях мы предпочитаем включать указания в виде комментариев в журнале.
Глава 5. Основы тастироаания классов ИМЕНОВАНИЕ ТЕСТОВЫХ СЛУЧАЕВ Проблема именования тестовых случаев сама по свба представляет определенный интерес. Мы бы хотели, чтобы эти имена в той или иной степени отражали, какой объект подаергавтся тестированию в данный момент. В средв, в которой номера параграфов используются для нумерации пунктов спецификации, имя тестового случая может содержать ссылку, закодированную там или иным способом, на параграф, который дал начало этому примеру. Это саойстао весьма желательно, поскольку оно упрощает задачу отслеживания соответствующего тестового случая и в силу этого обстоятельстве широко используется при именовании тестовых случаев. В то жа время номера пунктов спецификации никак на связаны со спецификациями на языке OCL или с диаграммами переходов, которые применяются для описания классов. В основу схемы именования тестовых случаев с использованием спецификации может быть положено имя операции и нумерация пред- и постусловий. Предположим, что некоторая операция определена с соблюдением следующих првд- и постусловий: операция pre: Ф or ® Если поставлена цель тестирования каждого сочетания предусловий и постусловий, то получаем следующий набор сочетаний, представленный а виде таблицы: ф О е Имя тестового случая F Т Т F Т Т F Т Т т F Т Т F Т Т F Т т т т F F F Т Т Т F F F Т Т Т Т Т Т oplF2T1T2F op1T2F1T2F op1T2T1T2F oplF2T1F2T op1T2F1F2T ор1Т2Т1F2T oplF2T1T2T op1T2F1T2T ор1Т2Т1Т2Т Имя тестового случая образуется путем нумерации элементов в предусловиях и постусловиях и включении в имя этих номеров, за которыми непосредственно следует символ "Т” или “F”, указывающий истинностное значение, ассоциироаанноа с этим тестовым примером. Если для конкретного тестового случая существует несколько классов эквивалентности, определенных в соответствии с граничными условиями, то к имени может быть добавлен суффикс - например, op1F2T1T2Fa, op1F2T1T2Fb и т.д. Пояснения к тестовым случаям могут быть включены а документацию, сопро- вождающую программный код или план тестирования. Эта схема работает ао время именования тестовых случаев, ориентированных на проверку какой-то одной операции. Тестовым случаям, предусматривающим взаимо- действия, могут быть присвоены имена, сформированные на база конкатенации иман каждой операции в последовательности, образующей взаимодействие.
Глава 5. Основы тестирования классов ПРОВЕРКА ПОСТУСЛОВИЙ И ИНВАРИАНТОВ Проверке постусловий и инвариантов не представляет трудностей, если предположить, что в CUT-кпвссв даны определения общедоступных опервций доступе к значениям состояния и/или атрибутов. Мы предложили два обычных подхода нвписания программ для проверки постусловий и инввриентов. Один из них предусматривает нвписение программного модуля тес- тирующей программы, вычисляющего значения атрибутов по мврв надобности. Другой метод использует ту или иную форму безы денных, в том числе файлы и массивы в памяти, откуда эти данные можно бреть по мерв необходимости. Рассмотрим проверку значений атрибутов epeedX и epeedY инварианте класса Velocity. Соот- ветствующее условие предусматривает вычисление синусов и косинусов углов. Мы можвм вычислять эти значения во время выполнения тестовых случевв с исполь- зованием функций Bin() и сов() из стандартной библиотеки, предполвгея, что эти функции заслуживают доверия. В кечвствв альтернативы можно зарвнее вычислить различные значения атрибутов для рвзличных значений направления и скорости, используемых в тестовых случаях, в звтем использоввть эти денные, когда в них возникает необходимость. Для выполнения подобного рода вычислений мы восполь- зовались программами ведения крупноформатных таблиц. В процессе проверки постусловий особой изобретательности требует кодирование выражений, в которых используется обознвчение @рге, каким снабжено значение переменной к моменту начала выполнения методе. Соответствующий метод должен сохранять эти значения в локальной переменной, значения которой используются всякий раз, когда выходные данные тестового случая становятся доступными для проверки. Для упрощения проверки значений @рге воспользуйтесь библиотечным методом класса Teeter, который соответствует конструктору копироввния. Например, при проверке постусловия для метода BetDirection(Direction dir), воспользуйтесь следующим программным кодом: Velocity *OUTatPre = newCUT( *getOUT() ) // Запомнить состояние if ( OUT.getSpeed() == OUTatPre.getSpeedQ && OUT.getDirection() == dir ) Если постусловия относятся к состоянию OUT-объекта, вызовите соответствующие операции с целью проверки этого состояния. Если в CUT-классв нет таких операций, определите их в тестовом классе как защищенные функции-члены. Примечание: если метод тестового случея предусматривеет выполнение операций, определенных в CUT- клвссе, позаботьтесь о том, чтобы эти тесты были включены в базовый набор. Язык OCL допускает использование в спецификации имен состояний в качестве атрибутов булевских вырвжвний [WK99], Класс не нуждается в определении опера- ции, которая явно возврещеет состояние экземпляра. Мы полвгеем, что в распоря- жении классов всегдв должно быть средство наблюдения за текущим состоянием объектов, использующвв имвне состояний, а не только диапазоны знвчений ат- рибутов - например, в классе PuckSupply былв определена операция ieEmpty(). Если CUT-клесс не в полной мерв поддерживает в своем интерфейсе операции, необхо- димые для тестирования предусловий и инвариантов на основе состояний, обретитесь к разработчикам с просьбой включить их в CUT-клесс, но нв включайте программные коды методов в клвсс Teeter. В конце концов, все клиенты (в данном случвв это класс Teeter] должны иметь возможность нвблюдвть за всем поведением объекта, если в спецификации имеется ссылке на тот или иной поведенческий аспект. Ра- зумеется, класс в своем общедоступном интерфейсе должен иметь все опврвции, необходимые клиенту для проверки предусловий любой общедоступной опврвции.
Глава 5. Осноаы тестирования классов Библиотечные методы OUT-объекта Тестирование классов выполняется путем создания экземпляров и проверки их поведения на наборе тестовых случаев. Выше мы назвали экземпляр класса, к ко- торому применяется тестовый случай, OUT-объектом, или тестируемым объектом. Основные требования, предъявляемые к OUT-объекту, предусматривают назначе- ние входным атрибутам конкретного тестового случая таких значений, чтобы удовлетворялись предусловия, обеспечивающие возможность прогона тестовых случаев. В классе Tester выполняются операции setOUT() и getOUT(), которые используются методами тестовых случаев для доступа к текущему OUT-объекту (см. рис. 5.14). Операция disposeOUT() предназначена для разрыва ассоциации OUT-объекта с его текущим экземпляром. Интерфейс тестирующего класса содержит набор операций для построения эк- земпляров CUT-класса. В число этих операций входит newCUT(Object), который представляет собой библиотечный метод, используемый для построения экземп- ляра CUT-класса, который является копией объекта, переданного ему в качестве аргумента — библиотечный метод, напоминающий конструктор копирования в C++. Конкретный класс Tester должен реализовать библиотечный метод, соответ- ствующий каждому конструктору, определенному в CUT-классе. Методы тесто- вых случаев используют эти библиотечные методы для построения OUT-объектов вместо конструкторов CUT-класса. Методы тестового случая применяют опера- цию getOUT() для доступа к текущему OUT-объекту. В случае класса VelocityTester определяется операция newCUT() для создания экземпляров класса Velocity, построенного конструктором по умолчанию, и операция setOUT(), кото- рая делает данный экземпляр текущим OUT-объектом. Кроме того, определяется также операция newCUT(s:Speed, d:Direction) для построения нового экземпляра с использованием конструктора Velocity::Velocity (s.’Speed, d:Direction). Методы те- стового случая должны использовать эти библиотечные методы для построения новых экземпляров CUT-классов в силу причин, которые станут понятными пос- ле рассмотрения иерархии классов в главе 7.10 Довольно-таки часто класс Tester используется для определения дополнитель- ных библиотечных методов в целях удобства работы с тестовыми случаями, для которых возникает необходимость создания OUT-объекта в некотором специаль- ном состоянии. Например, класс PuckSupplyTester может предложить операцию newPuckSupplyOfOne() для построения экземпляра класса PuckSupply (запас шайб), содержащего одну шайбу. Такие библиотечные методы должны быть обще- 10 Сделаем предварительные замечания. Для любого подкласса (CUT-класса), спроектированного в соответствии с принципом подстановки, тестовые случаи, предназначенные для проверки данного CUT-класса, все еше могут применяться к этому подклассу. Мы создадим тестовый класс для такого подкласса, который, в свою очередь, является подклассом тестового класса CUT-класса. В силу того, что в основе методов тестового случая лежат библиотечные методы, достаточно просто удалить эти методы тестового класса указанного выше подкласса, чтобы получить возможность создавать экземпляры подклассов. Как уже отмечалось в начале книги, объектно-ориентированные технологии упрощают тестирование, равно как и разработку.
Глава 5. Основы тестирования классов доступными, поскольку они весьма полезны, когда возникает необходимость в экземплярах CUT-класса для отладки другого класса. Методы тестовых случаев для этого другого класса могут использовать экземпляры этого класса Tester как помощь при создании экземпляров в требуемых состояниях. Однако, при реали- зации подобных методов необходимо следить за тем, чтобы были использованы другие библиотечные методы класса Tester, но не конструкторы тестируемого класса. Память для размещения тестируемых объектов должна быть выделена из кучи, поскольку совместное использование объекта всеми тестовыми случаями в общем случае невозможно. К тому же упрощается понимание кода тестового драйвера, который написан таким образом, что каждый метод тестового случая сначала со- здает свой собственный OUT-объект, после чего размещает его в памяти. Совмес- тное использование таких объектов методами тестовых случаев усиливает связ- ность. Старайтесь писать по возможности простые тестовые драйверы, даже за счет уменьшения быстродействия и/или за счет снижения эффективности ис- пользования пространства памяти. Одним из наиболее обременительных аспектов разработки тестовых драйверов является их тестирование и отладка. Драйвер тем лучше, чем проще его код. При использовании таких языков программирования, как C++, в котором уп- равление кучей ложится на плечи программиста, включите в обязанности каждого метода тестового случая удаление объектов, которые он распределяет. Метод disposeOUT() может удалять текущий OUT-объект. Базовое тестирование Методы тестовых случаев содержат программные коды установки OUT-объекта, который может потребовать пересылки последовательности запросов на модифи- кацию экземпляру CUT-класса. В процессе проверки постусловий методы тесто- вого случая используют операции средства доступа. Если конструктор, методы модификатора и методы средства доступа CUT-класса функционируют некоррек- тно, то и результаты, сообщенные тестировщиком, также будут некорректными. Первое, что должен сделать тестировщик - это проверить, правильно ли работают сами конструкторы и методы, подвергнув их проверке на тестовых случаях. Мы называем такие тестовые случаи базовым (или минимальным) тестовым набором.11 * Тщательное тестирование большинства основных операций, необходимых для проверки результатов тестирования, имеет решающее значение. Одно время мы разрабатывали компилятор, при тестирова- нии которого программы тестового набора всегда выполняли проверку на отказ каждого тестового случая — т.е. проверку по схеме настроить ввод тестового случая; выполнить ввод для тестового слу- чая; если какое-либо из условий не выполнено, уведомить об отказе. Компилятор мог успешно пройти через выполняемый тестовый набор, если он генерировал программные коды таким образом, что вычисления всех условных выражений давали значение true, в силу чего сообщения об ошибках отсут- ствовали. Этот факт несомненно следует считать слабой стороной такого метода тестирования. У нас возникла потребность в таком базовом тестовом наборе, который мог бы проверять правиль- ность вычислений условных выражений в операторах if.
Глава 5. Основы тестирования классов Базовый тестовый набор представляет собой набор тестовых случаев, которые тестируют операции CUT-класса, необходимые другим тестовым случаям для проверки их исходов. Такой набор предусматривает тестирование конструкторов и средств доступа. Велика вероятность того, что все тестовые случаи из базового тестового набора будут дублироваться в функциональном тестовом наборе. Мы предложили два основных подхода к базовому тестированию; в основе первого из них лежит спецификация, в основе второго — реализация: 1. Проверить, все ли конструкторы и средства доступа самодостаточны. Пост- роить тестовый случай для каждого конструктора и проверить правильность выбора значений атрибутов путем вызова средств доступа. 2. Проверить правильность использования переменных объекта конструкторами и средствами доступа. Это требует от тестировщика знания того, как реали- зованы атрибуты в CUT-классе. Его реализация основывается на свойствах видимости языков программирования, обеспечивающих классу-тестиров- щику доступ к реализации класса, который он тестирует. В число этих свойств входят дружественные функции в C++ и видимость пакетов в Java. Выбор соответствующего подхода должен учитывать, насколь тесно требуется связать тестовый класс с программным кодом класса, который он тестирует. Мы отыскали второй подход, позволяющий получить более достоверные результаты, хотя он и требует больших трудозатрат на программирование и тесно связывает про- граммные коды обоих этих классов — например, в C++ тестируемый класс должен объявить класс Tester как дружественный. В условиях второго подхода требуется меньше тестовых случаев в базовом наборе, недели в условиях первого подхода. ПРОВЕРКА УТВЕРЖДЕНИЙ В ТЕСТИРУЕМОМ КЛАССЕ В то время как основной механизм твстирования, предусматривающий прогон те- стовых случаев, реализует тестовый драйвер, ошибки могут возникать при включении проверок утверждений в программный код класса в процесса его разработки. В их число могут входить утверждения, предусматривающие проверки предусловий, постус- ловий и инвариантов. Дополнительно к инвариантам, описанным в классе, средство реализации может сформулироветь набор инвариантов, ориентированных на реали- зацию. Примером может послужить класс Sprite игры "Кирпичики". Дабы поддер- живать трабуемый уровень эффективности, каждый экземпляр содержит в своем локальном состоянии как рабочий прямоугольник (в виде совокупности угловой точки, ширины и высоты], так и координаты верхней левой и нижней правой точек, задающих рабочий прямоугольник. Избыточность сама по себе может служить источником ошибок, поскольку значания могут оказаться противоречивыми. Такая схема приво- дит к использованию инвариента класса нв уровне реелизеции, смысл которого в рассматривеемом случае заключается в том, что нижняя правая точка рабочего прямоугольника является нижнай правой точкой, сохраняемой в данном экзампляре. Класс SpriteTester не может содержать программных кодов, предназначенных для проверки подобного рода инвариантов уровня реализации до тех пор, пока он не получит доступа к реализации класса Sprite. Для упрощения тестирования средство раализации должно включать утверждение, предусматривающае проварку таких ин- вариантов уровня реализации соответствующих классов в каждой функции-члане, которая модифицирует рабочий прямоугольник экземпляра. Это приводит к упроще- нию процедуры отладки и тестирования, при этом не потребуется усиливать связи между классом-тестировщиком и вго CUT-классом.
Глааа 5. Основы тестирования классов СОВЕТ Вам сладуат реализовать в классе Tester защищенный метод для проаврки раздала постусловий. Одно и то же постусловие часто появляется в спецификациях более чем одной операции, объявленных для конкретного класса. Обращайтесь к этим защищенным методам, но нв включайте в каждый метод тестового случая одни и те жа программные коды проверки постусловий. Аналогично, дайтв определение библиотечного метода, возвращающего OUT-объект в состояние, необходимое для тестового случая. Нередко случается так, что сразу насколько тестовых случаев дают описания одних и тех же предусловий того или иного OUT-объекта, с тем чтобы иметь в своем распоряжении удобный метод построения экземпляров, благодаря чему количество программных кодов тестового драйвера уменьшается. Если методы тестового сценария используются с целью упрощения тестирования взаимодействий в класса, напишитв каждый метод тестового случая таким образом, чтобы он проверял состояние ввода этого тестового случая пврад тем, как гене- рировать события для OUT-объекта. Поскольку тестовые классы редко когда сами подвергаются тестированию (при помощи классов Tester), небольшая защитная программа может помочь а их отладке. Прогон тестовых наборов Абстрактный класс Tester включает в свои протоколы некоторые операции, позволяющие выполнять как все тестовые случаи, так и избранные тестовые набо- ры. Данные методы для этих операций реализовать совершенно не трудно. Каж- дый из них вызывает последовательность методов тестовых случаев. Позаботьтесь о том, чтобы базовый тестовый набор был выполнен раньше любого другого набо- ра подобного типа. Одна из возможных в таких случаях схем предусматривает прогон базового тестового набора при построении экземпляра конкретного тесто- вого класса, т.е. такой прогон становится частью его инициализации. ВОПРОС Можно ли разработать класс, упрощающий тестирование? Да, можно. Позаботьтесь о том, чтобы общедоступный интерфейс включал операции, которые обеспечивали бы возможность проверки клиентом всех условий из числа предусловий, постусловий и инвариантоа. Далее, сделайте так, чтобы текущее наблюдаемое состояние можно было наблюдать без необходимости со стороны клиента определять это состояние путем подбора соответстаующих текущих зна- чений атрибутов. Если в структура класса подобные методы не предусмотрены, предложите разработчикам включить их в интерфейс. Включение общедоступной операции в тот или иной класс с целью проверки его инвариантов полезно как для класса Tester, так и для разработчиков при выпол- нении отладки. Тем нв манее, соблюдайте осторожность при использовании этого программного кода для проверки постусловий методов тестового случая. Мы пред- почитаем кодировать независимый метод CUTInvarientHolds() в каждом реали- зуемом классе Tester. Если CUT-объект содержит статические функции-члены и/или элементы дан- ных, то класс Tester должен включать в себя программный модуль, который сле- дит за тем, чтобы программный код этого класса был подвергнут тестированию и работал корректно, или по меньшей мере напоминает о том, самому классу может
Глава 5. Основы тестирования классов потребоваться тестирование, прежде чем подвергнутся тестированию его экземп- ляры. Это отнюдь не критично, поскольку цель тестирования класса состоит в обнаружении ошибок, а не в диагностике источника этих ошибок. Тем не менее, подобное напоминание в какой-то степени может служить гарантией того, что будет разработан тестовый драйвер для тестирования этих статических функций-членов. СОВЕТ Убедитесь в том, что всв тестовые случаи выполнены аще раз после того, как отладочные программные коды будут удалены из программы класса. Время от времани разрвботчики включают программный код с целью облегчить отладку клвсса, напримар, проверку утверждений и операций, которые записывают данные трассировки в потоки. Многие поставщики программного обеспечения удаляют отладочный код непосредственно перед установкой программного продукта. (Напри- мар, для поддержки упомянутого подхода а C++ макрокоманда assert!) (библио- течный файл заголовков assert.h) осуществляет проверку утверждений только в тех случаях, когда не объявлен NDEBUG.) В некоторых случаях программа, содер- жащая отладочную информацию, можвт обладать поведением, отличающимся от поведения той жв программы, из которой отладочная информация удалена. Соот- ветственно, прослвдита за твм, чтобы прогон тестовых случаев выполнялся в режимах с поддержкой и без поддержки отладки. Отчеты о результатах тестирования Метод тестового случая определяет успех выполнения тестового случая. В на- шем проекте методы тестового случая сообщают результаты экземпляру тестового класса, который производит сбор статистических данных о результатах прогона тестового набора. Для каждого метода тестового набора полезно иметь в этом от- чете собственный раздел. Целесообразно воспользоваться строкой, обозначающей имя сценария или его назначение. Имейте в виду, что цель тестирования состоит не в том, чтобы отладить класс, но в том, чтобы убедиться, что он соответствует требованиям своей специфика- ции. Поскольку обычно в роли тестировщика класса выступает его разработчик, включение в драйвер программного кода, предпринимающего попытки диагности- рования проблем, связанных с CUT-классом, выглядят весьма заманчиво. Однако, дорогостоящие усилия, затрачиваемые на разработку диагностической программы, практически никогда себя не оправдывают. Для этих целей гораздо лучше подхо- дят символические отладчики и другие инструментальные средства. Разумеется, такая отладка может выполняться в контексте тестового драйвера. Примеры программных кодов тестового драйвера Мы проиллюстрируем разработку класса Tester на примере представительных фрагментов12 класса VelocityTester, реализованного на C++ и на Java. Свойства этих языков программирования и действующие в них ограничения приводят к двум различным проектам. План тестирования класса Velocity показан на рис. 5.16. Набор описаний тестовых случаев приводится на рис. 5.17. 12 Исходные коды этих программ отличаются изрядной длиной. Опущенные разделы кода соответству- ют шаблонам, представленным в данном примере.
Глава 5. Основы тестирования классов План тестирования компонентов Имя компонента Velocity Идентификационный номер СТР102895 | Разработчик (и): David Sykes Тестировщик (и): David Sykes Назначение данного компонента Этот класс играет центральную роль а реализации спрайтов, которые переме- щаются в игра «Кирпичики» или в любой другой аркадной игре. В общем случае целесообразнее прадстаалять все, что движется, на плоскости, представленной а айда свтки координат, принимающих целочисленные значения. Требования целенаправленной проаерки Поскольку этот компонент принадлежит к числу критически важных, проаеркв подлежит 100% аго программных кодов. Анализ рисков показал, что асе операции в интарфайсе будут, по-аидимому, востребованы другими компонентами системы и, следовательно, все они также должны тестироваться. Построение и сохранение тестовых наборов Тестовые наборы должны быть подготовлены в соответствии со стандартами проекта. Следовательно, тестовый драйвер должен принять форму класса VelocityTester и должен содержать операции, обеспечивающие прогон фун- кциональных и структурных тестовых случаев, а также тестовых случаев, про- веряющих взаимодействие и функционирование компонентов. Отчеты о результатах тестирования должны соответствовать проектным стан- дартам, которые зафиксированы а документации, сопровождающей абстракт- ный класс Teeter. Тестовые случен, ориентировенные не спецификацию Тестовые случаи для каждой операции обращаются к эквивалентным классам скорости (нулевой, медленный, умеренный и быстрый} и направления (еосток, сеееро-запад, зепад, юго-запад, юг и юго-еосток}. Используются парные сочетания. Тестовые случаи, ориентированные на реализацию Выполнить тестовые случаи, необходимые для покрытия всех программных кодов, которые на были задействованы при прогоне тестовых наборов, ориен- тированных на спецификацию. Тестовые случаи, предназначенные для тестирования взаимодействия и функционирования компонентов Выполнить тестовые случаи, осуществляющие тестирование взаимодействия операций reverse!), revereeXl) и revereeYl). Тестовые случаи, ориентированные не состояния Выполнить тестовые случаи, которые покрывают каждый переход на диаграмме состояний. Рисунок 5.1 Б. Покомпонентный план тестирования класса Velocity
Главв 5. Основы твстироввния клвссоа Описание Ввод Вывод Устоновка Событме(я) Результирующее состояние Исключи- тельные ситуации test default constructor none OUT = new Velocity; OUT.speed=0 and OUT.direction=0 none test constructor {spd = 6,12,1000; dir = 0..359} none OUT = new Velocityfspd, dir) OUT.speed = spd and OUT.direction = dir none test setDirection {spd = 1000; dir = 0..359} OUT:Veloc ity[speed=1000, direction=dir] OUT.setDi rection(dir+1) OUT.direction = dir and OUT.speed = spd none test setSpeed {spd = 0, 1,5, 10, 1000; dir = O, 30, 90, 135, 180, 182, 270, 335} OUT:Veloc ity[speed=100, direction=dir] OUT.set Speed(spd); OUT.direction = dir and OUT.speed = spd none test reverse() OUT: Velocity[speed = 10, direction = 0] OUT.reversef) OUT.speed = 10 and OUT.direction = 180 none OUT :Velocity[speed = 10, direction = 30] OUT.reverse() OUT.speed = 10 and OUT.direction = 210 none OUT :Velocity[speed = 10, direction = 90] OUT.reversef) OUT.speed = 10 and OUT.direction = 270 none OUT.Velocityfspeed = 10, direction = 135] OUT.reverse() OUT.speed = 10 and OUT.direction = 315 none OUT :Velocity[speed = 10, direction = 180] OUT.reverse() OUT.speed = 10 and OUT.direction = 0 none OUT :Velocity[speed = 10, direction = 182] OUT.reverse() OUT.speed = 10 and OUT.direction = 2 none OUT :Velocity[speed = 10, direction = 270] OUT.reverse() OUT.speed = 10 and OUT.direction = 90 none OUT :Velocity[speed = 10, direction = 335] OUT.reverse() OUT.speed = 10 and OUT.direction = 155 none reverseX() OUT :Velocity[speed = 10, direction = 0] OUT.reverse() OUT.speed = 10 and OUT.direction = 180 none OUT :Velocity[speed = 10, direction = 30] OUT.reversef) OUT.speed = 10 and OUT.direction = 150 none OUT:Velocity[speed = 10, direction = 90] OUT.reversef) OUT.speed = 10 and OUT.direction = 90 none OUT.Velocityfspeed = 10, direction = 135] OUT.reverse() OUT.speed = 10 and OUT.direction = 45 none OUT.Velocityfspeed = 10, direction = 180] OUT.reversef) OUT.speed = 10 and OUT.direction = 0 none OUT :Velocity[speed = 10, direction = 182] OUT.reverse() OUT.speed = 10 and OUT.direction = 358 none OUT:Velocity[speed = 10, direction = 270] OUT.reverse() OUT.speed = 10 and OUT.direction = 270 none OUT :Velocity[speed = 10, direction = 335] OUT.reverse() OUT.speed = 10 and OUT.direction = 205 none reverseY() Рисунок 5.17. Описания тестовых случаев для некоторых операций класса Velocity
Глава 5. Основы твстирования классов Некоторые тестовые случаи задаются в виде определенного сочетания значе- ний атрибутов из соответствующих диапазонов значений. Сначала приводится С++-код для классов Tester и VelocityTester, за которым следует код на Java. Для начала сделаем ряд замечаний, касающихся этих про- грамм: В версии C++ для генерации абстрактного класса Tester был применен шаб- лон, параметризованный CUT-классом. С использованием шаблона можно построить класс в корне иерархии тестовых классов для каждого CUT-клас- са. Таким образом, например, операции, подобные getOUTQ, возвращают указатель на экземпляр CUT-класса, а не указатель типа void * или указа- тель на некоторый абстрактный класс Object. В версии Java мы определяем класс Tester как абстрактный класс и приме- няем класс Object для представления класса OUT-объекта. Это требует, что- бы каждый метод тестового случая динамически преобразовывал ссылку на OUT-объект в ссылку на CUT-класс. Класс Tester в обоих реализациях обладает одними и теми же функцио- нальными средствами. В их число входят программные модули, которые подводят итоги и генерируют отчеты о результатах тестирования в систем- ный журнал. Подобная схема может быть существенно улучшена за счет построения специальной базы данных результатов тестирования и обеспече- ния более подробных отчетов. Обратите внимание на то, как библиотечные методы класса VelocityTester возвращают экземпляры класса Velocity. Тестировщик всегда должен объяв- лять такие библиотечные методы для возврата указателя или ссылки на CUT-класс. Базовый тестовый набор, реализованный в классе VelocityTester, является минимальным. Он просто проверяет корректность значений атрибутов для отдельного объекта, возвращаемых средством доступа. Более широкая сово- купность тестовых случаев, обеспечивающих проверку средств доступа от- носится к части функционального тестового набора. Метод CUTInvariantHold() класса VelocityTester использует библиотечные математические функции sin() и cos(). Мы полагаем, что эти функции возвращают правильные значения, и не подвергаем их проверке. В версии на C++ мы используем значение функции, вычисляющей арккосинус -1 при вы- числении значения "пи". В версии на Java применяется библиотека Math.PI. В целях экономии книжного пространства мы не приводим здесь все методы тестового случая. Метод тестового случая tc_Velocity() тестирует конструктор по умолчанию. Методы tcs_VelocitySpeedDirection() и tcs_setDirection() осуществляют прогон наборов тестовых случаев, описан- ных на рис. 5.17, для проверки нестандартного конструктора и операции setDirection().
Глава 5. Основы тестирования классов Код для класса Tester на C++. Компиляция этого кода выполнялась с использо- ванием Metrowerks Code Warrior Pro 5. ((include <fstream> ((include <iomanip> ((include <ctime> using namespace std; enum TestResult {Fail, TBD, Pass}; template<class CUT> class Tester { public: Tester<CUT>(string CUTname, string logFileName) _CUTname (CUTname) , _logStream (logFileName. c_str (J ) , _OUTPtr(0), _passTally (0) , _failTally(0), _TBDTally(0) { time_t sys time = time(0) ; _logStream « ctime(Ssystime) « endl; } virtual ~Tester<CUT> () { // Резюмировать результаты // тестирования в журнале _logStream « endl « "Summary of results:" « endl « "\t" « totalTallyO « " test cases run" « endl « fixed « showpoint « setprecision(2) « "\t" « setw(7) « "Pass:" « setw(5) « passTallyO « endl « "\t" « setw(7) « "Fail:" « setw(5) « failTallyO « endl « "\t" « setw(7) « "TBD « setw(5) « TBDTallyO « endl; _logStream.close(); ) virtual void runAHSuites () { runFunctionalSuite(); runStructuralSuite(); runlnteractionSuite(); } virtual void runFunctionalSuite() = 0; virtual void runStructuralSuite() = 0; virtual void runlnteractionSuite() = 0; int passTsllyO const { return _psssTally; } int failTallyO const { return _fsilTally; } int TBDTallyO const { return _TBDTally; } int totslTallyO const { return _passTally + _failTally + _TBDTally; } virtual CUT *getOUT0 { return _0UTPtr; } // Текущий OUT-объект virtual void disposeOUT() { // Завершить использование текущего // ОПТ-обчекта if ( I _OUTPtr ) { delete _OUTPtr; _OUTPtr = 0; } } virtual CUT *newCUT(const CUT (object) = 0; 8*
Глава 5. Осноаы тастироаания классов protected: virtual bool runBaselineSuite () - 0; virtual bool CUTinvariantHolds () = 0; void setOUT(CUT *outPtr) { _OUTPtr = outPtr; } // используется библиотечными методами void logTestCaseStart(string testID) { _logStream « "Start test case '' « testID « endl; } void logSubTestCaseStart(int caseNumber) { _logStream « "Start sub test case " « caseNumber « endl ; } void logTestCaseResult(TestResult result) ( _logStream « "RESULT: switch ( result ) ( case Fail: ++ _failTally; _logStream « "FAIL"; break; case TBD: ++ _TBDTally; _logStream « "To be determined"; break; case Pass: ++ _passTally; _logStream « "Pass"; break; default: _logStream « "BAD result (" « int(result) « ")" « endl; ) _logStream « endl; ) void logComment(string comment) { _logStream « "\t* " « comment « endl; } TestResult passOrFail(bool condition) { // Служебная программа для результата, который не может быть TBD. // Ояа также проверяет данный инвариант, if ( condition && CUTinvariantHolds() ) return Pass; else return Fail; ) private: string _CUTname; ofstream _logStream; CUT *_OUTPtr; int _passTally; int _failTally; int _TBDTally; // имя тестируемого класса // поток регистрационного журнала // указатель на текущий тестируемый объект /J количество тестовых случаев, успешно // выполненных на текущий момент // количество тестовых случаев, выполнение // которых закончилось неудачей // количество тестовых случаев, // предварительно успешно // выполненных на текущий момент
Главе 5. Основы твстироввния классов Программные коды класса VelocityTester в С++ // VelocityTester.h #include "Tester.h" finclude "Velocity.h" class VelocityTester public Tester<Velocity> { public: VelocityTester(string logFileName) Tester<Velocity>("Velocity", logFileName) { runBaselineSuite(); ) virtual void runFunctionalSuite() { tc_Velocity(); tcs_VelocitySpeedDirection(); tcs_setDirection(); ) virtual void runStructuralSuite () { ) virtual void runlnteractionSuite{) { } virtual Velocity *newCUT() { return new Velocity (); } virtual Velocity *newCUT(const Velocity Sv) ( return new Velocity(v); ) virtual Velocity *newCOT(const Speed speed, const Direction dir) ( return new Velocity(speed, dir); ) protected: virtual bool runBaselineSuite() ( // Проверить, что операции средства доступа вепротиворечивы logComment("Running baseline test suite."); Velocity v(1000, 321); if ( v.getSpeed() == 1000 && v.getDirection() == 321 && v.getSpeedXO == 777 && v. getSpeedY () == -629 ) ( logComment("Baseline suite passed"); return true; ) else ( logComment("Baseline suite FAILED"); return false; ) ) virtual bool CUTinvariantHolds() ( const Velocity &OUT = *getOUT(); const Direction direction = OUT. getDir ection (); const Speed speed = OUT.getSpeed(); const Speed speedX = OUT.getSpeedX(); const Speed speedY = OUT.getSpeedY(); static const double PI = 3.14159265; const double radians = 2.0 * PI * direction / 360.0; bool result = 0 <= direction && direction < 360 && speed >= 0 && speedX = int(cos(radians) * double(speed)) && speedY = int(sin(radians) * double(speed)) && (speedX*speedX + speedY*speedY) <= speed*speed;
Глава 5. Основы тестироввния классов if ( ! result ) { logComment("Invariant does not hold"); ) return result; ) void tc_Velocity() { // выполвить тестирование конструктора // по умолчанию logTestCaseStart("Velocity ()") ; setOOT(newCUT ()) ; Velocity SOOT = *getOOT() ; logTestCaseResult(passOrFail(OUT.getSpeed() == 0 SS OOT .getDirection () == 0) ) ; disposeOOT(); ) void tcs_VelocitySpeedDirection() { // выполнить тестирование Velocity(Speed, Direction) // Для этого потребуется выполнить прогон 360 тестовых случаев logTestCaseStart("Velocity(Speed, Direction)"); const Speed fixedSpeed = 1000; for ( Direction dir = 0 ; dir < 360 ; ++dir ) ( logSubTestCaseStart(dir) ; setOUT(newCUT(fixedSpeed, dir)); Velocity SOOT = *getOOT(); logTestCaseResult (passOrFail (OOT. getDirection () == dir SS OOT. getSpeed () = fixedSpeed) ) ; disposeOOT(); ) ) void tcs_setDirection() ( logTestCaseStart("setDirection"); const Speed fixedSpeed = 1000; setOOT(newCOT(fixedSpeed, 359)); // Любое звачение // переиенной dir ! = 0 Velocity SOOT = *getOOT(); for ( Direction dir = 0 ; dir < 360 ; ++dir ) ( logSubTestCaseStart(dir); OOT.setDirection(dir); logTestCaseResult (passOrFail (OOT. getDirection () = dir SS OOT. getSpeed () == fixedSpeed)); ) disposeOOT(); ) ) ; Главная программа создает экземпляр класса Tester и осуществляет прогон всех тестовых наборов. Результаты прогонов регистрируются в файле Velocity TestResult. txt. #include <iostream> using namespace std; // вводится пространство имев std finclude "VelocityTester.h"
Глава 5. Основы тестирования классов int main ( void ) ( VelocityTester vtCVelocityTestResults.txt"); vt.runAllSuites () ; return 0; } Код для класса Tester на Java. Мы приводим определение класса TestResult, кото- рый представляет три возможных исхода прогона тестовых случаев. import java.io.*; import java.util.*; /** Класс, который определяет три возможных исхода прогона тестового случая: Fail - неудачный исход TBD - неизвестный исход ("То be determined"), обычно в силу того, что полученный результат требует дальнейшего анализа или наблюдения Pass - успешный исход @см. Tester */ public class TestResult ( public TestResult(String value) { _value = value; } public String toStringO { return _value; } private String _value; static public final TestResult Fail = new TestResult("Fail") ; static public final TestResult TBD = new TestResult("TBD"); static public final TestResult Pass = new TestResult("Pass") ; } /** Абстрактный класс, который представляет тестировщик классов. В обяванности тестировщика класса С входит: 1. прогон тестовых наборов, 2. построение эквемпляров класса, который ов тестирует, 3. регистрация результатов тестирования */ abstract class Tester { /** Построить новый экземпляр. @param CUTname имя тестируемого класса @param logFileName имя файла, в котором регистрируются результаты */ public Tester(String CUTname, String logFileName) { _CUTname = CUTname; try ( log = new FileWriter(logFileName); } catch (lOException e) ( System.err.printin("Could not open file " + logFileName); } _OUT = null; _passTally = 0; _failTally = 0; _TBDTally = 0;
Глава 5. Основы твстироввния клвссов try { String line = new Date (). toString () + '\n ’ ; _log.write(line); ) catch (lOException e) { System.err.printin("Error writing to log file"); e.printstackTrace() ; ) ) public void dispose)) ( // Занести результаты в журнал try { int total — totalTally)); _log.write("\n"); _log.write("Summary of results:\n"); _log.write("\t" + total + " test cases run\n"); _log.write("\t" + "Pass:" + " " + passTallyf) + "\n"); _log.write("\t" + "Fail:" + " " + failTally() + "\n"); _log.write ("\t" + "TBD :" + ' " + TBDTallyO + "\n"); _log.close() ; ) catch (lOException e) ( System.err.printin("Error writing to log file"); e.printstackTrace(); ) ) public abstract Object newCUT(Object object); //скопировать объект public void runAUSuites () { runFunctionalSuite(); runStructuralSuite(); runlnteractionSuite(); public public public abstract void abstract void abstract void runFunctionalSuite () ; runStructuralSuite!) ; runlnteractionSuite () ; public int passTallyf) public int failTallyf) public int TBDTallyO public int totalTally() return _passTally + ) { return { return { return { _failTally _passTally; _f ailTally; _TBDTally; ) + _TBDTally public public Object getOUTO void disposeOUT0 { return { _OUT _OUT; ) = null; ) protected abstract boolean runBaselineSuite () ; protected abstract boolean CUTinvariantHolds () ; protected void setOOT(Object outPtr) { _00T = outPtr; ) protected void logTestCaseStart(String testID) ( try ( _log.write("Start test case " + testID + "\n"); _log.flush(); ) catch (lOException e) { System.err.printin("Error writing to log file"); e.printStackTrace() ; ) )
Глава 5. Основы твстироввния классов protected void logSubTestCaseStart(int caseNumber) { try { _log.write("Start sub test case " + caseNumber + "\n"); log.flush() ; ) catch (lOException e) { System.err.printin("Error writing to log file"); e.printStackTrace(); } } protected void logTestCaseResult{TestResult result} { if ( result == TestResult.Fail ) { ++ _failTally; try { _log.write("\tOUT: " + getOUT (} . toStringO + "\n"); _log.flush(); } catch (lOException e) { System.err.printin("Error writing to log file"); e.printStackTrace(); } } else if ( result == TestResult.TBD ) { ++ __TBDTally; } else if ( result == TestResult.Pass ) { ++ _passTally; } try { _log.write("RESULT: " + result.toString() + "\n"); _log.flush() ; } catch (lOException e) { System.err.printin("Error writing to log file"); e.printStackTrace() ; } } protected void logComment(String comment) { try { _log.write(”\t* " + comment + "\n"); _log.flush(); } catch (lOException e) { System.err.printin("Error writing to log file"); e.printStackTrace() ; } } protected TestResult passOrFail(boolean condition) { //Служебная программа для результата, который не может быть //Этот фрагмент программы проверяет также инвариант if ( condition && CUTinvariantHolds(} ) return TestResult.Pass; else return TestResult.Fail; TBD
Главе 5. Основы тестирования классов private String _CUTname; // имя тестируемого класса private Filewriter _log; // поток регистрационного журнала private Object _OUt ; // указатель на текущий тестируемый // объект private int _passTally; // количество тестовых случаев, // успешно выполненных на текущий // момент private int _failTally; // количество тестовых случаев, // выполнение которых завершилось // неудачей private int _TBDTally; // количество тестовых случаев, / / предварительно успешно ) ; // выполненных на текущий момент Java-код для класса VelocityTester. Java code for the VelocityTester class. //import java.util.*; import Tester; import Velocity; / ★ ★ Класс, предназначенный для тестирования класса Velocity. */ class VelocityTester extends Tester { public static void main (String args[J) { VelocityTester vt = new VelocityTesterCVelTest-Java.txt"); vt. runAHSui tes () ; vt.dispose() ; ) public VelocityTester(String logFileName) ( super("Velocity", logFileName); runBaselineSuite() ; ) public void runFunctionalSuite() ( tc_Velocity(); tcs_VelocitySpeedDirection(); tcs_setDirection(); ) public void runStructuralSuite() ( ) public void runlnteractionSuite() ( ) // Библиотечные методы построения экземпляров CUT-класса public Object newCUT(Object object) ( Velocity v = (Velocity)object; return new Velocity(v.getSpeed() , v. getDirection ()) ; ) public Velocity newCUT () ( return new Velocity(); ) public Velocity newCUT(int speed, int dir) ( return new Velocity(speed, dir);
Главв 5. Основы тастироввния клвссов protected boolean runBaselineSuite() { // Проверить, что операции средства доступа непротиворечивы logComment("Running baseline test suite.”); Velocity v = new Velocity(1000, 321); if ( v.getSpeed() == 1000 44 v.getDirection () == 321 44 v.getSpeedX() == 777 44 v.getSpeedY() == -629 ) { logComment("Baseline suite passed"); return true; ) else ( logComment("Baseline suite FAILED"); return false; ) protected boolean CUTinvariantHolds() ( Velocity OUT = (Velocity) (getOUT ()) ; int direction = OUT.getDirection(); int speed = OUT.getSpeed(); int speedX - OUT.getSpeedX(); int speedY = OUT.getSpeedY(); final double radians = Math.toRadians(direction); if ( direction > 90 ) ( double dx = Math.cos(radians) * (double)(speed); dx = Math.floor(dx); int expectedSpeedX - (int)dx; int expectedSpeedY = (int)Math.floor(Math.sin(radians) * (double)(speed)); boolean rest = (speedX*speedX + speedY*speedY) <= speed*speed; rest = rest; } boolean result - 0 <- direction 44 direction < 360 44 speed >= 0 44 speedX == (int) (Math. cos (radians) * (double) (speed)) 44 speedY == (int)(Math.sin(radians) * (double)(speed)) 44 (speedX*speedX + speedY*speedY) <= speed*speed; if ( ! result ) ( logComment("Invariant does not hold"); ) return result; ) protected void tc_setDirection001() { logТеstCaseStart("setDirectionOOl"); setOUT (newCUT (1000 , 0) ) ; Velocity OUT = (Velocity) (getOUT ()) ; OUT.setDirection(01) ; logTestCaseResult(passOrFail(OUT.getDirection() disposeOUT () ; 01)) ;
Глааа 5. Основы тестирования классоа void tc Velocity() { // выполнить тестирование конструктора // по умолчанию logTestCaseStart("Velocity()"); setOUT(newCUT()); Velocity OUT = (Velocity) getOUT () ; logTestCaseResult(passOrFail(OUT.getSpeed() == 0 && OUT .getDirection () == 0)); disposeOUT(); ) void tcs_VelocitySpeedDirection() { // Выполнить тестирование Velocity(Speed, Direction) logTestCaseStart("Velocity(Speed, Direction)"); final int speedvalue[] = { 6, 12, 1000 }; for ( int i = 0 ; i < 3 ; ++i ) { int speed = speedvalue[i]; for ( int dir = 0 ; dir < 360 ; ++dir ) { logSubTestCaseStart(dir); setOUT(newCUT(speed, dir)); Velocity OUT = (Velocity)getOUT(); logTestCaseResult(passOrFail(OUT.getDirection() = dir && OUT.getSpeed() == speed)); disposeOUT(); } ) ) void tcs_setDirection() { logTestCaseStart ("setDirection") ; final int fixedSpeed = 1000; setOUT(newCUT(fixedSpeed, 359)); // Любое Значение переменной // dir != 0 Velocity OUT = (Velocity) getOUT () ; for ( int dir = 0 ; dir < 360 ; ++dir ) { logSubTestCaseStart(dir); OUT.setDirection(dir); logTestCaseResult(passOrFail(OUT.getDirection() = dir && OUT. getSpeed() ~ fixedSpeed) ) ; ) disposeOUT(); ) Резюме В традиционном процессе тестирование классов соответствует тестированию программных модулей. Тестирование классов в режиме прогона тестовых случаев требует идентификации тестовых случаев, разработки драйвера тестирования, ко- торый применяет тестовые случаи к экземплярам CUT-класса, и выполнения тес-
Глааа 5. Основы тестирования классов тового драйвера. До сих пор мы описывали тестирование сравнительно простых классов — тех из них, экземпляры которых не вступают в сложные взаимодей- ствия с другими экземплярами. Необходимость построения тестовых классов определяется в результате анали- за спецификации класса и его реализации. Мы показали, как определяются требо- вания к тестовому классу за счет изучения предусловий и постусловий, а также диаграмм переходов. Добавление тестовых случаев, проверяющих взаимодей- ствия, увеличивает покрытие программного кода. Мы предложили структуру тестового драйвера, предусматривающую реализа- цию тестового класса для каждого тестируемого класса. Мы дали подробное опи- сание проектного решения, основанного на использовании классов Tester, кото- рые успешно применялись ранее. В числе преимуществ предложенного решения следует отметить прозрачную организацию использования абстрактного класса Tester, отображающую поведение и код, общий для всех тестовых драйверов клас- сов, и поддержку различных исполнителей, занятых тестированием и разработ- кой. В главе 7 будет показано, что применение тестовых классов приносит допол- нительные выгоды в контексте тестирования классов, связанных отношением наследования. Упражнения 5-1 Сформулируйте тестовые требования к конструкторам и операторам reverse() для класса Velocity (см. рис. 5.3). Рассмотрите, какие при этом возникают различия в требованиях в подходах контрактного и защитного программирования. Постройте тестовые случаи для проверки выполнения требований, которые вы сформулировали. 5-2 Выполните то же самое для имеющегося у вас элементарного класса. 5-3 Напишите тестовый драйвер для прогона тестовых случаев, построенных в рамках одного из предыдущих упражнений. Если его реализация выполнена на C++ или Java, можно начать с абстрактных классов Tester, описание которых дано в конце данной главы. 5-6 Составьте спецификацию абстрактного класса Tester, которая может оказаться полезной в вашей организации. 5-7 Рассмотрите дилемму базового тестирования. В рамках подхода, использующего спецификацию, класс Tester должен выносить оценку только на основе очевидного соответствия всех атрибутов объекта, когда он находится в некотором заданном состоянии. С другой стороны, подход, использующий реализацию, жестко сопос- тавляет программные коды класса Tester и CUT-класса, так что реализация класса Tester не может быть завершена до того, как будет завершена большая часть CUT- класса. При каких обстоятельствах вы будете поддерживать тестирование, основан- ное только на спецификации? При каких обстоятельствах вы будете настаивать на использовании обоих подходов?
Глава Тестирование взаимодействия и функционирования компонентов ► Вы хотите получить представление о рвзличных типех взаимодействия? См. рвздел «Взаимодействие объектов» ► У еес есть время только на то, чтобы выполнить прогон лишь тестовых случвев, которые приходят вем в голову? См. раздел «Выбор тестовых случаев» ► Хотите еще рез воспользоваться уже выполненной резрвботкой вашего тестового программного обеспечения? См. рездел «Тестовые швблоны» ► Хотите знеть, квк тестироветь прогреммы, которые генерируют исключительные ситуеции? См. рездел «Тестирование исключительных ситувций» Объектно-ориентированная программа содержит совокупность объектов, взаи- модействие которых приводит к решению конкретной проблемы. Способы взаи- модействия этих объектов определяют то, что делает программа и, соответствен- но, правильность выполнения программы. Например, в заслуживающем доверия экземпляре примитивного класса ошибок может и не быть, однако если услуги этого экземпляра не используются должным образом другими компонентами про- граммы, то сама эта программа содержит ошибки. Таким образом, для правильно- сти программы весьма критично корректное сотрудничество, или взаимодействие, объектов. У большинства классов имеются партнеры по сотрудничеству, т.е. методы та- кого класса взаимодействуют с экземплярами других классов. В главе 5 мы рас- сматривали вопросы выявления ошибок в реализации индивидуального класса, который не вступает в подобного рода взаимодействия. В главе 7 мы будем рас- сматривать взаимодействия определения подкласса с определением его суперклас- 238
Глааа 6. Тестирование взаимодействия и функционирования компонентов са. В этой главе область наших исследований расширяется за счет обсуждения те- стирования классов, которые взаимодействуют с другими классами. Взаимодей- ствия компонентов, являющиеся целью наших исследований, представляют собой взаимодействия объектов, происходящие во время рабочего цикла, например, когда один объект передается другому объекту в качестве параметра, или когда один объект поддерживает ссылку на другой объект как некоторую часть своего состояния. Взаимодействия всегда предполагают однонаправленный обмен сооб- щениями. Но некоторые взаимодействия предусматривают двунаправленный об- мен сообщениями между объектами. В этой главе мы полагаем, что взаимодей- ствия образуют последовательности. В главе 8 мы рассмотрим более сложные примеры взаимодействия между распределенными объектами, которые предпола- гают параллельные взаимодействия. Основное назначение тестирования взаимодействий состоит в том, чтобы убе- диться, что происходит правильный обмен сообщений между объектами, классы которых уже прошли тестирование в автономном режиме. Тестирование взаимо- действий может быть выполнено над взаимодействующими объектами, вложен- ными в прикладную программу, или на примере взаимодействующих объектов в среде, предоставленной для этой цели специальным средством отладки, подобным классу Tester. В этой главе обсуждаются оба подхода. Прежде всего, подробно опишем, что собой представляют взаимодействия объектов и как взаимодействия идентифицируются в интерфейсе конкретного класса. Затем мы рассмотрим тестирование взаимодействий вне контекста некото- рой конкретной прикладной программы. И, наконец, мы проанализируем некото- рые из трудных для решения проблем, которые возникают при тестировании вза- имодействий в контексте конкретной прикладной программы, и подходы к их решению. Взаимодействия объектов Взаимодействие объектов представляет собой просто запрос одного объекта (отправителя) на выполнение другим объектом (получателем) одной из операций получателя и всех видов обработки, необходимых для завершения этого запроса.' В большинстве объектно-ориентированных языков программирования это охва- тывает большую часть возможностей программы. В их число входит обмен сооб- щениями между объектом и его компонентами и между объектом и другими объектами, с которыми он поддерживает связи. Мы полагаем, что этими другими объектами являются экземпляры классов, которые уже были подвергнуты тестиро- ванию в автономном режиме в объеме, необходимом для завершения его реализа- ции. Мы полагаем, что определение интерфейс класса дано с использованием только операций, но отнюдь не данных. Если данные доступны для взаимодействующих модулей, то к тестированию такого досту- па следует подходить так, как если бы существовали операции set и get, обеспечивающие установку и получение значений этих данных.
Глава 6. Тастированив взаимодействия и функционирования компонентов ЧАСТИЧНОЕ ТЕСТИРОВАНИЕ КЛАССОВ В рамках итврвтивного, инкрвмвнтвльного подходе рвзработкв классе нврвдко вы- полняется по стадиям. Даются опраделения и/или рввлизуются только тв функци- онвльныв средства, которые удовлетворяют требованиям текущего комплектующего модуля. Отношения между классами часто быввют твкими, что невозможно предста- вить рвзработку конкретного клвсса а виде твкой последовательности действий, что все другие классы, с которыми вму необходимо взвимодвйствовать, были полностью рвзрвботвны и протестированы. Чвм нижв уровень, т.а., чвм примитивнее рввлизация, твм больше вероятность того, что клвссы будут полностью отлвжвны к определенному моменту времени и могут быть подвергнуты твстироввнию как звкончвнный про- грвммный модуль. В силу этого обстоятельстве другие клвссы разрабатываются и тестируются в режиме приращений. Твстироввнив классов производится в той стапвни, в какой они разработаны. Не- ращиввйтв возможности тестовых клвссов так жв, квк вы разрабатываете промыш- ленное прогрвммноа обеспечение. Выполните идвнтификвцию твх тестовых случаев, которые сможете идентифицировать, в затем рввлиэуйтв класс Tester, способный рввлизовать зти тестовые случаи. Ведите регистрационные записи, соблюдая согла- шения, квсвющився имвноаания тестовых случаев, или другие виды документации о происхождении квждого тестового случая, с таким расчетом, чтобы нв следующем этапе твстироввния появилвсь возможность сравнить происшедшие изменения в спецификации и реализации тестируемых классов и их классов Tester. Поскольку многочисленные взаимодействия объекта могут иметь место во вре- мя вызова любого отдельного метода на принимающий объект, мы намерены ис- следовать, какое влияние оказывают эти взаимодействия на внутреннее состояние принимающего объекта и всех объектов, с которыми он поддерживает связь. Та- кие воздействия принимают значения в диапазоне от «никаких изменений» до изменения значений некоторых атрибутов одного или большего числа объектов, осуществляющих изменения состояний одного или большего числа объектов, включая создание новых объектов и удаление существующих объектов. В ситуациях, когда в качестве основы тестирования взаимодействий объектов выбраны только спецификации общедоступных операций, тестирование намного проще, чем когда такой основой служит реализация. Мы ограничимся тестирова- нием взаимодействия только ассоциированных равноправных объектов и выбо- ром подхода общедоступного интерфейса. Такой подход вполне оправдан, по- скольку мы полагаем, что ассоциированные классы уже успешно прошли адекватное тестирование. Тем не менее, выбор такого подхода отнюдь не означает, что не нужно возвращаться к спецификациям, дабы убедиться в том, что тот или иной метод выполнил все необходимые вычисления. Это обусловливает необхо- димость проверки значений атрибутов внутреннего состояния получателя, в том числе любых агрегированных атрибутов, т.е. атрибутов, которые сами являются объектами. Основное внимание уделяется отбору тестов на основе спецификации каждой операции из общедоступного интерфейса класса.
Главв 6. Тестирование взвимодействия и функционирования компонентов Идентификация взаимодействий Взаимодействия неявно предполагаются в спецификации класса, в которой ус- тановлены ссылки на другие объекты. В главе 5 рассматривалось тестирование примитивных классов. Примитивный класс может порождать экземпляры, и эти экземпляры можно использовать без необходимости создания экземпляров каких- либо других классов, в том числе и самого этого примитивного класса. Такие объекты представляют собой простейшие компоненты системы и, несомненно, играют важную роль при выполнении любой программы. Тем не менее, в объект- но-ориентированной программе существует сравнительно небольшое количество примитивных классов, которые реалистично моделируют объекты задачи и все отношения между этими объектами. Обычным явлением для хорошо спроектиро- ванных объектно-ориентированных программ стало использование непримитивных классов; в этих программах им отводится главенствующая роль. Непримитивные классы поддерживают, а, возможно, даже требуют, использо- вания других объектов при выполнении некоторых или даже всех своих опера- ций. Выявите классы таких типов объектов, используя для этого отношения ассо- циации (в том числе отношений агрегирования и композиции), представленные на диаграмме класса. Ассоциации такого рода преобразуются в интерфейсы клас- са, а тот или иной класс2 взаимодействуют с другими классами посредством одно- го или нескольких следующих способов: 1. Общедоступная операция именует один или большее число классов как тип формального параметра. Сообщение устанавливает ассоциацию между по- лучателем и параметром, которая позволяет получателю взаимодействовать с этим параметрическим объектом. Операции attach() и detach() класса Timer, показанные на рис. 6.1, служат иллюстрацией такого вида отноше- ний. Экземпляр класса Timer может получить запрос на присоединение эк- земпляра класса TimeObserver. Метод notify() класса Timer посылает сообще- ние присоединенным экземплярам класса TimeObserver с требованием вызвать метод; в рассматриваемом случае это метод thick(). В этом примере получатель сохраняет ассоциацию как часть своего состояния и выполняет пересылку этих других объектов в рамках последующих операций. Другой сценарий заключается в том, что получатель выполняет пересылку парамет- ра, прямо или косвенно, как часть процесса обработки сообщения. 2. Общедоступная операция присваивает имена одному или большему количе- ству классов как тип возвращаемого значения. Операция position() класса Sprite, показанная на рис. 6.1, служит примером такого вида взаимодей- 2 Правильный метод формулирования этого понятия должен учитывать, что экземпляр непримитивного класса взаимодействует одним или большим числом экземпляров других классов. Поскольку специфи- кация и реализация класса определяют полное поведение любого экземпляра, мы воспользуемся бо- лее распространенным выражением этих отношений через классы. Тем не менее, имейте в виду, что взаимодействие есть отношение объектов, не отношение классов.
Глава 6. Тестироеание взаимодействия и функционироаания компонентов ствий. На класс описаний может быть возложена задача создания возвраща- емого объекта, либо он может возвращать модифицированный параметр. В такой среде как C++, в которой выделение памяти программируется явным образом, в спецификации должно быть четко определено, берет ли получа- тель на себя ответственность по выделению памяти под возвращаемый объект или делегирует эту обязанность отправителю. Методы класса Tester должны наблюдать за тем, как выполняются эти обязанности. 3. Метод одного класса создает экземпляр другого класса как часть своей реа- лизации. Как показано на рис. 6.1, в классе MovableSprite имеется метод обработки соударения с другим спрайтом. Программный код этого метода предусматривает создание некоторого числа экземпляров класса CPoint и других классов с целью их использования в качестве промежуточных объек- тов для определения, что происходит во время конкретного соударения. Объектам, подобным PlayField, с которым MovableSprite имеет равноправ- ные отношения, ничего не разрешается знать об этих других объектах. На- поминаем, что мы не будем выполнять никакого анализа объектов, находя- щихся в нижней части иерархии. Тем не менее, при выполнении тестов может произойти отказ в экземпляре некоторого класса С на уровне по- добъекта, например, в экземпляре класса CPoint. Подтверждение правиль- ности результатов этого теста потребует проверки состояния класса С. Рисунок В.1. Взаимодействие параметров
Глава 6. Тестирование взаимодействия и функционирования компонентов 4. Метод одного класса ссылается на глобальный экземпляр некоторого другого класса. Разумеется, принципы хорошего тона в проектировании рекоменду- ют минимальное использование глобальных объектов. Если реализация ка- кого-либо класса ссылается на некоторый глобальный объект, рассматривай- те его как неявный параметр в методах, которые на него ссылаются. Эти взаимодействия могут быть реализованы в языках программирования раз- личными способами. К взаимодействующим методам можно обращаться напря- мую, например, используя имя переменной, либо через указатель или ссылку. Когда используется указатель или ссылка, динамический тип объекта может отли- чаться от статического типа, связанного с указателем или ссылкой. Другими сло- вами, указатели и ссылки являются полиморфными структурами, следовательно, они привязаны к экземпляру любого количества классов. В контексте рис. 6.1, реализация класса Timer в C++ с высокой степенью вероятности хранит указатели на экземпляры любых подклассов класса TimerObserver. Реализация в языке Java хранит ссылки на экземпляры любого класса, который является подклассом класса TimerObserver или реализует интерфейс TimerObserver. Полиморфизм увеличива- ет число видов объектов, которые могут взаимодействовать с тестируемым клас- сом. Предусловия и постусловия для операций в общедоступном интерфейсе класса обычно содержат ссылки на состояния и/или на конкретные значения атрибутов любых взаимодействующих объектов. Мы можем разбить непримитивные классы на категории на основе свойств взаимодействий — т.е. на базе интенсивности вза- имодействия с другими экземплярами. Некоторые классы поддерживают ассоциа- ции с экземплярами других классов, но фактически никогда не взаимодействуют с этими экземплярами. Мы называем такие классы коллекциями (collections). Мы будем называть класс с более высоким уровнем взаимодействия сотрудничающими классами (collaborating classes). Намного меньшее количество классов будет «соби- рать» другие объекты в коллекции. Далее мы опишем, как выполнять тестирова- ния таких коллекций объектов, и обсудим тестирование сотрудничающих клас- сов. Коллекции Некоторые классы используют в своих спецификациях объекты, но фактичес- ки не взаимодействуют ни с одним из них — т.е. они никогда не обращаются к этим объектам с запросами оказать им ту или иную услугу. Вместо этого они вы- полняют одно или несколько следующих действий: сохраняют ссылки (или указатели) на такие объекты, которые обычно пред- ставляют отношения один-ко-многим между объектами программы создают экземпляры этих объектов удаляют экземпляры этих объектов.
Глава 6. Тестированив взаимодействия и функционирования компонентов Классы коллекций (или просто коллекции) могут быть описаны спецификаци- ей, которая ссыпается на другие объекты, но которая не ссылается на значения, вычисленные на основе состояния или значения атрибута этих объектов. В рамках проекта «Кирпичики» класс PuckSupply (см. рис. 6.2) представляет собой коллек- цию. Объект PuckSupply, как часть его структуры, порождает соответствующее ко- личество экземпляров класса Puck и по запросу возвращает указатель на один из этих экземпляров. Экземпляр PuckSupply никогда не использует операций, ассо- циированных с Puck; исключение составляют конструкторы. В противополож- ность этому, например, класс Timer, сохраняет ссылки (указатели) на средства ре- ализации интерфейса TimerObserver, такие как Puck, в случае их присоединения. Класс Timer посылает запрос tick() каждому присоединенному наблюдателю вся- кий раз, когда происходит событие Timer во время выполнения. Библиотеки классов, которые прилагаются к компиляторам и различным сре- дам разработки, обычно содержат наборы контейнерных классов (или просто кон- тейнеров). В C++ имеется библиотека шаблонов STL (Standard Template Library), а в Java существует набор контейнеров. Классы из таких библиотек содержат спис- ки, стеки, очереди и карты отображения (словари). Эти коллекции хранят объек- ты, которые им были переданы, и возвращают их в заданном порядке либо осуще- ствляют их поиск на основе специальных критериев. Взаимодействующие классы Непримитивные классы, которые не являются коллекциями, суть взаимодей- ствующие классы. Такие классы используют другие объекты в одной или большем числе операций и как часть своей реализации. Когда постусловие операции в ин- терфейсе класса ссылается на постусловие экземпляра объекта и/или указывает на // PuckSupply.h /* Запас шайб представляет собой набор шайб, которые можно брать по одной за раз. Эти шайбы были построены запасом с использованием конструктора по умолчанию. Когда запас шайб удаляется, он удаляется вместе со всеми содержащимися в ней шайбами. Со временем может быть добавлен способ увеличения запаса шайб. */ class PuckSupply { public: PuckSupply(); -PuckSupply () ; Puck* get(); int count () const; private: int _count; Puck* _store[N]; } ; Рисунок 6.2. Класс PuckSupply
Глава 6. Твстированив взаимодействия и функционирования компонентов то, что некоторые атрибуты этого объекта используются или модифицируются, то этот класс принадлежит к категории взаимодействующих классов. Класс BrickPile в игре «Кирпичики» (см. рис. 6.3) является взаимодействую- щим классом. Этот класс моделирует в игре прямоугольные конструкции из кир- пичей и выполняет обязанности по выявлению, но никак не обработки, соударе- ний шайбы с кирпичом. Он служит контейнером для кирпичей, но в то же время взаимодействует с игровым полем, с подсказкой (в которой фиксируются все из- менения в куче кирпичей, благодаря чему такая куча эффективно отображается на экране) и со спрайтами — в частности, с шайбой, которая ударяет о кучу кирпи- чей. После построения куча кирпичей помещается в конкретную точку некоторо- го игрового поля. Класс BrickPile взаимодействует со следующими классами: Playfield. Куча кирпичей занимает некоторую часть игрового поля. Hint. Куча кирпичей фиксирует поврежденные кирпичи в подсказке. CPoint. Местонахождение кучи кирпичей в игровом поле определяется точ- кой, которая представляет верхний левый угол этой кучи кирпичей. Brick. Куча кирпичей создает кирпичи как часть своей собственной конст- рукции и следит за тем, какие кирпичи повреждены, а какие целы. MovableSprite. Куча кирпичей распознает соударения кирпичей с шайбой, которая представляет собой некоторый вид перемещающегося спрайта. class PlayField; class Hint; finclude "Brick.h" const int ROWS = 6; const int COLS — 10; class BrickPile { public: BrickPile(PlayField* playField_p, const CPointS position, Hint& hint); ~BrickPile () ; void broken(Brick* brick_p) ; int unbrokenCount() const; Brick* overlaps(Sprite* sprite_p); protected: static const BrickColor evenBrickColor; static const BrickColor oddBrickColor; PlayField* _playField_p; Brick* _pile[ROWS][COLS]; int _count; CPoint _origin; CRect _boundingRect; ) РисуНОК Б.З. Заголовочный файл для класса BrickPile
Глввв 6. Твстироввнив взаимодействия и функционирования компонентов Тестирование базовых взаимодействий двух объектов — это только начало. Ко- личество потенциальных взаимодействующих объектов очень быстро становится невероятно большим. Довольно часто наиболее серьезные ошибки возникают не столько в результате взаимодействия двух объектов, сколько в результате взаимо- действия некоторого полного множества объектов. Объект BrickPile может рабо- тать безукоризненно при тестировании с объектом Playfield, но отказ может воз- никнуть, когда BrickPile вступит во взаимодействие с объектом Hint, чтобы зафиксировать факт разрушения кирпича. При этом возникает вопрос: как тести- ровать каждое взаимодействие — как индивидуальное или в рамках некоторой группы взаимодействий? Правильный выбор размера «куска» для тестирования определяется тремя сле- дующими факторами: 1. Мы проводим различие между объектами, которые вступили в отношение композиции с тестируемым объектом, и теми, которые просто ассоциирова- ны с этим объектом. В процессе тестирования классов проверяется взаимо- действие между составным объектом с составляющими его атрибутами. Вза- имодействия некоторого объекта с ассоциированными с ним объектами подвергается тестированию по мере интегрирования очередных слоев агре- гации. 2. Число слоев агрегации, выполненной между сеансами тестирования взаи- модействий, находится в тесной зависимости от того, насколько хорошо просматриваются дефекты. Если для тестирования выбран слишком боль- шой кусок, то в нем могут оказаться неправильные промежуточные резуль- таты, которые, тем не менее, никогда не будут зафиксированы на уровне проверки эффективности тестирования. Это может не быть проблемой выб- ранных параметров тестирования. В то же время небольшие изменения тес- товых параметров могут привести к отказу. Большее число степеней агреги- рования означает большее количество возможных параметров тестирования. 3. Чем выше сложность объектов, тем меньше вероятность того, что они будут интегрированы еще до проведения этапа тестирования. Об этой сложности можно судить по числу параметров каждого метода, по числу самих методов и числу атрибутов состояния каждого объекта. Как и в случае степеней аг- регирования, попытка провести тестирование слишком крупного программ- ного куска приводит к тому, что некоторые виды ошибок при тестировании выявляться не будут. Определение взаимодействий В обсуждениях, проводимых в следующем разделе и касающихся тестирования взаимодействий, мы полагаем, что операции, определяемые конкретным классом, заданы посредством предусловий, постусловий и инвариантов. Мы будем пользо- ваться языком OCL (Object Constraint Language — язык объектных ограничений).
Глааа 6. Тестирование взаимодействия и функционирования компонентов ПОСЛЕДСТВИЯ ВЫБОРА ЗАЩИТНОГО ИЛИ КОНТРАКТНОГО ПОДХОДА ПРИ ТЕСТИРОВАНИИ Защитное проектирование предпопагает, что перед отправкой сообщения выполня- ется либо незнечительнея, либо никакая проверка греничных значений параметров. Это приводит к уменьшению числа разделов предусловий, требует внутренней про- верки нерушения ограничений, накледывеемых не атрибуты, и увеличения количества разделов постусловий. Увеличение количестве постусловий вызывается тем, что уввличивеется число исключительных ситуеций, обусловленных необходимостью иден- тификации различных нарушений огреничений. В свою очередь, это еызывевт необ- ходимость применения большего числе тестовых случеее для взаимодействий, ори- ентированных на проверку граничных значений ввода, служащих источником исключительных ситуеций. Контректное проектировение предполегеет, что перед отпрвекой сообщения прове- ряются соответствующие предусловия, и сообщение не будет отправляться, если значения каких-либо параметров выходят зе допустимые пределы. Это увеличивает количество резделов предусловий, не требует внутренней проверки нарушений огра- ничений на атрибуты и приводит к уменьшению числа разделов постусловий. В рвзультете потребуется большее число тестовых случеее, чтобы найти и подвергнуть тестированию объект, который предпринял попытку послать сообщение, для которого предусловия были нерушены. В кечестее альтернативы мы прибегеем к помощи специельных прогремм внализа, чтобы докезать самим себе, что предусловия и в свмом деле нв могут быть нврушвны, тем семым устреняя необходимость в большем количестве тестовых случаев, превде, поплатившись зв это выполнением операций анвлизв вручную. В перспективе тестирования очень важно знать, какой подход был использован при формулировании спецификации конкретного интерфейса, подлежащего тес- тированию: защитного проектирования или проектирования по контрактам. Выбор подхода приводит к изменению способа взаимодействия получателя с отправите- лем. Мы сделаем простое предположение, согласно которому все операции в ин- терфейсе для заданного класса определяются с использованием только одного их этих подходов. Если класс, который должен подвергаться тестированию, исполь- зует смешанный подход, можно просто смешивать описываемые нами методы. Тестирование взаимодействий объекта Тестирование коллекций Тестирование коллекций производится с использованием методов, предназна- ченных для примитивных классов (см. главу 5). Тестовый драйвер создает экзем- пляры3, которые передаются как параметры сообщений в тестируемые коллекции. Задача тестовых случаев заключается главным образом в том, чтобы убедиться, что эти экземпляры успешно включаются в коллекцию и удаляются из нее. Точный класс каждого из объектов, используемых в процессе тестирования, не имеет зна- чения при проверке корректности функционирования коллекции, поскольку вза- 1 Библиотечные методы для создания тестируемых объектов (см. раздел «Библиотечные методы OUT- объектов») полезны при построении экземпляров, используемых при тестировании взаимодействий.
Глава 6. Тестирование взаимодействия и функционирования компонентов имодействие экземпляра коллекции с объектами, содержащимися в коллекции, отсутствует. Если во время фактического использования в коллекцию можно включить сорок или пятьдесят элементов, то следует построить такие тестовые случаи, которые добавляют в эту коллекцию, по меньшей мере, пятьдесят элемен- тов. Если невозможна типовая оценка верхней границы, проведите тестирование с использованием очень большого числа объектов в коллекции. Следует провести тестирование поведения объекта коллекции в обстоятель- ствах, когда он не способен распределять пространство памяти для того, чтобы включить в себя новый элемент. Структуры, подобные наращиваемым массивам, достаточно часто отводят пространство памяти сразу под несколько элементов. Имеются необходимые инструментальные средства, позволяющие тестировщику ограничить размер памяти, доступной во время прогона тестовых случаев, кото- рые проверяют распределение блоков памяти, превосходящих по размерам дос- тупные области памяти. Тестируемый объект должен вернуть исключительную ситуацию объекту, обратившемуся с запросом на выполнение данного действия. Мы займемся исследованием описанной проблемы в разделе «Исключительные ситуации, возникающие при тестировании». Если был использован подход защитного программирования, тесты с отрица- тельным исходом должны быть частью тестового набора. Для некоторых коллек- ций задается конечная емкость, в то же время все коллекции имеют некоторые практические пределы, такие как доступная память, которые должны быть протес- тированы с помощью тестов, превосходящих заданные пределы выделенной памя- ти. Если коллекция для целей хранения использует массив, то в тестовый набор должны быть включены тестовые случаи, моделирующие заполнение этого масси- ва, после чего предпринимающие попытки добавить в массив еще один элемент. При этом тестируемый объект должен сгенерировать соответствующую исключи- тельную ситуацию, и она должна быть воспринята объектом, который отправил соответствующее сообщение. Если использован контрактный подход, подобные проверки теряют всякий смысл. Важным аспектом тестирования коллекций, равно как и тестирования взаимо- действующих классов, является тестирование последовательностей операций, в ча- стности, как операции, выполняемые модификатором над одним таким объектом, взаимодействуют с другим объектом. Для тестирования этого аспекта коллекций можно прибегнуть к методам, связанным с тестированием, ориентированным на состояния (см. главу 5). Тестирование взаимодействующих классов Тестирование взаимодействующих классов сопряжено с большими трудностя- ми, нежели тестирование коллекций или примитивных классов. Рассмотрим класс BrickPile (куча кирпичей) из приложения «Кирпичики». Куча кирпичей — это аг- регация кирпичей, уложенных в прямоугольный штабель. Класс BrickPile подобен коллекции, в то же время BrickPile посылает семантически содержательные сооб- щения отдельным объектам Brick, например, чтобы определить местоположение
Глааа 6. Тестирование взаимодействия и функционирования компонентов конкретного кирпича в игровом поле либо разрушить тот или иной кирпич. От- ладка класса BrickPile невозможна без использования экземпляров класса Brick. В то же время трудно обнаружить ошибки в BrickPile, если в классе Brick не выяв- лены некоторые типы ошибок. На кучу кирпичей возложена обязанность обнару- жения соударений содержащихся в ней кирпичей с подвижными спрайтами (а именно, с шайбами), однако в ее задачу не входит обработка этих соударений. Она также регистрирует подсказки, связанные с разрушением кирпичей, с тем, чтобы объект представления игры «Кирпичики» эффективно обновлял экран.4 Для тестирования класса BrickPile потребуется задействовать один или боль- шее число экземпляров этих классов. По существу, экземпляр BrickPile не может быть построен без участия экземпляров классов Playfield, CPoint и Hint, посколь- ку они должны быть переданы конструктору как параметры (см. рис. 6.3). Разуме- ется, ему понадобится также экземпляр класса Brick, чтобы построить штабель кирпичей. CPoint, Hint и Brick суть примитивные классы и в силу этого обстоятельства могут быть протестированы с применением методов, представленных в главе 5. Класс CPoint, используемый в игре «Кирпичики», является одним из классов MFC (Microsoft Foundation Classes) и, следовательно, вызывает к себе «доверие», поэтому тестировать мы его не будем. Классы Playfield и BrickPile не относятся к числу примитивных и должны подвергаться тестированию в контексте их взаимо- действия с программными кодами других классов, с использованием методов из текущей главы. Связь между тестированием и подходом к проектированию Различие между контрактным и защитным методами проектирования (см. раз- дел «Последствия выбора защитного или контрактного подхода при тестирова- нии») распространяется и на тестирование. Контрактное проектирование возлага- ет большую ответственность на проектировщика, нежели на программы поиска ошибок. ДРУЖЕСТВЕННЫЕ ФУНКЦИИ Как правило, интерфейс класса содержит всв операции и, не дай Бог, данные, объявленные как общедоступные. Твм нв манвв, при использовании таких языков программирования как C++, которые поддерживают дружественные функции, которые, не будучи функциями-членами, всв же имеют доступ к скрытым частям класса, мы включаем любые такие функции в интерфейс. Напримвр, во многих классвх объявлена ассоциированная операция вставки (operator «), которвя позволяет вставить состо- яние экземпляра в поток, т.е. экземпляры можно подготовить вне текущей программы и поместить в фвйл или в квкую-либо другую последовательную структуру. Считайте зти функции операциями общедоступного интерфейса конкретного классе. Ев можно рассмвтривать как перспективу, выбранную программистом, использующим этот клвсс. 4 Подсказка предназначается системным компонентам, которые создают на экране изображение игро- вого поля; тем самым она несет информацию об искаженных частях игрового поля.
Главе 6. Тестирование взаимодействия и функционирования компонентов Это уменьшает объемы тестирования на уровне классов, поскольку в данном случае имеется меньше путей, что обусловлено меньшими размерами программ поиска ошибок. В то же время на уровне взаимодействий требуется большие объемы тестирования программ, разработанных в условиях применения контракт- ного метода, чтобы быть уверенным в том, что разработчик выполнил требования клиентской стороны контракта, соблюдая ограничения, вытекающие из постусло- вий. Основное внимание во время тестирования взаимодействий в условиях кон- трактного подхода уделяется проверке, выполнены ли объектом-отправителем предусловия методов получающего объекта. Не допускается построение тестовых случаев, нарушающих эти предусловия. Обычно практикуется перевод объекта- получателя в некоторое заданное состояние, после чего инициируется выполне- ние тестового драйвера, по условиям которого объект-отправитель требует, чтобы объект-получатель находился в другом состоянии. Смысл подобной проверки зак- лючается в том, чтобы установить, выполняет ли объект-отправитель проверку предусловий объекта-получателя, прежде чем отправлять заранее неприемлемое сообщение. Одновременно с этим проверяется, корректно ли объект-отправитель прекращает свою деятельность, возможно, генерируя при этом исключение. Например, рассмотрим следующую спецификацию метода broken() класса BrickPile, в рамках которого куча кирпичей взаимодействует с объектами класса Brick (см. рис. 6.4). Если был использован контрактный подход к проектирова- нию, то тестовый случай, в котором brick_p есть 0 (ноль), не имеет смысла. BrickPile::BrickPile(PlayField* playFieldp, const CPointS position, Hint& hint) ; pre: playField_p <> 0 and playField_p->boundingRect() . contains (position) post: self.boundingRect.TopLeft() = position BrickPile::-BrickPile(); pre: true post: bricks->isEmpty void BrickPile::broken(Brick* brick p); pre: brick_p <> 0 and not brick_p->isBroken() post: brick_p->isBroken () and self.unbrokenCountO - self@pre.unbrokenCount + 1 and bricks = bricks@pre->excluding(*brick_p) int BrickPile::unbrokenCount() const; pre: true post: result =(bricks->select( b : not isBroken(b)) ).size Brick* BrickPile::overlaps(Sprite* sprite p); pre: sprite_p <> 0 post: result = if ( bricks->collect( b b.boundingRect.intersect(sprite_p-> boundingRect()))->isEmpty ) then 0 else bricks->collect( b b.boundingRect.intersect(sprite_p-> boundingRect()))->asSequence->first Рисунок B.4. Спецификация класса BrickPile на языке OCL
Глвев 6. Тестироеение езеимодейстеия и функционироевния компонентов Следует воспользоваться тестовым случаем, в котором brick_p указывает на конкретный экземпляр кирпича, и этот тестовый случай должен четко продемон- стрировать факт удовлетворения постусловий. В процессе тестирования класса BrickPile нам понадобятся тестовые случаи, которые осуществляют взаимодействие с классом PlayField. В этом контексте в те- стовом наборе должен быть тестовый случай, в условиях которого классу PlayField поступает команда «разрушить» конкретный кирпич, который уже был разрушен. Этот тестовый случай проводит проверку с целью убедиться, что PlayField сам проводит проверку и в нарушение постусловий не отправляет сообщение broken() экземпляру PlayField. Выбор тестовых случаев Исчерпывающее тестирование, другими словами, прогон каждого возможного тестового случая, покрывающего каждое сочетание значений — вне всяких сомне- ний, надежный подход к тестированию. Однако во многих ситуациях количество тестовых случаев достигает таких больших значений, что обычными методами с ними справиться попросту невозможно. Если имеется принципиальная возмож- ность построения такого большого количества тестовых случаев, на построение и выполнение которых не хватит никакого времени, должен быть разработан систе- матический метод определения, какими из тестовых случаев следует воспользо- ваться. Если есть выбор, то мы отдаем предпочтение таким тестовым случаям, которые позволят найти ошибки, в обнаружении которых мы заинтересованы больше всего. Если у нас нет никакой предварительной информации, то, по-ви- димому, наилучшее, что можно предпринять в подобного рода ситуации — это случайный выбор. В этом разделе мы рассмотрим общие принципы выборки, пос- ле чего применим их к тестированию взаимодействий. При любом подходе к тестированию мы заинтересованы в том, чтобы система- тически повышать уровень покрытия. Если тестировщик просто создает тестовые случаи без достаточного их обоснования, то увеличение их количества часто при- водит на более поздних стадиях отладки к повторению проверок функциональ- ных средств, которые уже подвергались тестированию. Представленные здесь ме- тоды позволяют получить строго очерченные наборы тестовых случаев и четко определенные методы увеличения степени покрытия. Существуют различные способы определения, какой тестовый случай следует выбирать. Метод, который рассматривается первым, использует простой процесс выбора, основанный на распределении вероятностей. Распределение вероятностей определяет для каждого значения совокупности набор допустимых значений и ве- роятность выбора каждого из этих значений. В условиях равномерного распреде- ления вероятностей каждое значение совокупности получает одно и то же значе- ние вероятности выбора. Мы определяем совокупность, представляющую для нас интерес, как все воз- можные тестовые случаи, прогон которых возможен. При этом учитываются все предусловия и все возможные сочетания входных значений. Выборка представля-
Глава 6. Тестирование взаимодействия и функционирования компонентов ет собой подмножество некоторой совокупности, выбранное на базе заданного распределения вероятностей. Один из подходов заключается в том, чтобы основа- нием распределения вероятностей служил профиль использования. Если виды использования системы упорядочены по частоте использования, то соответствую- щие ранги могут быть преобразованы в вероятности. Чем выше частота использо- вания, тем больше вероятность выбора. Однако более подробно эти вопросы будут рассматриваться ниже (см раздел «Профиль использования»). Мы можем остановить свой выбор на расслоенной выборке, в условиях которой тесты выбирают из некоторой последовательности категорий. Расслоенная выбор- ка — это набор выборок, в котором каждая выборка представляет конкретную подсовокупность — например, мы можем провести отбор тестовых случаев, кото- рые, как мы точно знаем, заставят работать каждый компонент архитектуры. Со- вокупность тестов разделена на поднаборы таким образом, что каждый поднабор содержит все тесты, которые затрагивают конкретный компонент. Выборка произ- водится на каждом поднаборе независимо от других. Один из подходов, который зарекомендовал себя с хорошей стороны, предус- матривает использование активных субъектов из модели случаев использования в качестве основы для расслаивания тестовых случаев. Другими словами, мы произ- водим выборку тестовых случаев из случаев использования каждого действующего субъекта. Каждый действующий субъект использует некоторые поднаборы воз- можных случаев использования с определенной частотой (см. раздел «Профили использования»). Расслоение выборок случаев использования по действующим субъектам представляет собой эффективное средство повышения надежности сис- темы. При прогоне выборочных тестов система используется именно так, как это характерно для типовых ситуаций, при этом в первую очередь будут обнаружи- ваться те из дефектов, которые, по всей вероятности, будут обнаружены при обычном использовании системы. Устранение этих дефектов приводит к макси- мально возможному повышению надежности тестируемой системы при мини- мальных усилиях. Выборочный метод предлагает алгоритм для формирования тестового набора из множества возможных тестовых случаев. Это, однако, не выдвигает задачу оп- ределения совокупности тестовых случаев на первый план. Процесс тестирования предусматривает определение совокупности тестов, в которых мы заинтересо- ваны - например, функциональные тестовые случаи — с последующим определе- нием метода выбора, какие из этих тестовых случаев будут построены и выполне- ны. Тестовый набор для конкретного компонента может быть построен с использо- вание того или иного сочетания методов. Рассмотрим класс Velocity (векторная скорость), который упоминался в главе 5, где выполнялось исчерпывающее тести- рование значений направления, но проверялось лишь несколько значений скаляр- ной скорости. Мы можем уменьшить количество тестов сначала за счет использо- вания спецификации как источника тестовых случаев, а затем за счет применения выборочного метода с целью пополнения тестового набора.
Глава 6. Тестироввнив взаимодействия и функционирования компонвнтоа Спецификация класса Velocity предусматривает выполнение операции, кото- рая называется setDirection(const Direction &newDirection), предусловия которой требуют, чтобы атрибут newDirection принимал значение в пределах от 0 до 359 включительно. Это постусловие определяет, что направление получателя измене- но и получило значение newDirection. Сначала мы генерируем тестовые данные для этого метода, используя спецификацию в качестве основы. Прежде всего, об- ратите внимание на то обстоятельство, что Direction есть typedef для int, следова- тельно, выбор производится из набора целых чисел, а не из набора объектов. Вме- сто выбора из каждого случая использования (от 0 до 359), сначала выбираются значения, основанные на граничных значениях. Таким образом, в окрестности граничного значения, равного нулю, мы имеем три значения, скорее всего, это -1, О и 1. Если бы этот проект был «продуктом контрактного подхода», значение -1 было бы недопустимым тестовым случаем. Такой же набор тестовых значений должен быть выбран и для другой граничной точки, возможно это 358, 359 и 360. Опять-таки, значение 360 является недопустимым в контексте контрактного под- хода. Должны быть также выполнены тесты в диапазоне от 1 до 358, именно в этом случае в наибольшей степени проявляются преимущества выборки. Значе- ния в обоих интервалах можно выбирать, воспользовавшись формулами вида int(random() * 360) и int(-l * random() * 360). Функция random() генерирует псев- дослучайное значение в диапазоне от 0.0 до 1.0 в соответствии с равномерным распределением, так что каждое значение попадает в указанный интервал и каж- дое значение обладает равной вероятностью быть выбранным. Преимущество использования в тестовом примере генератора случайных зна- чений заключается в том, что в условиях итераций и многократного применения этих тестовых случаев будут проверены множество значений в указанных интер- валах, а не одно и то же во всех случаях. Недостаток такого подхода проявляется в том, что тестовые случаи в таких ситуациях воспроизвести невозможно, по- скольку всякий раз используются новые значения. Если тестовый драйвер будет выполнять регистрацию предложенных генератором значений как одну из опера- ций ведения журнала испытаний, тогда можно будет воссоздать любой тестовый случай, прогон которого завершился неудачно. Любое значение, полученное с по- мощью случайного выбора, на котором проявляется ошибка, непосредственно до- бавляется в тестовый набор и используется для тестирования усовершенствован- ного программного продукта. После исправления ошибки эти значения будут использованы для подтверждения корректности исправлений. Регрессионный на- бор состоит главным образом из тех тестов, которые первоначально вызвали ошибки, и которые в конечном итоге были успешно преодолены программным продуктом. Теперь рассмотрим взаимодействие двух классов: Sprite и MoveableSprite при выполнении операции collidelnto() (см. рис. 6.5). Оба класса Sprite и MoveableSprite являются абстрактными, следовательно, есть возможность спроек- тировать тесты, которые могут быть многократно использованы их подклассами.
Главе 6. Тастироввние взаимодействия и функционирования компонентов void MoveableSprite::collidelnto(Sprites sprite); pre: none post: sprite.collideWith(self) Рисунок Б.5. Спецификация операции collidelnto() Предусловия не устанавливают никаких ограничений на параметры, следова- тельно, потребуется отыскать другие пути для определения совокупности, из ко- торой можно было бы производить выборку. Существуют три оцениваемых фак- тора, в соответствии с которыми можно производить выборку. Во-первых, Sprite — это базовый класс очень большого семейства, каковым яв- ляется множество классов, связанных между собой отношением наследования. Объекты каждого из классов этого семейства могут заменять параметры Sprite. В силу этого обстоятельства мы должны производить выборку возможных парамет- ров из этого множества. Это одна из проблем, которая упоминалась ранее при об- суждении объектно-ориентированных систем. В некоторый момент в будущем новый член семейства может быть построен и передан в эту программу без по- вторной компиляции класса MoveableSprite. Традиционные способы инициации регрессионного тестирования в этой среде не работают. Управление этими спосо- бами должно выполняться в рамках инструментальных средств манипулирования конфигурацией или, возможно, средой разработки. Определение каждого нового класса дает начало циклу регрессионного тестирования. Однако обычно тестиро- вания требуют только перекрытые методы, если большая часть методов унаследо- вана. СОВЕТ Чтобы выявить классы, которые должны пройти регрессионное тастироввние в результате создания нового класса, воспользуйтесь диаграммой классоа. Прове- дите исследование родительских классов этого нового клвссв и выясните, в каких взаимодействиях эти классы принимают участия. Выполните прогон тестов, которые обеспечивают взаимодайствие родительских классов с другими классами, однако в данных твствх звменитв родительский класс новым классом. Второй оцениваемый фактор выборки требует исследования, может ли тот или иной член семейства принимать различные состояния, которые служат причиной того, что два объекта одного и того же класса ведут себя по-разному. Вполне оче- видно, что в состояниях классов Puck и Wall, скорее всего, имеются интересные отличия. В случае семейств классов конечные автоматы соотносятся с уровнями наследственной иерархии. Наш собственный опыт, равно как и множество публи- каций на эту тему, показывает, что если посмотреть вниз дерева иерархии насле- дования, то в производных классах обнаруживается то же или даже большее коли- чество состояний, нежели в базовом классе. При выборе покрытия состояний каждого класса мы должны уделять повышенное внимание новым состояниям, добавленным на конкретном уровне иерархии наследования.
Главв 6. Тестироаанив взаимодействия и функционирования компонентов Третий оцениваемый фактор относится к семейству классов, ассоциированных с классом MoveableSprite. Это семейство представляет собой подмножество се- мейства Sprite. Как только соответствующие тестовые случаи будут разработаны, их можно сразу применять к любому классу семейства при условии, что во время их разработки соблюдался принцип подстановки. При наличии этих трех оцениваемых факторов появляется возможность ком- бинаторного взрыва, если речь идет о количестве тестовых конфигураций (см. рис. 6.6). По условиям такого сценария в рамках конкретного тестового случая член семейства классов MoveableSprite посылает сообщения некоторому члену се- мейства классов Sprite, который может находиться в любом из своих состояний. Ортогональная матрица тестирования Ортогональная матрица позволяет получить особый метод выборки, способный ограничить последствия комбинаторного взрыва за счет объявления попарных со- четаний некоторого множества взаимодействующих объектов. Большинство отка- зов, возникающих при взаимодействиях, обусловлены двусторонними взаимо- действиями. Одним из инструментом выборки образцов является система OATS (Orthogonal Array Testing System - система тестирования с использованием ортого- нальной матрицы). Ортогональная матрица представляет собой массив значений, в которой каждый столбец представляет собой коэффициент, который в рамках эксперимента рассматривается как переменная. В рассматриваемом нами случае он представляет конкретное семейство классов5 в системе программного обеспече- ния. Каждая переменная может принимать некоторое множество значений, име- нуемых уровнями. В нашем случае тестирования каждый уровень есть конкретный класс этого семейства. Рисунок В.В. Комбинаторный взрыв тестовых случаев ’ Семейство классов — это некоторый класс и все унаследованные от него классы.
Главв 6. Тестирование взаимодействия и функционировения компонвнтов А В С 11 13 2 1 2 2 3 1 3 1 4 2 1 2 5 2 2 1 6 2 3 3 7 3 1 1 8 3 2 3 9 3 3 2 Рисунок В.7. Попарные сочетания трех факторов, каждый из которых обладает тремя уровнями Используется также параллельный коэффициент и множество уровней, которые соответствуют состоя- ниям этих классов. Значение, которое вводится в конкретную ячейку матрицы, представляет собой эк- земпляр класса или конкретное состояние объекта. В ортогональной матрице коэффициенты образу- ют попарные сочетания, а не всевозможные комби- нации уровней для коэффициентов. Например, предположим, что имеется три коэффициента, на- пример, А, В и С, каждый из которых имеет, скажем, три уровня, 1, 2 и 3. Возможны 27 сочетаний этих значений — 3 для коэффициента А, умноженное на 3 (число уровней коэффициента В), умноженное на 3 (число уровней С). Если вместо них использовать попарные сочетания, т.е., если учитывать только те сочетания, в которых заданный уровень появляется в дважды, то таких сочетаний, как следует из рис. 6.7, только 9. Система OATS предлагает сбалансированный подход. Каждый уровень коэф- фициента появится то же количество раз, что и любой другой уровень этого коэф- фициента. Если мы будем интерпретировать строки таблицы как тестовые случаи, то 18 из 27 тестов не проводятся. Это систематизированный метод способ умень- шения числа тестовых случаев. Если позже мы решим, что нужно выполнить до- полнительные тесты, мы будем точно знать, какие сочетания не подвергались тес- тированию. Это также логический способ уменьшения тестовых случаев. Большая часть ошибок обнаруживается при взаимодействии пар объектов, а не при взаи- модействии нескольких объектов. Таким образом, мы проводим тестирования си- туаций, в которых ошибки проявляются с большей вероятностью. Чтобы показать возможности системы OATS, сначала рассмотрим пример общего характера, а за- тем продолжим изучение игры «Кирпичики». Пример общего характера охватыва- ет взаимодействие отправителей, принадлежащих к семейству классов А, получа- телей, принадлежащих к семейству классов С и параметров из семейства Р (см. рис. 6.8). С каждым классом связана диаграмма переходов. Дальнейшие подроб- ности не имеют значения. Количество состояний каждого класса, соответствую- щее нашим предположениям, задается рис. 6.9. Основные усилия в условиях предлагаемой методики приходятся на отображе- ние задачи тестирования взаимодействия двух иерархий наследования на пара- метрические объекты. Для выявления нужных тестовых случаев с помощью орто- гональной матрицы потребуется выполнить следующих 5 действий: Действие 1: Идентифицировать все коэффициенты. Передающая иерархия - это один коэффициент, принимающая иерархия — это второй коэффици- ент. Имеется также коэффициент, ассоциированный с позицией каждого параметра в сообщении. Существует дополнительный коэффициент, ассо-
Глввв 6. Твстироввнив взаимодействия и функционирования компонентов Рисунок В.8. Общий пример применения системы ОА TS циированный с каждым коэффициентом класса, а именно, состояния, ассо- циированные с экземплярами этого класса. В данном эксперименте (см. рис. 6.8) участвуют шесть коэффициентов: иерархия класса А, иерархия класса Р и иерархия класса С, а также коэффициенты состояний, ассоции- рованные с иерархией каждого класса. Действие 2: Определить уровень каждого коэффициента. Уровень каждого коэффициента определяется путем анализа множества возможных значе- ний. • Один коэффициент имеет один уровень: в семействе параметрических классов имеется один член: класс Р. • Два коэффициента имеют максимум два уровня; семейство класса-отпра- вителя содержит два члена: А и В; максимальное число состояний класса из семейства Р равно двум. • Три коэффициента имеют максимум три уровня: принимающее семей- ство содержит три члена, а максимальное число состояний класса семей- ства А и семейства С равно трем. Действие 3: Определить стандартную ортогональную матрицу, которая соот- ветствует решаемой задаче. Имея потребность в шести коэффициентах мак- симум трех уровней, мы можем воспользоваться таблицами заранее вычис- ленных массивов значений, именуемых стандартными матрицами [Phadke89], Обозначение 2'хЗ7 для L (см. рис 6.16) указывает на то, что Количестао Класс СОСТОЯНИЙ А 1 В 2 Рисунок 6.9. Количество состояний, ассоциированных с классами, в общем примере применения системы OATS 93,,
Глава 6. Тестирование взаимодействия и функционирования компонентов матрица отображает один коэффициент с двумя уровнями и семь коэффи- циентов с тремя уровнями. L представляет собой наименьшую стандарт- ную матрицу, которая может использоваться для решения рассматриваемой задачи. Стандартная матрица может быть больше, чем требуется для реше- ния задачи, но никак не меньше6. Действие 4: Установить такое соответствие каждого коэффициента целочис- ленному значению матрицы, благодаря которому становится возможной ин- терпретация стандартной матрицы. Вхождения в стандартную матрицу суть целочисленные значения. Мы проводим анализ каждого коэффициента в следующем списке: • В семейство классов-отправителей входят два класса, А и В, так что пер- вый столбец стандартной матрицы LIg может использоваться для пред- ставления этих данных (рис. 6.10). Мы используем специальную коди- ровку, согласно которой значение 1 в первом столбце матрицы соответствует классу А, а значение 2 — классу В. • Класс имеет А два состояния, а класс В — три состояния. Там, где имеет- ся различие в числе уровней, можно воспользоваться столбцом, который соответствует максимуму или превосходит его. Второй столбец в матри- це LJg имеет максимум, равный трем, что соответствует рассматриваемым данным. Интерпретация значений во втором столбце зависит от значе- ний в первом столбце. Для значения 2 (класс В) в первом столбце значе- нию 2 мы ставим в соответствие состояния класса В из второго столбца. Если значение в первом столбце есть 1, то второй столбец представляет состояния класса А. На рис. 6.11 значения состояний для класса В прямо соответствуют целым значениям в этом столбце. Поскольку класс А име- ет только два состояния, то как мы будем интерпретировать значение 3 во втором столбце, когда в первом столбце стоит значение 1 (класс А)? Мы интерпретируем его, как если бы это были значения 1 или 2. Если значение какого-либо столбца не соответствуют должным образом значе- ниям других столбцов, то мы интерпретируем его как некоторое другое значение предметной области, которое повторяется. В этом случае, как отмечено в рассматриваемой таблице, значение 3 будет соответствовать в матрице состоянию 1. Эта интерпретация может быть произвольной или основанной на том факте, что экземпляр класса А имеет большую веро- ятность пребывать в состоянии «1», либо на том факте, что пребывание в состоянии «1» сопряжено с большим риском. Воспользуйтесь матрицами большего, нежели требуется на текущий момент, размера, если количе- ство уровней может измениться в будущем - например, если в семейство принимающих классов могут быть добавлены новые подклассы. Использование матриц больших, чем необходимо для реше- ния текущей задачи, размеров позволит в будущем расширить тестовый набор в случае добавления новых уровней.
Глава 6. Тастированив взаимодействия и функционирования компонентов • Третий столбец матрицы L]g представляет иерархию параметров, в кото- рой имеется всего лишь один класс Р. Любое значение в третьем столбце . представляет класс Р (рис. 6.12). • Четвертый столбец матрицы L]S представляет состояния класс Р, которых здесь два (рис.6.13). Однако в этом столбце имеются значения 1, 2 и 3. Значение 1 матрицы соответствует состоянию 1, значение 2 соответству- ет состоянию 2 класса Р, значение 3 повторит состояние 2 класса Р. • Пятый столбец матрицы Ltg представляет класс С иерархии, в которой имеется три члена. Существует прямое соответствие классов целочис- ленным значениям матрицы. Соответствующая интерпретация показана на рис. 6.14. • Шестой столбец представляет состояния классов С, D и Е (рис. 6.15).По- скольку класс С обладает только двумя состояниями, значение 3 матри- цы будет соответствовать значению 2. Для классов D и Е имеется прямое соответствие состояний значениям матрицы. Примечание: последние два столбца матрицы L не используются. Действие 5: Построить тестовые случаи на основе отображений и строк таб- лицы. Каждая строка ортогональной матрицы на рис. 6.16 описывает один конкретный тестовый случай. Ортогональная матрица отображается обратно на тестовые случаи путем декодирования номера уровня строки матрицы обратно в индивидуальные списки каждого коэффициента. Таким образом, например, 10-я строка матрицы L интерпретируется как тестовый случай под номером 10, в котором экземпляр класса В, находясь в состоянии 1, должен послать сообщение путем передачи экземпляра класса Р в состоянии 3 экземпляру класса Е в состоянии 2. Два последних значения в строке иг- норируются, поскольку эти коэффициенты не используются. Критерий адекватности системы OATS Одним из достоинств системы OATS является ее способность настраиваться на ту или иную плотность покрытия тестируемого программного продукта тестовыми случаями. Можно выделить следующие уровни покрытия, которыми в дальней- шем можно воспользоваться: Исчерпывающее покрытие — рассматриваются все возможные комбинации всех факторов. Полное доверие и куча затрат. Минимальное покрытие — тестируются только взаимодействия базовых клас- сов каждой иерархии. Слабое доверие на основании небольшого числа ус- пешных тестовых случаев. Произвольное покрытие — тестировщик случайным образом выбирает тесто- вые случаи из нескольких классов. Уровень доверия неясен, количество те- стовых случаев произвольно. Выборка не является статистически случай- ной. 9*
Главе 6. Тестирование взвимодайствия и функционирования компонентов Значение предметной области Значение элемента метрицы А 1 В 2 Рисунок 6.10. Иерархия класса А Знечение предметной облести Значение элемента матрицы А, Состояние 1 1 А, Состояние 2 2 А, Состояние 1 3 В. Состояние 1 1 В, Состояние 2 2 В, Состояние 3 3 Рисунок 6.11. Состояния в иерархии класса А Знечение предметной области Значение элемента матрицы р 1 р 2 р 3 Рисунок 6.12. Состояния в иерархии класса Р Значение предметной области Значение элементе метрицы Р, Состояние 1 1 Р, Состояние 2 2 Р, Состояние 3 3 Рисунок 6.13. Состояния в иерархии класса Р Значение предметной облести Значение элемента матрицы с 1 D 2 Е 3 Рисунок 6.14. Иерархия класса С
Глава 6. Тестирование взаимодействия и функционирования компонентов Значение предметной области Значение элемента матрицы С, Состояние 1 1 с, Состояние 2 2 с, Состояние 2 3 □, Состояние 1 1 □, Состояние 2 2 □, Состояние 3 3 Е, Состояние 1 1 Е, Состояние 2 2 Е, Состояние 3 3 Pl/ICyHOK Б.15. Состояния в иерархии класса Р Тестовый случай А Аstate Р Р state С С , , state 1 2 3 4 5 6 7 8 1 1 1 1 1 1 1 1 1 2 1 1 2 2 2 2 2 2 3 1 1 3 3 3 3 3 3 4 1 2 1 1 2 2 3 3 5 1 2 2 2 3 3 1 1 6 1 2 3 3 1 1 2 2 7 1 3 1 2 1 3 2 3 8 1 3 2 3 2 1 3 1 9 1 3 3 1 3 2 1 2 1 0 2 1 1 3 3 2 2 1 1 1 2 1 2 1 1 3 3 2 1 2 2 1 3 2 2 1 1 3 1 з 2 2 1 2 3 1 3 2 1 4 2 2 2 3 1 2 1 3 1 5 2 2 3 1 2 3 2 1 1 6 2 3 1 3 2 3 1 2 1 7 2 3 2 1 3 1 2 3 1 8 2 3 3 2 1 2 3 1 РисуНОК Б.1 Б. Стандартная ортогональная матрица L^Q.' х З7)
Главв 6. Твстированиа взаимодействия и функционироввния компонентов Представительное покрытие — равномерная выборка, обеспечивающая опре- деленный уровень тестирования каждого класса. Уровень доверия один и тот же для каждого класса, а количество используемых тестовых случаев минимально. Взвешенное представительное покрытие — в представительное покрытие до- бавляются тестовые случаи из соображений относительной важности или риска, характерного для того или иного класса. Этот подход рассматривался в данном разделе. В любой точке, в которой в матрице имеется больше уровней, чем в фактической задаче, тестировщик имеет возможность созда- вать дополнительные тесты для приоритетных уровней коэффициентов. После выполнения всех тестовых случаев изучите результаты тестирования с тем, чтобы определить, могут ли обнаруженные отказы быть ассоциированы с од- ним или большим числом конкретных уровней коэффициентов — например, вполне возможно, что прогон тестовых случаев, ассоциированных с экземплярами класса А, которые пребывают в состоянии 2, завершится неудачей. Эта информа- ция будет полезна для разработчиков при выявлении ошибок, и в то же время она полезна и для тестировщиков, поскольку она показывает, что могут потребоваться дополнительные тестовые случаи. Еще один пример Вернемся теперь к примеру MovableSprite::collideInto(). Объект MovableSprite может быть передан любому объекту Sprite, когда передается сообщение collidelnto(). В рассматриваемой разработке в семейство класса Sprite входят клас- сы MovableSprite, StationarySprite, Puck, Paddle, Brick, Wall, RightWall, LeftWall, Floor и Ceiling. Выполним анализ и сделаем следующие замечания: 1. Классы Sprite, MovableSprite, StationarySprite и Wall — абстрактные классы. Мы будем обсуждать проблему тестирования абстрактных классов в следую- щей главе, а на данный момент лишь заметим, что они не являются частью сценария системы OATS. 2. Только классы Puck и Paddle являются производными класса MovableSprite, следовательно, только классы MovableSprite, Puck и Paddle могут получить сообщение collidelnto(). В то же время, все классы, производные от Sprite, могут получить сообщение collideWith(). 3. Каждый объект, передаваемый как параметр, пребывает в некотором конк- ретном состоянии. Будучи в различных состояниях, объекты могут вести себя по-разному. Экземпляры класса MovableSprite могут перемещаться или оставаться неподвижными. Если экземпляр двигается, то он двигается в определенном направлении. В перспективе состояний, направления могут быть разбиты на группы с такими именами: DueNorth, DueSouth, DueEast, DueWest, NorthEast, Northwest, SouthEast и Southwest. Обратите внимание
Глава 6. Твстироааниа взаимодействия и функционирования компонентов на то обстоятельство, что экземпляры класса Paddle могут перемещаться только в направлении DueEast и DueWest. 4. В тех случаях, когда объект отправителя и параметрический объект окажут- ся одним и тем же объектом, останутся только два столбца семейств классов и два столбца состояний классов. Возможные значения каждого атрибута тестового случая показаны на рис. 6.17. Если мы подвергли тестированию все возможные комбинации, количество возможных тестов равно 2 х 9 х 8 х 9 = 1296. Некоторые из них можно исклю- чить, поскольку неподвижные спрайты не имеют состояний направления. Общее количество тестовых случаев будет равно 2х9х2х9 + 2х9х1х6 = 432 — все еще достаточно большое число. Если воспользоваться системой OATS, можно про- должить уменьшать количество тестовых случаев, не снижая при этом в эффек- тивности тестирования. Например, на основании данных рис. 6.17, избранными комбинациями являются следующие: Paddle, DueEast, Puck, SouthEast Paddle, DueEast, Puck, NorthEast Paddle, DueEast, Puck, NorthWest Paddle, DueEast, Puck, DueWest Puck, DueEast, Puck, DueWest Система OATS позволяет исключить тестовый пример #4, поскольку в примере #3 класс Paddle тестируется, когда лопатка двигается в направлении DueEast; в примере #5 класс Paddle тестируется, когда лопатка двигается в направлении DueWest; в примере #3 класс Paddle тестируется, когда происходит соударение лопатки с шайбой (класс Puck). Полный анализ с применением системы OATS существенно сокращает количество необходимых тестов. Класс Состояние Класс Состояние отпрааителя отпрааителя получателя получателя Puck DueNorth Puck DueNorth PaddlB DuaSouth Peddla DueSouth DueEast Brick DuaEast DueWast Wall DueWest NorthEast RightWall NorthEast NorthWast LeftWall NorthWBSt SouthEast Ceiling SouthEast Southwest Floor Southwest NotMoving NotMoving Рисунок В.17. Тестовые значения атрибутов
Глава 6. Твстироаанив взаимодействия и функционироввния компонентов class Stackcclass Т>{ Stack<T>(); void Push(const T (object); T Pop(); bool isEmpty (); ) ; Рисунок 6.18. Шаблон класса Stack на C++ Еще одно применение системы OATS Рассмотрим потребность в тестировании коллекции, такой как класс Stack, в условиях которой этот класс реализован в виде шаблона C++ (см. рис. 6.18). По замыслу разработчиков предусматривается замена шаблонного параметра Т любым классом при порождении экземпляров класса Stack. Разумеется, мы не можем тестировать определение класса Stack, используя для этой цели все воз- можные подстановки. Класс Stack, как и любая другая коллекция, не вызывает никаких методов для работы с объектами, которые содержит. Следовательно, ин- терфейс, реализованный параметрическим классом, не имеет существенного зна- чения. Чтобы выполнить тестирование программных кодов шаблона, проведем рас- слоенную выборку классов из всех доступных классов, включая библиотеки сто- ронних производителей программных продуктов, языковые библиотеки и при- кладные программы. В зависимости от конкретного языка программирования и ряда других факторов, категории в таком расслоении должны содержать сведения об объемах памяти, используемых каждым экземпляром, число ассоциаций, и све- дения о том, являются ли объекты, помещенные в коллекцию, постоянными. Да- лее из этого множества классов всякий раз, когда возникнет необходимость тести- рования рассматриваемой коллекции, мы будем отбирать некоторое подмножество классов. Эта вторая выборка выполняется с использование системы OATS. В случае более сложных шаблонов для каждого параметра создаются множества возможных подстановок. Система OATS создает тесты, которые предусматривают различные комбинации подстановок параметров. Эти тесты обеспечивают макси- мальный просмотр взаимодействий параметров при минимальном количестве тес- товых случаев. Тестирование серийно выпускаемых компонентов Все в больших масштабах функциональные возможности приложения возрас- тают за счет приобретения отдельных порций программного обеспечения, которые еще называются компонентами1. Качество таких компонентов меняется в широких пределах от одного поставщика программных продуктов к другому. До тех пор пока не появятся соответствующие стандарты или рынок не заставит поставщиков * 7 В главе 7 обсуждение вопросов, связанных с компонентами, будет продолжено, однако мы считаем, что здесь также уместно поговорить о них.
Главв 6. Твстироввниа взвимодайствия и функционироввния компонентов существенно повысить качество компонентов, в своих планах необходимо пре- дусматривать приемочные испытания любого вновь приобретенного компонента. В рамках приемочных испытаний компонент должен быть погружен в кон- текст, в котором он будет использоваться. Соответствующие тестовые случаи дол- жны тщательно исследовать пределы, очерченные спецификацией8. Создание спе- цификации в виде документа не будет напрасной тратой времени и усилий, поскольку разработчикам такой документ может понадобиться для корректного использования компонентов подобного рода. Мы предпочитаем начинать приемочные испытания с использования экстре- мальных, даже запредельных значений — например, гонять мышку взад-вперед вдоль панели, чтобы породить очень большое число событий типа движение мыши. Компонент с ошибками, скорее всего, будет “обескуражен” таким большим чис- лом событий и выйдет из строя. Другие стрессовые тесты предусматривают удер- живание “повторяющейся” клавиши в нажатом состоянии в течение достаточно продолжительного промежутка времени или многократный вызов меню, прежде чем система сможет ответить или притушить те или иные позиции меню. Это в такой же степени проверка отношения производителей программных продуктов к деталям, в какой и проверка самого программного продукта. Если при этом обна- ружится большое число отказов, вы обоснованно можете полагать, что и качество самого программного обеспечения достаточно низкое. Если вы и дальше пожелаете продолжить проверку, то тестирование такого компонента пойдет в соответствии со строками любого класса. Даже если компо- нент построен на базе нескольких классов, среди них обычно имеется главный класс, через который пользователь получает доступ к компоненту. Тестирование следует проводить на базе как раз этого класса. Изучение конкретных примеров использования в рамках приемочных испытаний Рассмотрим серийно выпускаемый компонент пользовательского интерфейса Grid и проведем некоторые исследования, чтобы определить, как его следует тес- тировать перед применением в конкретном приложении9. На рис. 6.20 показан сценарий жизненного цикла, представляющий собой описание одного из конкрет- ных случаев использования компонента, который может быть употреблен для по- строения тестовых случаев определенного типа. Сетка JavaBean отображает информацию в виде строк и столбцов. Она дает возможность пользователям производить выборку представленной информации, манипулировать ею и сохранять ее. Рассматриваемый программный продукт со- держит 4 определения интерфейсов (реализованные в виде абстрактных классов C++) и 10 определений общедоступных классов. * Создайте формальную спецификацию, если таковая отсутствует. 9 Мы используем фактически существующий продукт, однако здесь преднамеренно изменяем его назва- ние во избежание ненужных юридических осложнений.
Глава 6. Тестирование взаимодействия и функционирования компонантов План тестирования компонентов Имя компоненте Grid Идентификационный номер СТ-007 Резработчик (и): Анонимный Тестировщик (и): John D. McGregor Назначение данного компонента Данный компонент предназначен для использования в интерфейса пользова- теля в приложениях, разрабатываемых компанией. Он является частью библио- теки, приобретенной у компании NoTest Software Company («Компания, которая на проводит тестирования программного обеспечения»]. Требования целенепревленной проверки Данный компонент представляет собой завершенный программный продукт, однако никакая проектная документация к библиотеке нв прилагаатся. Какая бы то ни было цалвнаправлвнная проверка навозможна. Построение и сохренение тестовых наборов Тестовые наборы должны быть подготовлены а соответствии со стандартами проекта. Класс GridTester должен содержать тестовый драйвер. В этом классе должны быть предусмотрены опарвции, обеспечивающие прогон функциональ- ных и структурных тестовых случаев, а также тестовых случаев, проверяющих взаимодвйствиа и функционирование компонантов. Отчеты о результатах те- стирования должны соответствовать проектным стандартам. Пользователи должны иметь возможность использовать тестовый класс в своих приложениях в качестве тестов для проверки взаимодействий. Тестовые случаи, ориентированные не спецификацию Постройте прад- и постусловия для каждого метода а документированном интерфейсе API. Выполните тестовые случаи для каждого раздела постусловий каждого мвтодв. Соотватствующив тестовые случаи вы найдвтв в класса GridTeater. Тестовые случен, ориентировенные нв реализецию Доступны только двоичные файлы. Тастированив, ориентированное на реали- зацию, невозможно. Тестовые случаи, предназначенные для тестироввния взвимодайствия и функционирования компонентов Чтобы определить в типовом приложении объекты, с которыми класс Grid будет взаимодействовать, следуйте сценариям использования, аналогичным сцена- рию, показанному на рис. 6.20. Постройте и выполните твсты таким образом, чтобы Grid мог взаимодействовать с этими объектами. Тасты хранятся в классе GridTester. Тестовые случаи, ориентировенные на состояния Документация нв содержит диаграмм состояния, к которым можно было бы прибегнуть. Выполните подстановку сцвнариаа, в которых даны опрадвлвния жизненных циклов случаев использования. Сценарий выборки показан на рис. 6.20. Эти тасты могут быть комбинацией других тестовых случаев, ориенти- рованных на спецификацию. Рисунок 6.19. План тестирования компонентов комплектующего модуля Grid
Главв 6. Твстироввние взаимодействия и функционировения компонентов Компонент Grid предлагвет список системных атрибутов. В отображении, состоящем из трвх столбцов, поквзвны имя атрибута, текущее значение и знвчвние по умолчанию. Пользоввтель выбирввт атрибут Screen Color и редактирует текущвв знвчвние. Затем пользователь выбирввт действие Seve (Сохранить] из мвню File (Фвйл) с тем, чтобы сохранить информацию в модуле Grid. Рисунок 6.20. Сценарий жизненного цикла В эти общедоступные классы вложены дополнительные классы. Два из десяти общедоступных классов, а именно, GridMain и GridBigAdapter, принадлежат к числу тех, которые разработчики применяют для интегрирования компонент в собственные приложения. Соответствующая документация содержит стандартные HTML-страницы JavaDoc. Эти страницы содержат комментарии, помещенные туда разработчиками классов, однако ничего, что касалось бы рассматрива- емого «компонента» в них нет. Каждый метод представлен в форме int compareTo(Java.iang.Object another Object). Метод compareTo() возвращает значение типа int и требует один параметр anotherObject типа Object. Никакой информации касательно каких-либо ограни- чений, накладываемых на параметр anotherObject, или каких-либо указаний отно- сительно диапазона возвращаемого значения, не существует, хотя наши аналити- ки установили, что он может принимать только три различных значения. Класс GridMain содержит более сотни методов. Многие из них относятся к числу простых методов средств доступа, в то же время большое количество этих методов являются методами модификаторов, которые устанавливают конкретные значения атрибутов объектов этого класса. И хотя тестирование может оказаться весьма трудоемкой процедурой, соответствующий компонент предоставляет раз- нообразные функциональные средства, и, следовательно, открывает пути для про- ведения тщательного тестирования, что в свою очередь поможет существенно сэ- кономить усилия одновременно многих разработчиков, объекты которых будут взаимодействовать с данным компонентом. Приемочные испытания представляют собой некоторую комбинацию элемен- тов тестирования классов и тестирования взаимодействий. Следовательно, мы за- интересованы в создании шаблонов взаимодействия этого компонента с осталь- ными частями системы, равно как и в построении спецификаций отдельных методов в интерфейсах этого компонента. Этот компонент соответствует стандар- тному проектному шаблону пользовательского интерфейса среды Java. Он исполь- зует класс Adapter для поддержки создания объектов класса Listener, необходимо- го для захвата различных типов событий. Для достижения эффективности тестирование взаимодействий этого компонента должно следовать этому шаблону. Прежде всего, займемся анализом. Класс Grid (координатная сетка)- это преж- де всего коллекция. Он содержит в себе и отображает данные, а также поддержи- вает слабое взаимодействие с объектами, которые он удерживает. Больше всего он взаимодействует с генераторами событий. Взаимодействие, которое он поддержи- вает со своим содержимым (т.е. с удерживаемыми объектами), заключается в том,
Глааа 6. Тестирование взаимодействия и функционирования компонентоа что он отображает их, осуществляет их поиск и сохранение и направляет события в их адрес. Другой тип взаимодействий возникает, когда один из объектов высту- пает с требованием, чтобы сетка передала ему объект, который хранится в некото- рой конкретной ячейке. Выдается ли объекту, обратившемуся с запросом, клон? Ссылка? Предохраняет ли это действие сетку от удаления в процессе сборки му- сора? В число нескольких простых взаимодействий, для выполнения которых, соб- ственно говоря, и была задумана эта сетка, входят следующие: 1. На конкретной ячейке сетки производится щелчок кнопкой мыши. 2. На конкретной ячейке сетки производится отпускание кнопки мыши. 3. На конкретной ячейке сетки производится двойной щелчок кнопкой мыши. Потребуется построить средство тестирования, представленное специальным классом Adapter, который ведет прослушивание такого рода событий и по мень- шей мере регистрирует время возникновения события и время его обработки. Процедура тестирования должна автоматически создавать события для различных действий и передавать эти события в сетку. Также потребуется дать оценку выте- кающему из всего этого поведению сетки. Более полное тестирование взаимодействия предусматривает проверку полно- го жизненного цикла сетки. Составьте несколько тестовых драйверов (сценариев) жизненного цикла, которые дают краткое описание типичных случаев использова- ния этого компонента, как следует из рис. 6.20. Средство тестирования порождает экземпляры класса Grid с различными типами данных из текущего приложения в ячейках сетки. Средство тестирования должно дать сетке команду выполнить счи- тывание их содержимого из памяти и его отображения. Тестировщик должен вы- полнить ряд манипуляций с мышкой. Другой объект должен затребовать и удер- живать в памяти содержимое, по меньшей мере, одной ячейки в таблице. Средство тестирования должно дать команду сетке сохранить эти данные и в ко- нечном итоге уничтожить сетку. Проверка правильности результатов тестирова- ния требует, чтобы тестировщик наблюдал за визуальным поведением сетки по мере создания все новых событий и проверял, может ли модуль сборки мусора удалить сетку, в то время как объект поддерживает ссылку на один из объектов, представляющего собой содержимое сетки. СОВЕТ Используйте базовую логику программ-примеров стороннего производителя в качества основы для индивидуальных тестовых случаев. Мы используем стандар- тный тестовый драйвер и затем строим тестовые случаи, начиная с основных прогреммных кодое программ-примеров. Попутно во время приемочных испытаний мы обнаружили несколько проблем. Эти проблемы были переданы на рассмотрение компании, разрабатывавшей рас- сматриваемый компонент, и были устранены в последующих версиях этого про- граммного продукта.
Глава 6. Тастироааниа взаимодействия и функционирования компонентов Тестирование протоколов По мере того как некоторый объект вступает во взаимодействие с другими объектами, увеличивается количество сообщений, которые он получает. В соот- ветствии со спецификацией, эти сообщения должны быть упорядочены в опреде- ленную последовательность. Тестирование по протоколу проверяет реализацию на соответствие спецификации. Различные протоколы, в которых принимает участие тот или иной объект, могут быть логически выведены из пред- и постусловий, регламентирующих выполнение отдельных операций, объявленных в классе этого объекта. Идентифицирующая последовательность вызовов методов, в которую объединяются методы, чьи постусловия удовлетворяют предусловиям следующего метода, образует протокол. Такие последовательности намного легче обнаружить на диаграмме состояний конкретного класса, нежели искать их, сопоставляя пись- менные формулировки пред- и постусловий. Тестовый набор, предназначенный для тестирования взаимодействий, содер- жит тесты каждого протокола. По существу, это особая форма тестирования жиз- ненного цикла. Каждый протокол представляет собой жизненный цикл тестируе- мых объектов классов в сочетании с другими классами. Каждый протокол соответствует последовательности состояний, которая начинается с исходных со- стояний двух объектов (как показано на диаграммах этих двух классов), из после- довательностей состояний каждого объекта и завершается заключительными состояниями (опять-таки, обозначенными на диаграммах состояний). Соответ- ствующий тестовый пример проводит эти два объекта через одну полную после- довательность методов. Рассмотрим класс Timer и диаграмму его состояний, показанную на рис. 2.19. Протокол можно обнаружить путем отслеживания этой диаграммы состояний. Один из протоколов можно сформировать, если отправить одно или большее чис- ло сообщений attach(...), за которым следует сообщение enable(), сообщение disable() и в завершение, сообщение delete(). Благодаря этому получаем тестовый случай жизненного цикла. Этот протокол является эффективным средством тес- тирования объекта в части его взаимодействий с клиентскими объектами. Тестовые шаблоны Тестовые шаблоны - это проектные шаблоны, предназначенные для тестирова- ния программных продуктов. Тестовые шаблоны [GHJV94] захватывают и много- кратно используют знания из области проектирования программного обеспече- ния, которые получили широкое распространение в среде проектировщиков объектно-ориентированных программных продуктов. Каждый шаблон представ- ляет собой конкретную конфигурацию взаимодействия некоторой совокупности объектов, которые образуют в масштабах всей разработки определенный кластер. Описание шаблона содержит требования к контексту, в котором этот шаблон дол- жен рассматриваться, совокупность факторов, которые играют существенную роль в анализе, проводимом с целью выбора компромиссных решений, а также инст- рукции, как построить соответствующие объекты. Для описания этих шаблонов
Глввв 6. Твстироввнив взвимодвйствия и функционироввния компонентов применяются те же форматы, которые используются в среде разработчиков, одна- ко при этом некоторым разделам описания придается особый смысл. Мы успешно воспользовались идеей связывания тестовых шаблонов с конк- ретными проектными шаблонами. Когда разработчик прибегает к помощи конк- ретного проектного шаблона для структурирования некоторой части системы, тес- тировщик (в роли которого может выступать другой разработчик) знает, какой тестовый шаблон следует использовать для структурирования тестовых кодов. В основе компонента Grid лежит проектный шаблон Listener, который соотно- сится с обобщенным проектным шаблоном Observer при включении обработки со- бытий в его интерфейс GUI (Graphical User Interface — графический интерфейс пользователя). В следующем разделе рассматриваются ассоциированные тестовые шаблоны. Тестовый шаблон Listener Цель Существует необходимость провести тестирование взаимодействия объектов Listener, ActionListener и TargetObject, которые принимают участие в проектном шаблоне Listener (см. рис. 6.21). Эти взаимодействия должны быть проверены с тем, чтобы убедиться, что: каждое взаимодействие корректно устанавливает состояние каждого уча- ствующего в нем объекта каждое взаимодействие есть некоторое законченное целое в том смысле, что каждый объект, который должен в нем участвовать, в нем участвует каждое взаимодействие согласуется со спецификацией участвующих в нем объектов. Объект Listener передается объекту, который принимает события. Listener «проявляет интерес» только к некоторому множеству событийных объектов. Объект ActionListener направляют зарегистрированному объекту Listener только те события, для которых Listener зарегистрирован. Каждый объект Listener ассоции- рован с некоторым экземпляром класса TargetObject. Когда Listener получает со- бытие, он выполняет соответствующие действия над своим целевым объектом. Это действие определяется как метод в классе Listener. Зарегистрировать (specializedListene?^ ActionListener 4--------х Уведомить 4--------------------у Действие, специфичное^'---^/ . \ для конкретного приложения Targetobject 1 Рисунок 6.21. Концептуальные взаимодействия в шаблоне Listener
Глава 6. Тестирование взаимодействия и функционирования компонентов Контекст Проектный шаблон Listener интенсивно используется в среде Java, в то же время эквивалентные шаблоны обработки событий применяются во всех объект- но-ориентированных языках. Такой шаблон, в частности, используется в контек- сте пользовательского интерфейса. Большинство программ в Java содержит боль- шое число экземпляров шаблона Listener. В классе Listener очень мало собственного кода. Разработчики обычно определяют исходный код, используя механизм анонимного класса. Это обстоятельство существенно осложняет авто- номное тестирование этого механизма. Факторы Существует несколько факторов, которые сдерживают разработку тестовых классов: Модификация промышленного программного обеспечения с тем, чтобы приспособить его для целей тестирования, требует прогона дополнительных тестов после того, как это серийное программное обеспечение будет возвра- щено в свое первоначальное состояние. По этой причине мы предпочитаем не вносить изменений в приложение с целью его тестирования. Тестовый класс должен знать, когда объект Listener получает события. Это можно сделать, если заставить тестовый класс генерировать события либо регистрироваться для тех событий, которые были использованы для тести- рования класса Listener. Участвующие объекты уже прошли тестирование на уровне классов. Следо- вательно, методы доступа и модификации индивидуальных объектов пользуются доверием и могут быть задействованы во время выполнения этого теста. Малые размеры каждого подкласса класса Listener и большое число классов Listener, используемых в приложениях, требует, чтобы тесты можно было легко создавать и доставлять в новый класс Listener. Решение Класс TestListener создает среду, в которой происходят взаимодействия объек- тов, представленных в проектном шаблоне Listener, и производятся наблюдения за этими взаимодействиями. Объект TestListener порождает экземпляры этого шаблона. Объект TestListener может генерировать любое количество событий, для которых способен регистрироваться конкретный объект Listener. По существу, в такой ситуации имеет место один тип тестового случая. Собы- тие порождается объектом TestListener и передается объекту ActionListener. Если объект ActionListener работает правильно, то это событие направляется всем заре- гистрированным объектам Listener. Объекты Listener вызывают специальные действия в свои объекты TargetObject. Объект TestListener регистрируется с ActionListener, так что он получит события практически в то же время, когда про-
Глава 6. Тестирование взаимодействия и функционирования компонентов исходит тестирование объекта Listener. Затем объект TestListener проверяет Targetobject, чтобы определить, произошли ли и там ожидаемые изменения. Проектирование Экземпляр класса TestListener после наследования от ActionListener может ре- гистрироваться с объектом ActionListener. Затем он получит уведомление о конк- ретном событии и будет знать, что пора активизировать тестирование объекта SpecializedListener (см. рис. 6.22). Конкретный пример Рассматриваемый шаблон может быть применен к классу TimeObserver игры «Кирпичики» и к ассоциированным с ним классам (см. рис. 6.23). Итоговый контекст Промышленные классы, участвующие в шаблоне, прошли тестирование на предмет взаимодействия друг с другом. Были построены последовательности тес- товых классов и тестовые случаи, которые могут, возможно, после незначительной модификации, быть использованы для различных типов событий. С помощью подхода, предусматривающего применение таких шаблонов, можно достаточно недорого тестировать новые события и новых слушателей. РисуНОК 6.22. Концептуальные взаимодействия в шаблоне Listener, расширенные с целью включения тестирования РисуНОК 6.23. Создание экземпляров шаблона Listener, расширенного с целью проверки датчиков времени в игре «Кирпичики»
Глава 6. Твстированиа взаимодействия и функционирования компонентов Тестирование исключительных ситуаций Исключительная ситуация представляет собой альтернативный путь возврата из метода, который не обязательно переходит на выполнение следующего после вызова этого метода оператора. Исключительные ситуации обладают достоинства- ми в двух отношениях: Значение, возвращаемое в исключительной ситуации, есть объект, который может быть произвольно сложным. Точки, в которых генерируется исключительная ситуация, меняются в зави- симости от глубины иерархии агрегации. Многие проектировщики интерфейсов используют исключительные ситуации (или просто исключения) для обработки сбойных ситуаций, которые могут воз- никнуть в процессе обработки данных. Исключительные ситуации представляют собой хорошую альтернативу кодам возврата, и в ряде случаев могут уменьшить объем программного кода, необходимого для обработки кодов возврата. В то же время исключения полезны при обработке исключительных условий, которые возникают во время обработки данных, фактически не связанных с ошибками. Разрабатываемый нами проект игры «Кирпичики» использует исключения для за- вершения игры в ситуациях, когда либо запас шайб израсходован, либо разрушен последний кирпич. На рис. 6.24 показано, как исключительные ситуации или коды возврата могут использоваться в C++ для решения проблемы считывания из входного потока. В то время как прототип метода readlnt(int) подтверждает документально, что будут генерироваться только исключительные ситуации класса ReadEnror (и его производных классов), для C++ не требуется функция (или функция-член), воз- вращающая список исключений, которые он может сгенерировать. При тестирова- нии (и вполне возможно, при разработке!) это обстоятельство порождает пробле- му в том смысле, что постусловия могут не пройти тестирования в полном объеме. Всегда существует возможность того, что в контексте выполнения некото- рой функции может сгенерироваться неожиданное исключение, возможно, сис- темного уровня. Следовательно, применение в разработках на языке C++ исклю- чительных ситуаций с целью полного описания любого интерфейса, который использует исключения, можно признать хорошей практикой. Исключительные ситуации, возникающие в процессе тестирования, приводят к появлению двух различных перспектив. Во-первых, на уровне тестирования класса основное внимание уделяется проверке, на самом ли деле в соответствую- щих ситуациях методы этого класса генерируют исключения, как это утверждает- ся. Эту задачу можно решать в рамках обычного тестирования класса, поскольку каждая потенциальная генерация исключения должна быть сформулирована в виде соответствующего раздела постусловий. В классе PuckSupply должны быть тесты, которые позволяют определить, сгенерировал ли исключение объект OutOfPuck (запас шайб исчерпан) в ситуации, когда в запасе не осталось ни од- ной шайбы.
Глава В. Тестирование взаимодействия и функционирования компонентоа // Код возврата enum Status {OK,FILE_CLOSED,BAD_DATA); Status readlnt(int Sdata); // Считывает целое значение из стандартного входного потока, // запоминает их в переменной data и возвращает ОК. // Если целое значение считать невозможно, то возвращается // сообщение о возникновении проблемы. void main () { int count; switch (readlnt(count)) { case OK: process(count); break; case FILE_CLOSED: / / Выполнить соответствующее восстановительное / / действие или функцию завершения ... break ; case BAD_DATA: // Выполнить соответствующее восстановительное // действие или функцию завершения ... break ; ) } // Исключительные ситуации class ReadError {}; class FileClosedReadError: public ReadError {) ,- class BadDataReadError: public ReadError {}; void readlnt(int Sdata) throw (ReadError); // Считывает целое значение нз стандартного входного потока и // запоминает их в переменной data. Если операция считывания // завершится неудачно, генерируется одно из исключений // FileCloseReadError (Ошибка считывания - закрыт файл) // или BadDataReadError (Ошибка считывания - неправильные данные) void main () { int count; try { readlnt(count); process(count); } catch (FileClosedReadError) { // Выполнить соответствующее восстановительное // действие или функцию завершения } catch (BadDataReadError) { // Выполнить соответствующее восстановительное / / действие или функцию завершения } } Рисунок Б,24. Структура программы обработки ошибок, возвращающей программные коды и методы обработки исключительных ситуаций
Главв 6. Твстированиа взаимодействия и функционирования компонентов Критерий покрытия требует, чтобы класс генерировал каждое исключение, предусмотренное спецификациями методов. Требуется, по меньшей мере, один тестовый пример на каждое исключение. Тестовый драйвер устанавливает в тестируемом объекте такие условия, кото- рые приводят к возникновению исключительной ситуации. Этот драйвер заготав- ливает испытательный блок, внутри которого специальный стимул вызывает ме- тод, который и генерирует конкретное исключение. Это сообщение захватывается тестовым драйвером, который проверяет, что исключение выполнено корректно. Поскольку исключения представляют собой объекты и принадлежат соответству- ющим классам, операторы, осуществляющие захват, могут воспользоваться систе- мой контроля типов, чтобы проверить, что выбран корректный тип исключения. Во-вторых, тестирование взаимодействий, проводимое во время интегрирова- ния, определяет, были ли исключения, сгенерированные в нужное время, получе- ны в нужном месте. Это тестирование исследует взаимодействия порождающего объекта, инициирующего последовательность вызовов методов (инициирующий объект), которые приводят к исключению и захватывают его, и объектом, который попадает в исключительное состояние и собственно генерирует исключение (гене- рирующий объект). Например, когда в игре “Кирпичики” выдается сообщение OutOfPuck, захватывает ли его какой-нибудь объект? Происходит ли его захват в требуемом месте? Инициирующий объект отделен от объекта PuckSupIy, который фактически генерирует исключение, несколькими уровнями агрегирования. В этом случае тестовый драйвер порождает экземпляр инициирующего объек- та. В обязанности инициирующего объекта входит создание тех уровней, которые находятся ниже его в иерархии агрегирования. Тестовый драйвер побуждает ини- циирующий объект построить все эти уровни и переводит инициирующий объект в состояние, в котором исключение генерирует объект нижнего уровня. Критерий покрытия на этом уровне тестирования заключается в том, чтобы убедиться, что каждое сгенерированное исключение оказывается захваченным в требуемом месте. Обе эти точки зрения могут быть проверены на начальных стадиях разработки. В процессе целенаправленной проверки проектной модели системного уровня каждое определенное пользователем исключение, экземпляр которого построен, должно быть отслежено в диаграмме последовательностей сообщений вплоть до объекта, который захватывает это исключение, для сценария, который приводит к генерации исключения. Тестирование взаимодействий на системном уровне В какой-то момент компоненты становятся настолько сложными, что проще тестировать их в контексте самого приложения, нежели в среде, предлагаемой те- стовым драйвером. Некоторые части системы невозможно смоделировать одиноч- ным классом. Например, пользовательский интерфейс, который реализует боль- шая часть приложений, не является отдельным экземпляром конкретного класса, но представляет собой некоторую совокупность объектов, которые поддерживают ввод и вывод данных. Взаимодействия, которые допускают тестирование на сис-
Глава В. Тестироааниа взаимодействия и функционирования компонентов темном уровне, относятся к числу тех взаимодействий, которые проверяются на системном уровне. Это означает, что мы можем видеть прямые результаты тести- рования, а также то, что должна существовать прямая зависимость между пользо- вательским интерфейсом и способностью отображать результаты тестирования. Резюме В объектно-ориентированных системах взаимодействия объектов образуют ос- новную структуру программы. В процессе тестирования с целью выявления конк- ретных проблем, возникающих у объектов по мере того, как они интегрируются в более крупные и более сложные объекты, различные проблемы, в число которых входят проверка предусловий и полнота объектов, возвращаемых в ответ на полу- ченное сообщение, выявляются на ранних стадиях. Существует множество факто- ров, оказывающих влияние на каждое взаимодействие. Методы статистической выборки, такие как система OATS, предоставляют средства выбора подмножеств эффективных сочетаний этих факторов при изучении возможности использова- ния тестовых случаев. На разработку тестового программного обеспечения оказывает влияние разра- ботка программного продукта, для тестирования которого оно предназначается. В среде разработки объектно-ориентированного программного обеспечения разра- ботка промышленного программного обеспечения направляется некоторым мно- жеством стандартных шаблонов. Путем выявления и документирования стандарт- ных способов взаимодействия тестовых объектов друг с другом и тестируемым программным продуктом, менее опытные тестировщики могут воспользоваться познаниями своих более опытных коллег. Результатом будет более качественное программное обеспечение тестирования, допускающее многократное использова- ние. Упражнения 6-1 Разработайте тестовый набор для проверки исключительных ситуаций, определения которых даны в проектной документации игры «Кирпичики». 6-2 Постройте набор входных данных для метода Velocity::setSpeed. 6-3 Составьте план тестирования серийно выпускаемых комплектующих модулей. На- сколько изменится этот план, если данный комплектующий модуль является про- граммным компонентом, ориентированным на конкретную предметную область, а управление осуществляется через пользовательский интерфейс? 6-4 Выберите проектный шаблон, используемый в проекте, над которым вы трудитесь в данный момент. Найдите другие проекты, в которых он был использован. Изучите тестовые программы этих проектов. Сделайте обобщение этих программ с тем, чтобы построить собственный тестовый шаблон.
Глава Тестирование иерархий классов ► Вы хотите знать, что должно подаергаться повторному тестированию в унаследованном программном коде? См. раздел «Иерархическое инкрементальное тестирование» ► Вы хотите инкапсулировать тестовые случаи для тестирования конкретного класса, используя для этой цели РАСТ-архитектуру? См. раздел «Организация тестового программного обеспечения» ► Вы хотите знать, какие виды тестирования классов возможны, если класс абстрактный? См. раздел «Тестирование абстрактных классов» Наследование представляет собой мощный механизм, обеспечивающий много- кратное использование интерфейсов и программных кодов. В этой главе мы да- дим описание подходов к тестированию классов из иерархии наследования в ре- жиме прогона тестовых случаев. Мы покажем, как выполняется тестирование подкласса при условии, что суперкласс успешно прошел тестирование с примене- нием методов, описанных в главах 5 и 6. Мы рассмотрим различные аспекты тес- тирования подклассов, включая: адекватное тестирование подклассов; повторное использование тестовых случаев, применяемых для тестирования некоторого кон- кретного класса, при тестировании подклассов этого класса; реализацию тестовых драйверов для тестирования подклассов. Кроме того, мы обсудим проблему тести- рования абстрактных классов. По мере обсуждения будут приводиться примеры методов тестирования, написанные как на C++, так и на Java. Глава начинается с краткого обзора свойств наследования и обсуждения пред- положений относительно того, как должно использоваться наследование. Затем мы перейдем к анализу отношения наследования в перспективе тестирования с тем, чтобы определить, что необходимо подвергать тестированию в подклассе. Мы дадим описание параллельной архитектуры тестирования классов (Parallel Architecture for Class Testing — PACT), которая является способом формирования классов Tester в иерархии наследования. 277
Глава 7. Тастированиа иерархий классов Наследование в объектно-ориентированных разработках Наследование представляет собой механизм многократного использования. По всей вероятности, наследование, как механизм многократного использования про- граммных кодов, стало основным фактором, который сделал объектно-ориентиро- ванное программирование столь привлекательным в конце восьмидесятых и в на- чале девяностых годов. Однако, хороший тон программно-ориентированного программирования требует от программиста соблюдения строгой дисциплины при использовании отношения наследования - т.е., действовать необходимо в соот- ветствии с принципом подстановки (см. раздел «Принцип подстановки»). В усло- виях такой дисциплины наследование становится механизмом многократного ис- пользования интерфейсов, а не программных кодов.1 В рамках обсуждений, которые проводятся в данной главе, мы полагаем, что механизм наследования ис- пользуется только при условии соблюдения принципа подстановки. При таком условии набор тестовых случаев, предназначенный для тестирования класса, вполне подходит и для тестирования подклассов этого класса. Обычно для тести- рования подклассов применяются дополнительные тестовые случаи. Благодаря тщательному анализу инкрементальных изменений, которые определяют подкласс на основе суперкласса, тестировщикам иногда удается избежать тестирования не- которых частей подкласса в силу того, что тестовые случаи, используемые для те- стирования родительского класса, выполняют одни и те же программные коды, унаследованные производным классом без изменений. На стадиях анализа и проектирования отношение наследования, связывающее те или иные классы, может быть выражено одним из двух следующих общеприня- тых способов: 1. Как специализация некоторого класса, который уже был определен 2. Как генерализация (т.е. обобщение) одного или большего числа классов, кото- рые уже были определены Отношения наследования могут быть определены в любой момент итеративно- го, инкрементального процесса разработки. В частности, отношение специализа- ции может применяться на достаточно поздних стадиях разработок, не оказывая существенное влияние на другие программные компоненты. Подобная гибкость является одним из достоинств при использовании наследования и одной из силь- ных сторон объектно-ориентированных технологий. Сильной стороной таких технологий является и то, что программный код, обеспечивающий тестирование классов иерархии в режиме прогона тестовых слу- чаев, допускает многократное использование. Мы покажем, как планы тестирова- ния и тестовые драйверы производных классов могут быть получены из тестов для их базового класса. Наш опыт показывает, что повторное использование программных кодов непосредственно следует из повторного использования интерфейсов.
Глава 7. Тестирование иерархий классов Требования, предъявляемые к тестированию подклассов Реализация классов существенно упрощается, если она выполняется по иерар- хии сверху вниз. Аналогично, тестирование классов, образующих иерархию на- следования, в общем случае также упрощается, если оно проводится по иерархии сверху вниз. Проводя тестирование сначала в верхней части иерархии, мы полу- чаем возможность уделить внимание, прежде всего, общим интерфейсам и про- граммным кодам, и только потом сосредоточиться на разработке программных кодов тестовых драйверов для каждого подкласса. Реализация иерархии наследо- вания снизу вверх может потребовать существенных переделок при реконфигура- ции общих программных кодов в новый суперкласс. То же самое справедливо и в отношении тестовых драйверов. Чтобы упростить наши рассуждения, мы полага- ем, что тестирование иерархии наследования должно проводиться сверху вниз. Сначала рассмотрим тестирование подкласса класса, успешно прошедшего проце- дуру тестирования. Предположим, что мы хотим провести тестирование класса D, который пред- ставляет собой подкласс класса С. Предположим, что С подвергся адекватному тестированию путем прогона тестовым драйвером тестовых случаев. Что потребу- ется подвергнуть тестированию в классе D? Поскольку D наследует, по меньшей мере, часть спецификации, а также часть реализации от С, вполне естественно предположить, что какую-то часть тестового программного обеспечения класса С можно повторно использовать для тестирова- ния класса D. В том-то и все дело. Примером может служить вырожденный слу- чай, когда D наследует С и не делает никаких изменений. Следовательно, D есть эквивалент С в смысле спецификации и реализации. Класс D вообще не надо те- стировать, если мы намерены предположить, что компилятор безошибочно вы- полняет свои функции. Из этого предположения следует, что если С успешно проходит все тестовые случаи, то D также должен успешно пройти эти испытания. В более общем случае, когда D содержит инкрементальные изменения, которые делают его отличным от С, усилия, необходимые для адекватного тестирования D, могут быть снижены благодаря повторному использованию тестовых случаев ’и некоторых фрагментов тестового драйвера, предназначенных для тестирования класса С. Мы покажем, как можно без особых усилий распространить тестовую процедуру класса С на тестирование класса D. Возможности совершенствования При той поддержке, которую отношение наследования получает в среде С++ или Java, оно допускает только небольшие инкрементальные изменения при пост- роении класса D на базе класса С. Мы можем определить новый производный класс D, который отличается от С только в четырех моментах: 1. Добавить одну или большее количество новых операций в интерфейс класса D и, возможно, новые методы в D для реализации каждой новой операции.2 2 Новая операция может быть абстрактной (или, в терминологии C++, чистой виртуальной операцией), реализация которой возлагается на подклассы.
Глааа 7. Тастироааниа иерархий классоа 2. Изменить спецификацию или реализацию той или иной операции, объяв- ленной классом С одним из следующих способов: а. Изменение в D спецификации операции, объявленной в С. Ь. Перекрытие в D метода3 из С, реализующего операцию, унаследован- ную классом D. Обратите внимание на то обстоятельство, что можно воспользоваться лю- бым из этих способов, либо обоими сразу. Перекрытие метода в подклассе - это обычная практика. Можно также внести изменения в спе- цификацию той или иной операции, не внося прямых изменений в метод, который реализует эту операцию в подклассе.4 3. Добавить в D один или большее количество новых переменных экземпляра для реализации большего числа состояний и/или атрибутов. 4. Изменить инвариант класса в D. Поскольку отношение наследования может быть использовано в силу весьма разнообразных причин, предположим, что наследование применяется только в соответствии с принципом подстановки. Это предположение вполне оправдано, ибо многие преимущества объектно-ориентированного программирования проис- текают из полиморфизма. Принцип подстановки гарантирует, что объекты, при- вязанные к интерфейсам, ведет себя должным образом, благодаря чему программ- ные коды приобретают большую надежность и проще воспринимаются. Мы также полагаем, что соблюдается также и принцип сокрытия информации, в соответ- ствии с которым любые данные в объекте не принадлежат к категории общедос- тупных. Если, тем не менее, присутствуют общедоступные данные, то мы расши- рим тему обсуждения, выдвинув предположение, что операции считывания и записи общедоступных данных отвечают, соответственно, неявным операциям get (получить) и set (установить). Поскольку D наследует определенную часть своей спецификации от С, то все процедуры тестирования, ориентированные на спецификации и используемые для тестирования С, могут быть задействованы и для тестирования D. Принцип подстановки гарантирует применимость всех тестовых случаев для тестирования D. Для проверки новых операций, возможно, дополнительно понадобятся новые тестовые случаи, ориентированные на спецификации и способные выполнять проверку операций, предусловия которых ослаблены либо постусловия которых усилены, а также тестовые случаи, ориентированные на реализацию и предназна- ченные для проверки новых методов. Если инварианты классов уточняются в подклассах, потребуется также добавить в тестовый набор тестовые случаи, прове- ряющие и эти уточнения (см. рис. 7.1). 3 Мы полагаем, что если этот класс реализован в C++, то такие операции объявляются как виртуальные в базовом классе. Неудачное использование функции-члена нарушает принцип подстановки. 4 Например, в основу реализации может быть положен образец шаблона методов (Template Method pattern) [GVJ94J.
Глава 7. Тестирование иерархий классов Подкласс D добавляет новую переменную экземпляра (newVarj и новую операцию (opNew()). Подкласс D перекрывает метод ор2(), объявленный в С, поскольку эта операция имеет новую спецификацию и/или новую реализацию. Рисунок 7.1. Уточнение возможностей в отношении наследования, связывающем два класса Иерархическое инкрементальное тестирование Инкрементальные изменения, вносимые в класс С во время построения произ- водного класса D, могут быть использованы при выяснении, что потребуется под- вергнуть тестированию в D. Рассмотрим такие инкрементальные изменения в пер- спективе тестирования. Поскольку D является подтипом С, то все тесты, ориентированные на спецификации, использованные для тестирования С, приме- нимы и для D. Применимы также многие из тестов, ориентированных на реализа- цию и на тестирование взаимодействий. Мы будем употреблять термин унаследо- ванные тестовые случаи в отношении тестовых случаев, использованных для тестирования подклассов, которые были предназначены для тестирования их ба- зового класса. Путем несложного анализа можно определить, какие из унаследо- ванных тестовых случаев могут использоваться для тестирования того или иного подкласса. Как часть того же анализа, можно определить, какой из унаследован- ных тестовых случаев не должен использоваться для тестирования этого подклас- са. Мы еще раз воспроизведем здесь список инкрементальных изменений, приве- денный в предыдущем разделе, и исследуем каждое из них с точки зрения перспективы тестирования. 1. Добавить одну или большее количество новых операций в интерфейс класса D и, возможно, новые методы в D для реализации каждой новой операции. Новая операция привносит новые функциональные возможности и новые программные коды, которые должны тестироваться. Новая операция напря- мую не затрагивает существующие, унаследованные операции или методы. Мы должны дополнить тестовый набор новыми тестовыми случаями, ори- ентированными на спецификацию и предназначенными для тестирования каждой новой операции. Нам следует добавить в этот набор тестовые слу- чаи, ориентированные на проверку реализаций и взаимодействий, дабы удов- летворить требования критериев покрытия, заложенные в планы тестирования, если только операция не является абстрактной и имеет реализацию.
Глава 7. Тастирование иерархий классов 2. Изменить спецификацию или реализацию той или иной операции, объяв- ленной классом С одним из следующих способов: а. Изменение в D спецификации операции, объявленной в С. Потребуется добавить новые тестовые случаи, ориентированные на спе- цификацию, чтобы выполнить проверку этих операций. Дополнитель- ные тестовые случаи предусматривают новые вводы, которые соответ- ствуют ослабленным предусловиям, и проверяют выводы на наличие новых ожидаемых результатов, которые являются результатом любого ужесточения требований постусловий. Все еще допускается применение тестовых случаев, предназначенных для тестирования этой операции, какой она объявлена в классе С, однако требуется их повторный прогон. Кроме того, следует добавить более строгие требования постусловий к выводу каждого тестового случая, использованного для тестирования этой операции в рамках класса С. Ь. Перекрытие в D метода из С, реализующего операцию, унаследованную классом D. Для тестирования этого метода можно повторно использовать все унас- ледованные тестовые случаи, ориентированные на спецификацию. По- скольку в этом случае появляются новые программные коды, которые должны тестироваться, потребуется просмотр всех тестовых случаев, ориентированных на реализацию и на проверку и взаимодействий, их ревизия и внесение добавлений, необходимых для того, чтобы они соот- ветствовали тестовым критериям в отношении покрытий. 3. Добавить в D один или большее количество новых переменных экземпляра для реализации большего числа состояний и/или атрибутов. По всей вероятности, новая переменная добавляется в связи с появлением новых операций и/или программных кодов в перекрываемых методах, а процедура тестирования применяется с учетом новых обстоятельств. Если новая переменная не используется ни в одном из методов, никаких измене- ний вносить не нужно. 4. Изменить инвариант класса в D. Инварианты класса можно рассматривать как дополнительные постусловия для каждого тестового случая. Мы предпочитаем считать их неявными по- стусловиями и записывать тестовые случаи без явных ссылок на ограниче- ния, накладываемые инвариантами. Выход тестового случая подвергается ограничениям со стороны инвариантов — другими словами, для выхода каждого тестового случая неявно предполагается, что «и выполняются инва- рианты класса». Следовательно, если инвариант класса меняется, потребует- ся повторно выполнить прогон всех унаследованных тестов, дабы убедиться в том, что новые инварианты выполняются.
Глава 7. Тестирование иерархий классов Нет необходимости добавлять тестовые случаи, ориентированные на специфи- кации, которые остаются неизменными при переходе от базового класса к произ- водному классу. Тестовые случаи могут быть повторно использованы в том виде, в каком они есть. Мы не обязаны осуществлять прогон этих тестовых случаев, если операции, для проверки которых они предназначены, никак не изменялись, т.е. не изменялась ни спецификация, ни реализация. Однако потребуется выполнить повторный прогон любого тестового случая, предназначенного для проверки той или иной операции, если ее метод изменился косвенным путем, поскольку он ис- пользует операцию, которая сама подверглась изменениям. Для проверки таких методов могут понадобиться дополнительные тестовые случаи, ориентированные на реализацию. Мы будем называть проведение описанного выше анализа и его результаты Н1Т-тестированием (Hierachical Incremental testing — иерархическим инкремен- тальным тестированием). Упомянутым анализом можно воспользоваться для оп- ределения, какие тестовые случаи необходимо добавить в тестовый набор конк- ретного подкласса, а какие унаследованные тестовые случаи выполнять не нужно. Выявление тестовых случаев, выполнение которых не обязательно, требует опре- деленной изобретательности. На практике обычно проще и целесообразнее вы- полнять повторные прогоны всех тестовых случаев. Тем не менее, определение, какой из тестовых случаев годится для повторного использования, все же прино- сит определенную пользу. На рис. 7.2 дается обобщенное представление анализа, связанного с Н1Т-тес- тированием. В первом столбце мы относим каждую операцию, определенную для производного класса D, в категорию новых, усовершенствованных или неизмененных операций. Третий столбец указывает, затрагивают ли эти изменения тестирова- ние, ориентированное на реализацию. Таблица вводит факторы общедоступности и приватности. Приватные свойства класса не оказывают влияния на общедос- тупный интерфейс. Вхождение Нет в таблице указывает на то, что инкрементальное изменение (в строке, содержащей это вхождение) не вызывает инкрементального эффекта в те- стовом наборе — т.е. тестовые случаи, предназначенные для тестирования супер- класса, могут использоваться и для тестирования подкласса. Вхождение Да озна- чает, что для тестирования этих инкрементальных изменений в тестовый набор должны быть добавлены новые тестовые случаи. Вхождение Возможно означает, что тестировщик должен провести исследования программного кода реализации с тем, чтобы определить, требуются ли дополнительные тестовые случаи для обес- печения определенного уровня покрытия. В качестве небольшого примера рас- смотрим класс Timer (таймер) из проекта игры «Кирпичики», который моделирует течение времени в виде последовательности дискретных «тиканий». Каждое собы- тие таймера обрабатывается экземпляром класса Timer, который оповещает об этом событии другие объекты, участвующие в сеансе игры «Кирпичики». В свою очередь, эти объекты обрабатывают другое тиканье таймера. Этот аспект проекта основан на шаблоне Observer (наблюдатель) [GHJV94],
Глава 7. Тестирование иерархий клессов Инкрементальные Затрагивается ли Затрагивается ли изменения спецификация класса? реализация класса? Новая операция Общедоступная Да Возможно, если новый метод затрагивает, прямо или косвенно, унаследованные переменные Приватная Нет Да Усовершенствованная операция/ подмененный метод Общедоступная Да Возможно Приватная Нет Да Новая переменная Общедоступная Да Да (константная)* Приватная Нет Да Неизмененная операция Общедоступная Нет Возможно, если метод использует, прямо или косвенно, операцию, которая претерпела изменения. Приватная Нет Да Мы предполагаем, что любая общедоступная переменная объявлена как const (final). Мы не рекомен- дуем использовать какие-либо переменные в интерфейсе класса; вместо этого следует отдать пред- почтение операциям доступа. Рисунок 7.2. Сводная таблица усовершенствований и их результатов в условиях иерархического инкрементального тестирования (HJT-тестирования)
Глава 7. Тестированиа иерархий классов Экземпляр класса Timer играет роль субъекта, а другие объекты сеанса игры «Кирпичики» выступают в роли наблюдателей. Если проект реализован так, как показано на рис. 7.3, т.е. на базе существующих, прошедших тестирование клас- сов Subject и Observer, что соответствует предписаниям шаблона Observer, то из рис. 7.2 можно определить, что должно тестироваться в классе Timer. В частности, спецификации методов attachQ, detach() и notify() не нужно подвергать дальней- шему тестированию, поскольку их спецификации не отличаются от тех, которые были определены в классе Subject. Не нужны также и тестовые случаи, ориентиро- ванные на реализацию, поскольку из программного кода (не показан) можно оп- ределить, что изменения не коснулись потоков выполнения этих методов. Други- ми словами, эти методы не используют новых программных кодов класса Timer, либо нет никаких новых взаимодействий с другими объектами. Зато в тестовый набор необходимо добавить тестовые случаи, ориентированные на специфика- цию, чтобы выполнить тестирование новой операции tick(), которая обрабатывает события таймера, и тестовые случаи, ориентированные на реализацию, чтобы вы- полнить тестирование метода, который реализует ее. Что касается тестирования класса TimeObserver, то, как показывает HIT-тестирование, мы должны осуще- ствить тестирование перекрытой операции update() за счет добавления соответ- ствующих тестовых случаев, ориентированных на спецификацию, поскольку спе- цификация указанной операции подверглась изменению в подклассе (она описывает возможные изменения состояний конкретных подклассов). Поскольку эта операция является абстрактной в классе Observer, вхождение Возможно в HIT- таблице превращается в необходимость добавления также и тестовых случаев, ориентированных на реализацию. this->tick() Рисунок 7.3. Диаграммы классов Timer и TimerObserver
Глава 7. Твстированиа иерархий классов Проведем теперь исследование иерархического инкрементального тестирова- ния в контексте плана тестирования — другими словами, в перспективе отбора тестовых случаев. Затем оно будет рассматриваться на более высоком уровне реа- лизации. Для примера воспользуемся иерархией наследования, корнем которой является класс Sprite игры «Кирпичики» (см. рис. 7.4). Спрайт — это абстракция, представляющая любой объект, который может появиться на игровом поле неко- торой аркадной игры. Это название имеет историческое значение в предметной области аркадных игр. К некоторым атрибутам, ассоциированным со спрайтом, относятся: bitmap (битовое отображение), которое формирует визуальный образ, size (размеры), который описывает ширину и высоту битового отображения, location (местоположение) на игровом поле и bounding rectangle (ограничивающий прямоугольник), который представляет собой наименьший прямоугольник игро- вого поля, который содержит образ спрайта (если он находится на игровом поле5). Movable sprite (подвижный спрайт) — это спрайт, который способен менять свое местоположение на игровом поле. С подвижным спрайтом ассоциирована вектор- ная скорость, с которой он перемещается в текущий момент. Velocity (векторная скорость) представлена направлением движения и расстоянием, преодоленным (в единицах длины игрового поля) за единицу времени. В нашей модели конкретны- ми видами подвижных спрайтов являются риск (шайба) и paddle (лопатка). Stationary sprite (неподвижный спрайт) есть спрайт, позиция которого не меня- ется, пока он находится на игровом поле. В нашей модели примером неподвиж- ного спрайта можно считать brick (кирпич). В связи с тем, что в игре «Кирпичики» используется только один вид спрайтов, было принято решение — возможно, оп- рометчивое - не представлять неподвижные спрайтов через абстрактные классы в рамках текущего приращения. Следовательно, в нашей модели класс Brick являет- ся непосредственным наследником класса Sprite. Спецификации некоторых операций этих классов, представленных в иерархии Sprite, показаны на рис. 7.5. Тестовые случаи, ориентированные на спецификацию В условиях иерархического, инкрементального тестирования изменения в спе- цификации подкласса, которые обусловливают ее отличие от спецификации базо- вого класса, определяют то, что подлежит тестированию. Требования к тестирова- нию сведены в столбец Затрагивается ли спецификация класса? на рис. 7.2. И хотя наш анализ базируется на в некоторой степени неформальных спецификаци- ях, представленных на рис. 7.5, такая методика применима к спецификациям любой формы, включая спецификации, написанные на языке OCL или представ- ленные на диаграммах состояний. 5 В качестве примера назовем шайбу, принимающую участие в игре, и шайбу, находящуюся вне игры. Первая из них находится па игровом поле, а последняя - за пределами этого поля.
Глава 7. Тестирование иерархий классов TimerObserver Sprite < Brick CSize _size; CBitmep* _ЬКтвр_р; CPoint _pleyFieldPosition; PleyField* _plByField_p; bool JsBroken; BrickPile* _brickPile_p; stetic const int bitmepIDO; BrickColor _color; Sprite(int resourcelD, const CSize& size); Sprite(int resourcelD, const CSize& size, PleyField* plByField_p, const CPoint& pos); ~Sprite(); void tick() const CSize& size() const; CBitmep* bitmepO const; const CRect& boundingRect() const; bool overleps(const Sprite* sprite_p) const; const CPoint& position() const; void setPosition(const CPoint& position); PleyField* pleyField() const; void crumble(); Brick(const BrickColor); Brick(const BrickColor, BrickPile* brickPile_p); bool overleps(const Sprite* sprite_p) const; bool isBroken() const; void hitByPuck(Puck* puck_p); BrickColor getColor() const; stetic const CSize Size; MovebleSprite Velocity _currentVelocity; CPoint _prevPleyFieldPosition; CRect _prevBoundingRect;; bool JsMoving; Peddle stetic const CSize size; const Mouse* _mouse_p; void reverse(); void collidelnto(Sprite& sprite); void move(); Velocity getVelocity() const; void setVelocity(const Velocity& newVel); bool islnMotion() const; bool lsHeedingLeft() const; bool isHeedingRight() const; bool isHeedingUp() const; bool isHeedingDown() const; void startMoving(); void stopMoving(); void tick(); Speed getSpeed() const; Direction getDirection() const; void setSpeed(Speed newSpeed); void setDirection(Direction newDirection); void reverseX(); Peddle(Mouse* mouse_p); void hitByPuck(Puck* puck_p); void collidelnto(Sprite& sprite); <F Puck static const CSize size; bool JsDeed; Puck(); void die(); void hitByPuck(Puck* puck_p); void collidelnto(Sprite& sprite); void move(); Рисунок 7.4. Модель класса в иерархии наследования класса Sprite
288 Главе 7. Тестироввние иерврхий классов Sprite либо находится на игровом поле, либо за пределами любого игрового поля Sprite(int resourcelD, const CSize& size); pre: resourcelD отождествляется с битовым образом, a size корректно отображает высоту и ширину битового образа post: новый экземпляр содержит изображение, однако он не помещен ни на одно игровое поле Sprite(int resourcelD, const CSize& size, Playfield* playfield p.const CPoint& pos); pre: resourcelD отождествляется с битовым образом, a size точно отображает высоту и ширину битового образа, playfield_p.const не равна нулю и pos есть допустимое местоположение на игровом поле post: новый экземпляр содержит изображение и отображается на игровом поле в указанном месте MovableSprite либо находится на игровом поле, либо за пределами любого игрового поля; либо движется, либо неподвижен MovableSprite(int resourcelD, const CSize& size); pre: resourcelD отождествляется с битовым образом, a size точно отображает высоту и ширину битового образа post: новый экземпляр содержит изображение, однако он не помещен ни на одно игровое поле и неподвижен void setVelocity(const Velocity& newVel); pre: отсутствуют post: приемник обладает скоростью newVel void moveQ; pre: отсутствуют post: если не находится в подвижном состоянии, то ничего не делает, иначе перемещается в позицию, определяемую текущим положением и текущей скоростью. Если столкновение происходит в процессе с другим спрайтом S, то collidelnto(S) void collidelnto(Sprite& sprite); pre: отсутствуют post: обновляет собственное состояние и состояние спрайта с целью отобразить соударение, в котором приемник ударяет о sprite Puck либо находится на игровом поле, либо за пределами любого игрового поля; либо движется, либо неподвижен, либо участвует в игре, либо не участвует void moveQ; pre: отсутствуют post: если не перемещается и не участвует в игре, то ничего не делает, иначе перемещается в позицию, определяемую текущей позицией и текущей скоростью. Если столкновение происходит в процессе с другим спрайтом S, то collidelnto(S) void collidelnto(Sprite& sprite); pre: обновляет собственное состояние с целью изменить направление перемещения, и обновляет состояние спрайта sprite с тем, чтобы отобразить ситуацию, когда шайба ударяет о спрайт Рисунок 7.5. Неформальная спецификация одной из частей иерархии класса Sprite
Глава 7. Твстироааниа иерархий классов Прежде всего, сосредоточим внимание на классе MovableSprite в предположе- нии, что тестовые случаи для класса Sprite уже выбраны и реализованы (см. рис. 7.6).6 Класс MovableSprite привносит несколько новых операций и атрибутов, ре- ализующих движение модели по игровому полю, и перекрывает некоторые мето- ды. К числу новых операций в классе MovableSprite относится операция move(), которая обновляет позицию подвижного спрайта на игровом поле, операция setVelocity (const Velocity &), меняющая значение скорости, с которой перемеща- ется подвижный спрайт, операция isMoving() const, которая проверяет, находится ли подвижный спрайт на текущий момент в состоянии движения, и collideInto(Sprite &), которая меняет состояние подвижного спрайта, дабы отра- зить факт соударения с другим спрайтом на игровом поле. В число подмененных методов попадает и конструктор. Однако большая часть операций, объявленных в классе Sprite, наследуются без изменений. Реализация класса MovableSprite использует для сохранения атрибута скорости и признака, указывающего, движется ли спрайт, следующие две новых переменных: _currentVelocity, которая представляет собой экземпляр класса Velocity, ис- пользуемый для запоминания текущей скорости. _isMoving, которая указывает, находится ли на текущий момент подвиж- ный спрайт в состоянии движения. Когда эта переменная принимает значе- ние false, операция move() не дает эффекта. Что нам следует сделать, чтобы выполнить адекватное тестирование класса MovableSprite в условиях, когда класс Sprite успешно прошел тестирование? Ос- новой программного кода подкласса служит программный код суперкласса, кото- рый уже тестировался. Из модели класса (рис.7.4) и спецификаций операций (рис. 7.5), принимая во внимание результаты НГГ-тестирования (рис. 7.2), можно сделать следующие выводы: Инварианты, подвергшиеся изменениям, требуют, чтобы все тестовые случаи класса Sprite были использованы при тестировании класса MovableSprite и чтобы были проверены новые инварианты. Для тестирования новых операций класса MovableSprite требуется построе- ние тестовых случаев, ориентированных на спецификацию, а также постро- ение тестовых случаев, ориентированных на реализацию. Потребуется про- верить взаимодействие множества новых операций - например, установить конкретное значение векторной скорости, а затем несколько раз заставить двигаться подвижный спрайт, дабы убедиться, что он корректно реагирует на установленную скорость, либо изменить скорость и проверить, правиль- но ли выбирается направление (вверх, вниз, влево или вправо). Операции класса Sprite, методы которых не перекрывались в классе MovableSprite, не нуждаются в дополнительном тестировании. 6 К обсуждению проблемы тестирования абстрактных классов, таких как Sprite, мы обратимся в дан- ной главе несколько позже. 10
Главв 7. Твстирование иерархий классов План тестирования компонентов Имя компонента клвсс Sprite | Идентификационный номер TBS Разработчик (и): Dave Sykes Тастироащик (и): Deve Sykes Назначаниа данного компонанта Ценный клвсс представляет вбстрвкцию квждого объекта, который можвт по- явиться нв игровом полв врквдной игры. Его основное незначение состоит в установкв протоколв для всвх отличных друг от другв подклвссов спрайтов. Трабоаания целенапрааланной проаерки В силу критической ввжности данного компонента, твстировению полежат все 100% вго прогрвммного кода. Построаниа и сохранание тестоаых набороа Тестовые неборы должны быть подготовлены в соответствии со ствндвртами проекте. Клвсс SpriteTestar должен содержеть тестовый дрвйввр. В этом клвссе должны быть предусмотрены опервции, обеспечивающие прогон фун- кционвльных и структурных тестовых случаев, а также тестовых случвев, про- веряющих взвимодействие и функционироввние компонентов. Отчеты о резуль- твтвх тестирования должны соответствоввть проектным стендартвм. Тастоаыа случаи, ориантироаанныа на спецификацию Выполните тестовые случаи для квждого сочетания предусловий и постусловий каждого метода. Проверьте текже, что инвврианты рассматривеемого класса являются чвстью квждого тестового случвя. Выполните тестовый случай, ко- торый покрывввт каждый пврвход в предстввлвнии состояний. Тестоаыа случаи, ориантироаанныа на реализацию Выполните тестовые случви, которые покрывают квждую строку прогрвммного кодв квждого неабстрактного мвтодв. Тастоаыа случаи, предназначенные для тестирования азаимодайстаия и функционирования компонантоа Отсутствуют Тестовые случаи, ориентированные на состояния Никаких тестовых случввв дополнительно к тем, которые включены в тестовый набор в категории тестовых случвев, оривнтироввнных на спецификацию. Рисунок 7.В. План тестирования компонентов класса Velocity
Глааа 7. Тестирование иерархий классов Тестовые случаи, ориентированные на реализацию Столбец под названием Затрагивается ли реализация класса? на рис. 7.2, опре- деляет, что подлежит тестированию по отношению к реализации. Если соответ- ствующее вхождение содержит Возможно, то тестировщик обязан провести иссле- дование программного кода с целью установить, нужны ли дополнительные тестовые случаи. В случае класса MovableSprite лишь небольшое число методов было добавлено в целях реализации операций, осуществляющих перемещения. Методы операций, ассоциированные с позиционированием спрайта на игровом поле, не перекрывались. Метод tick() перекрыт и теперь он может заставить под- вижный спрайт изменить свое положение на игровом поле в соответствии с теку- щим значением его векторной скорости. Взяв за основу информацию, представленную на рис. 7.2, можно сделать сле- дующие замечания в отношении тестирования, ориентированного на специфика- цию класса MovableSprite: Для тестирования методов size(), bitmap(), boundingRec(), overlaps(), position)), setPosition() и playfield() не нужны никакие новые тестовые слу- чаи. После исследования программных кодов и после того, как будет уста- новлено, что их взаимодействие с методом tick() не имеет места, мы прихо- дим к заключению, что в повторном прогоне этих тестовых случаев необходимости нет. Тестовые случаи, ориентированные на реализацию, нужны для тестирова- ния всех новых методов, таких как reverse(), move() и иже с ними. Тестовые случаи, ориентированные на реализацию, необходимые для тести- рования реализации абстрактного метода tick(). Нам также потребуются тестовые случаи, предназначенные для тестирования взаимодействий и связанные с проверкой правильности реализации методов startMoving() и stopMoving(), а также для исследования влияния эффекта, полу- чаемого от изменения состояния, на другие операции, такие как tick() и reverse(). Организация тестового программного обеспечения Отношение между требованиями к тестированию подкласса, такого как, напри- мер, MovableSprite, и требованиями к тестированию базового класса поддержива- ет отношение наследования между классами типа Tester, описанное в главе 5. Другими словами, можно разработать тестовый драйвер для подкласса D, постро- ив его класс Tester по образцу класса Tester для С, суперкласса класса D. На рис. 7.7 показана структура, которую мы называем РАСТ-архитектура (Parallel Architecture for Class Testing — Параллельная архитектура тестирования классов) [McGr97], Структура, определенная РАСТ-архитектурой для иерархии класса Sprite, приводится на рис. 7.8. 10*
Глава 7. Твстированиа иерархий классов Рисунок7.7. РАСТ-архитектура драйвер емый класс Рисунок 7.8. РАСТ-архитектура для иерархии класса Sprite
Глава 7. Тастированив иерархий классов Использование РАСТ-архитектуры снижает трудозатраты, необходимые для те- стирования нового подкласса. Организация методов тестового случая и методов тестового сценария, описание которых были даны в двух предыдущих главах, уп- рощает тестирование подклассов, позволяя вызывать их в подклассах класса Tester. Если операция уточнялась в подклассе, то соответствующие методы тесто- вого драйвера могут быть использованы в подклассе и уточнены в ситуации, когда возникает необходимость отразить новые предусловия, постусловия и/или реали- зацию. В такой подкласс класса Tester могут быть добавлены методы тестового случая и методы тестового сценария, используемые при проверке новых опера- ций. РАСТ-архитектура обеспечивает четкую организацию, которую совершенно нетрудно осуществить. Корнем РАСТ-иерархии служит абстрактный класс Tester, описание которого приводится в главе 5. Каждый подкласс класса Tester должен предоставить реали- зации абстрактных операций и перекрывать методы любых других операций. Каж- дый подкласс имеет такую же базовую организацию, которая описывалась в главе 5: методы тестовых случаев, методы, соответствующие каждому конструктору, предназначенные для построения тестируемых объектов, а также методы, предназ- наченные для построения тестируемых объектов в некотором конкретном состоя- нии. Эти классы достаточно просты для реализации при условии отбора соответ- ствующих тестовых случаев. Тестирование абстрактных классов Обычно мы ожидаем, что корневым классом некоторой иерархии классов (а, возможно, и некоторые его прямые потомки) оказывается абстрактный класс. В этом разделе мы рассмотрим возможные способы тестирования абстрактных клас- сов, подобных, скажем, Sprite и MovableSprite. Тестирование классов в режиме прогона тестовых случаев требует построения экземпляров каждого класса. Боль- шинство языков объектно-ориентированного программирования, в том числе C++ и Java, поддерживают синтаксические средства идентификации абстрактных классов. Семантика языков программирования обычно препятствует построению экземпляров абстрактных классов. Это обстоятельство представляет собой пробле- му при тестировании, поскольку мы не сможем построить требуемые экземпляры. Мы выделили несколько различных подходов к тестированию абстрактных клас- сов. Каждому из них присущи сильные и слабые стороны. Мы представляем эти подходы в контексте тестирования абстрактного класса Sprite. Один из подходов к тестированию абстрактных классов, подобных Sprite, ил- люстрируется на рис. 7.9. В рамках этого подхода конкретный подкласс класса Sprite определяется исключительно для целей тестирования. На упомянутом ри- сунке мы присваиваем этому классу имя ConcreteSprite. Реализация класса ConcreteSprite определяет заглушку для каждой абстрактной операции в классе Sprite. Если один или большее число методов в классе Sprite являются шаблон- ными методами, использующими одну из абстрактных операций класса Sprite, то этот абстрактный метод должен быть снабжен соответствующими заглушками -
Глава 7. Тастированиа иерархий классов Рисунок 7.9. Один из подходов к тестированию абстрактного класса в режиме прогона тестовых случаев такими, что он эффективно удовлетворяет постусловиям операции, для которой поставлены заглушки. В некоторых экземплярах такая цель достигается без осо- бых усилий. В случае более сложных операций написание удовлетворительных заглушек требует значительных усилий. Как только конкретный класс реализован, библиотечный метод objectUnderTest() класса Tester - например, SpriteTester - создает экземпляр конкретного подкласса. Один из недостатков такого подхода заключается в том, что реализация абст- рактных методов не может быть достаточно просто распространена на абстрактные подклассы без использования отношения множественного (многократного) насле- дования. Рассмотрим, например, что необходимо для тестирования абстрактного класса MovableSprite, который является подклассом абстрактного класса Sprite (как следует из рис. 7.10). В идеальном случае класс ConcreteMovableSprite может повторно использовать заглушки, построенные в классе ConcreteSprite. Однако, это повторное использование не может быть выполнено сразу, если ConcreteMovableSprite наследуется как от MovableSprite, так и от ConcreteSprite. В то время как множественное наследование в C++ возможно, оно же невозмож- но либо не поощряется для использования в этих целях в большинстве объектно- ориентированных языков программирования. Второй подход к тестированию абстрактного класса заключается в том, что он тестируется как часть его прямого потомка. В контексте тестирования класса Sprite это будет сделано в рамках тестирования, скажем, класса Puck. Такой под- ход устраняет необходимость разработки дополнительных классов для целей тес- тирования, правда, за счет увеличения сложности тестирования этого конкретного класса. При написании класса Tester для конкретного класса, такого как Puck, мы должны позаботиться о реализации класса Tester для каждого предка, тем самым снабжая каждый из них соответствующими и корректными тестовыми случаями и методами тестовых сценариев. На практике это сделать нетрудно. Тщательный анализ программного кода в классах Tester для абстрактных классов позволяет снизить трудозатраты, необходимые для того, чтобы получить корректную реали-
Главв 7. Твстироввниа иерархий классов Рисунок 7.10. Еще один подход к тестированию абстрактного класса в режиме прогона тестовых случаев зацию класса Tester для конкретного подкласса. Если конкретный подкласс ус- пешно проходит все тестовые случаи, вполне можно предположить, что и пред- шествующие ему классы успешно пройдут свои тестовые случаи. Тем не менее, это предположение далеко не всегда оправдывается. Например, конкретный подкласс мо- жет перекрыть тот или иной метод, определение которого дано в одном из абстракт- ных классов. В упомянутой ситуации для тестирования этого метода должен исполь- зоваться другой конкретный подкласс, не осуществляющий такого перекрытия. Ни один из этих подходов нельзя считать полностью удовлетворительным. Мы провели исследование третьего подхода, основанного на прямой реализации вер- сии абстрактного класса для целей тестирования. Другими словами, мы пытались отыскать способ записывать исходные программные коды класса в таком виде, чтобы их можно было откомпилировать либо как абстрактный, либо как конкрет- ный класс. Тем не менее, ни подход, основанный на наследовании с редактирова- нием, ни подход, основанный на условной компиляции7, не дали хороших ре- зультатов ввиду сложности полученного программного кода и трудности его ’ Наследование с редактированием сводится к клонированию кода путем копирования существующих исходных кодов с последующим редактированием полученной копии, т.е. добавления, удаления или изменения его функций. В подобных случаях мы копируем программные коды абстрактного класса в отдельный файл, а затем реализуем любые абстрактные операции с целью конкретизации этого клас- са. Естественно, основной недостаток наследования с редактированием заключается в том, что лю- бые изменения, внесенные в первоначальную версию исходного программного кода, не распростра- няются автоматически на все клоны исходного кода.
Глава 7. Тестирование иерархий классов восприятия, чем, собственно говоря, и объясняется его повышенная чувствитель- ность к ошибкам. В качестве приемлемой альтернативы следует рассматривать тестирование аб- страктного класса с применением целенаправленной проверки вместо тестирова- ния в режиме прогона тестовых случаев. Ревизии допустимы, поскольку в типич- ных абстрактных классах редко выполняются, а зачастую вообще отсутствуют, реализации абстрактных операций. Наш собственный опыт показывает, что обще- доступные интерфейсы абстрактных классов довольно быстро приобретают устой- чивость. Конкретными операциями являются в первую очередь инспекторы или простые модификаторы, для тестирования которых вполне достаточно инспек- ции. Для проверки конструкторов и деструкторов зачастую одной инспекции не- достаточно — например, конструктор класса Sprite предусматривает поиск побито- вого изображения, ассоциированного с идентификатором ресурса. Дабы обойти этот недостаток, можно воспользоваться условной компиляцией, основанной, например, в C++ на препроцессорных директивах #if defined (TEST), #elseif и #endif, обеспечивающих сохранение программных кодов абстрактной и конкретной версий одного и того же класса в одном и том же исходном файле. Применение условной компиляции существенно затрудняет чтение и сопровож- дение программных кодов. Мы все-таки предпочитаем выполнять тестирование конкретных классов в режиме прогона тестовых случаев, поскольку это упрощает регрессионное тести- рование. РАСТ-архитектура предоставляет определенные преимущества при тес- тирования семейств классов, и в силу этого обстоятельства мы намерены разраба- тывать классы Tester с целью тестирования абстрактных классов. В нашей практической деятельности мы отдаем предпочтение второму из рассмотренных выше подходов, т.е. тестированию абстрактных классов совместно с первым кон- кретным подклассом. Подобный подход достаточно прост и требует сравнительно небольших затрат ресурсов на разработку программ тестового программного обес- печения. В этом случае существенно облегчается регрессионное тестирование. Резюме Многие специалисты придерживаются мнения, что отношение наследования представляет собой мощное средство анализа и разработки. Оно обеспечивает ис- ключительно мощные инструментальные средства тестирования в условиях, когда применяется в процессе разработки, который соответствует принципу подстанов- ки. Отношение наследования сохраняется и для тестовых случаев. Тестовые набо- ры для подклассов могут быть взяты из тестовых наборов их родительских клас- сов. На базе анализа внесенных изменений мы решаем, какие тестовые случаи следует добавить в тестовый набор, какие тестовые случаи выполнить повторно, в какие тестовые случаи потребуется внести изменения, а какие из них вообще не нужно выполнять. РАСТ-архитектура предоставляет в распоряжение тестировщи- ков предельно полезный способ организации тестовых драйверов, осуществляю- щих тестирование классов.
Глава 7. Твстироввние иерархий классов Упражнения 7-1 Основной идей, характеризующей иерархическое, инкрементальное тестирование (HIT) является предположение, что после проведения несложного анализа можно избежать повторного тестирования программного кода, который уже подвергался тестированию. Выберите классы из иерархии наследования в текущем проекте и выполните Н1Т-анализ. Дайте оценку трудозатрат на реализацию набора тестовых случаев, достаточного для тестирования в полном объеме всех классов иерархии, и сравните полученную оценку с реальными трудозатратами на выполнение Н1Т- анализа. 7-2 Напишите тестовый драйвер для абстрактного класса с небольшим количеством операций. Дайте оценку трудозатратам, необходимым для выполнения тестирования этого класса, воспользовавшись всеми тремя подходами, описание которых изло- жено в конце данной главы. 7-3 Реализуйте иерархию РАСТ-архитектуры классов Tester для классов одной из иерар- хий наследования, к которой вы имеете доступ. Внесите в абстрактный класс Tester, описанный в главе 5, такие изменения, которые позволили бы использовать его в своей тестовой среде. Усовершенствования могут включать оценку распределения памяти, сбор информации о временных соотношениях и более сложную регистрацию результатов.
Глава Тестирование распределенных объектов ► Вы хотите дать определение стандартов для описания распределенных систем? См. раздел «Описание распределенных объектов» ► Вы хотите знать, какой смысл вкладывается в определение «путь», особенно, когда речь идет о распределенных системах? См. раздел «Тестирование путей в распределенных системах» ► Вы хотите разработать тесты, которые исследуют временные характеристики прогремм? См. раздел «Временная логика» ► Вы хотите знать, как выполнятся тестирование Internet-приложения? См. раздел «Максимальный вариант распределенной системы - Internet» В наше время лишь немногие системы разрабатываются с целью выполнения на одном процессоре в рамках одного процесса. В стремлении обрести гибкость и расширяемость многие системы разрабатываются в виде фрагментов, которые дос- таточно независимы, чтобы находиться в различных процессах, отдельно от дру- гих компонентов. Термин распределенный (distributed) обозначает систему с архи- тектурой клиент/сервер, в условиях которой клиентский и серверный фрагменты спроектированы так, что они находятся в отдельных процессах. Современные си- стемы отличаются большим разнообразием, нежели системы клиент/сервер вось- мидесятых годов прошлого столетия, в которых сервер всегда был представлен базой данных, а клиент просто запрашивал или модифицировал данные. Выпол- нение приложений обычно начинались с загрузки одного из компонентов прило- жения с тем, чтобы он выполнялся на компьютере заказчика. Информационный поток возвращается на сервер приложений, который, в свою очередь, взаимодей- ствует с компонентом базы данных в процессе выполнения транзакции. В данной главе в наш репертуар методов тестирования мы добавим тесты, предназначенные для выявления новых видов дефектов, прежде всего, связанных с распределенным программным обеспечением, способным использовать два или 298
Глввв 8. Твстироввнив распределенных объектов большее количество процессоров. В основном мы будем рассматривать следую- щих два типа ошибок: Параллельные потоки выполнения должны координировать свой доступ к совместно используемым значениям данных. Сбои в синхронизации этих доступов могут привести к тому, что неправильные значения данных будут находиться в памяти даже тогда, когда каждый поток выполняет корректную обработку данных. Может случиться так, что какой-либо узел распределенной системы пере- стает правильно функционировать, даже если все другие процессоры рабо- тают должным образом. Может выйти из строя то или иное сетевое соеди- нение между узлами, в то время как вся остальная система продолжает исправно выполнять свои функции. Это приводит к отказу системы. Пример игры «Кирпичики», каким мы его использовали до сих пор, не являет- ся примером распределенной системой и не может быть полезным для нас в дан- ном контексте. Далее мы будем пользоваться примерами из игры «Кирпичики» в версии Java, которая является многопоточной системой и использует аплет, вы- полняемый в Web-браузере как часть его интерфейса. Базовые концепции Базовый элемент, с которым нам придется иметь дело в этой главе — это поток (thread). Поток — это независимый контекст исполнения в рамках процесса опе- рационной системы. У него имеется собственный программный счетчик и локаль- ные данные. Поток представляет собой наименьший элемент, выполнение которо- го можно планировать. Большинство современных операционных систем позволяет отдельным процессам группировать множество потоков в логически связанный набор, в котором одни свойства являются общими для всех элементов набора, в то время как другие характерны для отдельных элементов. Отдельный поток представляет собой некоторую последовательность вычислений и является простейшей ситуацией для тестирования. Методы, которые были рассмотрены в предыдущих главах, «покрывали» различные точки входа и пути через отдельный поток вычислений. Эти методы соответствовали различным логическим путям в логике потока, включая динамические подстановки одного фрагмента программ- ного кода вместо другого. Основные осложнения, обусловленные наличием множества потоков, возника- ют, когда потоки совместно используют информацию или осуществляют доступ к хранилищам данных, доступных более чем одному потоку. Чтобы параллельность приносила хоть какую-нибудь пользу, должно быть как можно меньше зависимо- стей между потоками. Зависимости означают, что порядок, в котором выполняют- ся вычисления в двух конкретных потоках, имеет значение. Поскольку выполне- ние каждого потока планируется операционной системой независимо от других потоков, разработчик обязан предусмотреть специальный механизм для синхрони- зации потоков с тем, чтобы был выдержан корректный порядок вычислений.
300 Главв 8. Тастироввнив рвспрвдалвнных объектов В объектно-ориентированных языках программирования имеются естествен- ные средства синхронизации, которые предусматривают сокрытие атрибутов за интерфейсами и в ряде случаев, установку соответствия между потоками и объек- тами. Это означает, что синхронизация становится видимой в интерфейсе соот- ветствующего объекта (например, ключевое слово synchronize в Java) и что обмен сообщениями является ключевым элементом синхронизации. В такой среде тес- тирование классов не способно обнаружить многие нарушения синхронизации, если вообще что-то способно обнаружить. Только когда объекты некоторого мно- жества объектов взаимодействует друг с другом, появляется реальная возможность обнаружения дефектов синхронизации. Исследование тестовых случаев, к которым мы прибегали ранее, первоначально было сформулировано в виде последовательности действий, и версия C++, кото- рую можно загрузить из Web, является последовательной версией. Версия Java вводит параллелизм. По существу, объекты класса MovablePiece автономны по отношению к другим вычислениям игры «Кирпичики». В этом смысле объект Timer поддерживает отдельный поток и посылает сообщение tick() каждому объек- ту MovablePiece, который регистрируется с ним. Проблема синхронизации, кото- рую необходимо исследовать, может быть сформулирована следующим образом: может ли поток объекта Timer сохранить контроль над процессором и несколько раз посылать сообщения tick() отдельному объекту, прежде чем поток, который управляет отображением на экране, сможет просчитать последствия соударения, изменить местоположение перемещаемого объекта и обновить отображение. Вычислительные модели Последовательная обработка операторов программ является моделью вычисле- ний по умолчанию. В этом разделе мы обсудим некоторые другие модели и корот- ко остановимся на проблемах, сопровождающих тестирование каждой из них. Совмещенная модель В рамках совмещенной (concurrent) модели вычислений вводится логическое понятие множества событий, происходящих в одно и то же время. Физически в одних случаях возможно, а в некоторых — нет, чтобы два события происходили в один и тот же конкретный момент времени, однако проект должен быть разрабо- тан так, чтобы можно было предположить, что события совпадают во времени. После того как в современные операционные системы было введено понятие уп- рощенных потоков, существенно облегчилось восприятие модели упомянутого типа. Тестирование с целью обнаружения дефектов должно сосредоточиваться на тех точках, в которых взаимодействуют два потока. Методы должны пройти обычное тестирование, описание которого изложено в главе 5, прежде чем они будут ис- пользованы для тестирования взаимодействий. Тестирование взаимодействий, о котором шла речь в главе 6, должно обеспечить возможность двум или большему числу клиентов запрашивать одну и ту же службу. Подробнее эту проблему мы
Глава В. Тастирование распределенных объектов будем рассматривать ниже в этой главе (см. раздел «Тестирование путей в распре- деленных системах»). Параллельная модель Параллельная (parallel) модель вычислений использует некоторое множество физических процессоров для осуществления физически параллельных вычисле- ний. То есть, выполнить такой объем вычислений, какой способны выполнить все процессоры в одно и то же время. Существуют несколько определений понятия «параллельный компьютер», однак'о обычно под этим понятием подразумевают наличие высокоскоростной общей шины, совместно используемой некоторым множеством процессоров, при этом считается, что сами процессоры находятся в одной «коробке» (блоке). NOAA (National Oceanic and Atmospheric Administration - Национальная администрация по океану и атмосфере) использует компьютер с более чем двумя тысячами процессоров для вычисления прогнозов на базе боль- ших объемов измерений, выполненных в различных точках земного шара. Одна- ко, мы не будем касаться проблем, связанных с такой моделью. Сетевая модель В сетевой (networked) модели физический параллелизм вычислений достигает- ся за счет подключения отдельных блоков к устройствам связи, которые работают с меньшим быстродействием, нежели внутренняя шина. Именно эту модель мы и будем рассматривать в силу того обстоятельства, что она может взаимодействовать с таким интенсивно используемыми системами, как Internet. Одна из проблем те- стирования, связанная с вычислениями в сети — это трудности синхронизации работы независимых машин, образующих сетевую систему. Это обусловливает трудности в определении, насколько тщательному тестированию была подвергну- та конкретная реализация, поскольку моменты времени, в которые происходит то или иное событие, измеряются локальными часами. Не вдаваясь в подробности относительно работы в сети и проблем обмена данными в Web, мы перейдем к обсуждению методов тестирования систем, в которые встроены компоненты Web. Распределенная модель В распределенных (distributed) системах используются многочисленные про- цессы, поддерживающие гибкую архитектуру, в условиях которой некоторое ко- личество участвующих объектов могут претерпевать изменения. И хотя объекты системы могут быть распределены по многочисленным процессам одной и той же машины, обычно они распределяются на множество физических компьютеров. Эти распределенные компоненты должны быть способными определять местопо- ложение других компонентов, с которыми они должны взаимодействовать. Объект, который в одних случаях называется «Службой имен», в других - реест- ром, в третьих — как-то еще, известен всем компонентам. В некоторых случаях список машин, которые получили право быть частью системы, содержится в спе- циальном конфигурационном файле. Эти и другие порции программного обеспе-
Глава 8. Тестирование распределенных объектов чения образуют то, что мы называем инфраструктурой распределенной системы. Такая инфраструктура может быть стандартизована и может допускать многократ- ное использование в рамках различных систем, для чего требуется ее минималь- ная модификация, а в некоторых случаях не требуется вносить никаких измене- ний. Мы рассмотрим также ряд проблем, имеющих отношение к тестированию таких распределенных компонентов и систем. Основные различия Мы хотим рассмотреть некоторые из основных различий между последователь- ными системами и другими моделями, и прежде всего, в перспективе тестирова- ния. Недетерминированность Очень трудно точно воспроизвести прогон конкретного теста в ситуации, когда программное обеспечение содержит множество параллельных потоков. Точный порядок событий определяется планировщиком операционной системы. Измене- ния, вносимые в программы, не связанные с тестируемой системой, могут повли- ять на порядок, в котором выполняются потоки тестируемой системы. Это озна- чает, что после обнаружения неисправности, выявления дефекта, принятия мер по его устранению и повторного тестирования, нельзя быть уверенным в том, что дефект действительно устранен, только на основании того обстоятельства, что ошибка не обнаружилась повторно во время конкретного прогона. Подобное положение вещей заставляет прибегнуть к одному из следующих подходов: Провести более тщательное тестирование на уровне классов. Проверка струк- туры класса, который генерирует распределенные объекты, должна выяс- нить, существуют ли соответствующие средства синхронизации событий в структуре класса. Динамическое тестирование класса должно определить, правильно ли работает синхронизация в контролируемой среде тестирова- ния. Выполнять большое количество тестовых случаев, стараясь при этом фикси- ровать порядок следования событий. Это повышает вероятность того, что были выполнены все возможные виды упорядочения. Проблемы, которые мы сейчас пытаемся решить, возникают в результате нарушения правиль- ной последовательности действий. Если все возможные последовательности этих действий были воспроизведены, этого достаточно, чтобы дефект был обнаружен. Дать описание стандартной тестовой среды. Для начала следует выбрать наи- менее загруженную машину с минимально возможным количеством соеди- нений с сетью, модемами или какими-либо другими совместно используе- мыми устройствами. Определите приложения, которые нужно выполнить для того, чтобы платформа стала жизнеспособной. Добавьте к ним базовый
Главе В. Тастирование распределенных объектов набор приложений, которые могут работать на типовой машине. Каждый тестовый случай должен дать описание любого изменения, вносимого в эту стандартную среду. Сюда входит порядок запуска процессов в работу. Включение отладчика в стандартную среду позволяет тестировщику выпол- нить проверку очередности, в которой потоки создаются, выполняются и удаляются. Чем шире среда, чем больше объектов могут ее использовать, и чем интенсивнее она использует сетевые средства, тем сложнее поддержи- вать взаимодействие с такой средой. Где только возможно, должны исполь- зоваться испытательные лаборатории, в которых машины изолированы (по крайней мере, на начальных стадиях тестирования) от остальной корпора- тивной сети и находятся в полном распоряжении процессов тестирования. Дополнительная инфраструктура Многие распределенные объектные системы берут за свою основу инфраструк- туру, приобретенную у сторонних поставщиков программного обеспечения. Со временем будут выпускаться и серийные версии таких инфраструктур. Нужно построить набор регрессионных тестовых случаев, предназначенный для тестиро- вания совместимости приложения и инфраструктуры. Вторая связанная с этим проблема — это перестройка конфигурации системы. Некоторые инфраструктуры относятся к категории самомодифицирующихся структур и выполняют собственную реконфигурацию в случае перестройки кон- фигурации системы. По существу, специально подобранные наборы входных дан- ных могут вынудить выбор другого пути выполнения, поскольку прежний путь уже не существует. Анализ инфраструктуры должен выявить набор стандартных конфигураций инфраструктуры, и для каждой из них должны быть выполнены соответствующие тесты. Частичные отказы Распределенная система может обнаружить, что ее программный код невоз- можно выполнить из-за отказов аппаратных средств или программного обеспече- ния одной из машин, на которых работает система. Приложение, функционирую- щее на одной машине, не испытывает подобного рода отказов: она либо работает, либо не работает. Возможность частичных отказов заставляет включать в тестовые наборы такие тесты, которые моделируют отказы в результате разрыва или вывода из строя подключений к сети или отключения того или иного сетевого узла. Это может быть осуществлено в упомянутой выше испытательной лаборатории. Тайм-ауты Сетевые системы избегают тупиковых ситуаций за счет установки соответству- ющих значений таймеров в тех случаях, когда запрос отсылается в другую систе- му. Если за заданный промежуток времени нет никакого ответа, этот запрос теря- ет силу. Система может оказаться заблокированной или одна из машин сети может быть очень занята и ей потребуется на ответ больше времени, нежели по-
Глава В. Тестирование распределенных объектов зволяет таймер. Программное обеспечение должно быть способным реализовать корректное поведение в случаях, когда на запрос выдается ответ и когда ответ не выдается, даже если такое поведение существенно отличается в упомянутых двух ситуациях. Тесты должны выполняться на различных конфигурациях загрузки машин, функционирующих в сети. Динамическая природа структуры Построение распределенных систем часто сопряжено с возможностью измене- ния их конфигурации, например, когда конкретные запросы направляются на раз- личные машины в динамике, т.е., в зависимости от их загрузки. Системы часто разрабатываются с таким расчетом, чтобы допускать участие в системе различного числа машин. Тесты нужно тиражировать для различных конфигураций. Если имеется фиксированное число конфигураций, должна быть возможность тестиро- вать все эти конфигурации. В противном случае для выбора конкретных тестов и конфигураций можно воспользоваться системой OATS. Потоки Мы уже ввели понятие потока как вычислительной единицы, выполнение ко- торой можно планировать. Во время разработки принимаются основные компро- миссные решения, касающиеся необходимого числа потоков. Увеличение числа потоков может привести к упрощению некоторых алгоритмов и методов вычисле- ний, но в то же время это увеличит риск возникновения проблем упорядочива- ния. Снижение количества потоков способствует уменьшению проблем, но в то же время делает программное обеспечение более жестким и довольно часто менее эффективным. Синхронизация Когда два или большее число потоков должны получить доступ к одной и той же области памяти, необходим механизм, который препятствует этим потокам блокировать друг друга. Два потока могут одновременно предпринять попытку воспользоваться одним и тем же методом, модифицирующим данные. Некоторые языки программирования, подобные Java, предусматривают специальное ключе- вое слово, которое автоматически подключает механизм, предотвращающий по- пытки одновременного доступа. Другие языки, такие как C++, требуют от каждого разработчика построения явных структур. В объектно-ориентированных языках задачи синхронизации решаются проще, поскольку такой механизм может быть сосредоточен в методе, известном как мо- дификатор общих атрибутов данных. Фактические данные защищены от прямого доступа при помощи специальных методов. Обоснование необходимости синхронизации В проектной документации описание синхронизации может находиться в раз- делах, регламентирующих защиту, диаграммы состояний, представленной на язы-
Глввв В. Тестирование распределенных объектов ке UML. В языке Java ключевое слово synchronize используется в сигнатуре мето- да для обозначения необходимости использования механизма синхронизации. В C++ нет специального ключевого слова, обозначающего синхронизацию; в то же время механизмы синхронизации разрабатываются в виде специальных классов. Например, при построении экземпляра объекта, осуществляющего текущий конт- роль, должна быть указана область, доступ к которой необходимо синхронизировать. Проверка того, что все требования удовлетворены Даже в случае, когда язык программирования автоматически предоставляет ме- ханизм синхронизации, разработчик может поместить спецификацию синхрони- зации не туда, куда надо. В процессе тестирования средства тестирования должны построить множество тестовых объектов, использующих потоки. Каждый из них выдает свой запрос к тестируемому объекту (OUT-объекту). Тестирование путей в распределенных системах Тестирование путей является прочно установившейся практикой выбора тесто- вых случаев. Путь представляет собой логически непрерывную цепь операторов, которая выполняется, когда используются специальные наборы входных данных, такие как, например, приводимый ниже: s>; if(condl) S2 else S3 Здесь имеют место два пути - S(, S2 и Sp S3. Другие структуры управления по- рождают новые пути и могут привести к появлению неопределенного числа путей или, что еще хуже, к бесконечному числу путей. Покрытие программных кодов измеряется путем подсчета процента путей, за- действованных тестовым случаем. Выполнение 100% путей (ветвей) в программе представляет собой полное покрытие программного кода, хотя при этом могут оказаться необнаруженными дефекты, имеющих отношение к вычислительной среде. Альтернативами являются только измерения ветвей, исходящих из опера- торов выбора, либо операторы if и case, которые были покрыты. Однако при этом непокрытыми остаются комбинации ветвей от одной управляющей структуры к другой. Согласно другому определению, путь представляет собой связь между местом, в котором переменной присваивается значение (def), и местом, в котором это зна- чение используется (use). Покрытие каждой пары def-use и составляет полное по- крытие пути. Другие виды важных атрибутов программных кодов могут использо- ваться для определения «пути». Например, тестирование ветвей программ, как было указано выше, определяет пути, в основе которых лежат операторы приня- тия решений, использованные в программе. Ричард Кейвер (Richard Caver) и К.С. Тай (К.С. Тау) [СаТа98] предложили для распределенных систем определение пути, обеспечивающего эффективное покры- тие. Сначала мы предложим следующие два определения:
Глава В. Тестирование распределенных объектов SYN-событие: SYN-событие представляет собой любое действие, которое предусматривает синхронизацию двух потоков. Примером SYN-события может служить порождение одного потока другим. SYN-последовательность: SYN-последовательность — это последователь- ность SYN-событий, которые будут происходить в установленном порядке. Это один из типов пути в программном коде. Идея состоит в разработке тестовых случаев, которые соответствуют SYN-noc- ледовательностям. Например, когда программа начинает выполняться, функцио- нирует только один поток. Когда он порождает второй поток, это событие являет- ся SYN-событием. В простом случае каждый поток выполняет простые вычисления. В конце концов, оба эти потока сливаются и выполнение программы завершается. Это единственная SYN-последовательность, поскольку любой от- дельно взятый набор входных данных предусматривает выполнение обоих пото- ков. Основной или «главный» поток не рассматривается как один из путей, по- скольку он выполняется независимо от характеристик набора входных данных. Рисунок 8.1. служит иллюстрацией взаимодействия нескольких объектов. Объект BricklesView представляет собой основной поток программы. Он создает второй поток, который отводится классу Timer. Управление объектами Puck и Paddle осуществляется из главного потока. thePaddle BricklesView theTimer currentPuck Рисунок 8.1 .Диаграмма активности нескольких потоков
Глава В. Тастированив распределенных объектов Сообщения tick(), передаваемое из объекта Timer объектам Puck и Paddle, суть точки синхронизации, следовательно, представляют собой SYN-события. SYN- последовательности, которые представляют для нас интерес, простираются от мо- мента создания объекта Timer до момента его уничтожения. В этом случае имеет место бесконечное число SYN-путей, поскольку Timer непрерывно продолжает отправку сообщений tick() вплоть до своего уничтожения. Сообщения, иниции- рующие построение объектов, их уничтожение, запуск в работу и останов — это SYN-события. Анализ этих событий помогает обнаружить следующие из SYN-ny- тей, которые должны быть отработаны тестовыми случаями: 1. Создание таймера; его работа до тех пор, пока игра не окончится; уничто- жение таймера 2. Создание таймера; его работа в течение некоторого времени, останов, за- пуск в работу, завершение игры; уничтожение таймера 3. Создание таймера; его работа в течение некоторого времени, останов, унич- тожение игры пользователем; уничтожение таймера 4. Создание таймера; его работа в течение некоторого времени, останов, за- пуск в работу, останов таймера и его запуск в работу три раза, завершение игры; уничтожение таймера. Накопленный нами опыт и проведенные исследования показали, что метод SYN-путей позволяет выявлять дефекты, которые главным образом относятся к категории дефектов синхронизации. Использование этого метода не может ис- ключить необходимость применения обычных методов тестирования путей с це- лью обнаружения дефектов, не относящихся к категории ошибок синхронизации. После того, как мы ознакомились с примером, проведем анализ типов событий в объектно-ориентированных языках программирования, которые можно квали- фицировать как SYN-события. Создание и уничтожение объекта, который представляет собой поток. Создание и уничтожение объекта, который инкапсулирует поток. Передача сообщений от объекта одного потока к объекту другого потока. Один поток осуществляет управление другим потоком, переводя его в ре- жим ожидания или в режим активизации. Тестировщик должен уметь отслеживать пути от одного такого события до дру- гого. Даже если имеет место множество путей от одного SYN-события до другого, проходящий через управляющие операторы, нужно осуществить покрытие только одного пути, чтобы обеспечить покрытие SYN-пути. Точное место, где происходят эти события, частично зависит от того, в каком месте находятся сами потоки. Объект, обладающий собственным потоком, должен подвергнуться тщательно- му тестированию как класс (со всеми его агрегированными атрибутами), прежде чем ему будет позволено вступать во взаимодействие с другими объектами. На
Глава В. Тестирование распределенных объектов рис. 8.2. показаны избранные фрагменты класса TimeTester. В частности, мы включаем в тестовый набор лишь небольшое число тестовых случаев. Экземпляр класса Tester работал в контексте завершенной игры в течение непродолжительно- го времени, прежде чем он подвергся тестированию как класс. Была обнаружена проблема, связанная с тем, что таймер был запущен в работу, но его никак нельзя было остановить! Метод pause() устанавливал атрибут типа Boolean, который бло- кировал дальнейшую посылку временных отметок («тиканье»), однако сам поток не останавливался и продолжал использовать системные ресурсы. Это удалось об- наружить лишь после того, как было подвергнуто тестированию отсутствие вре- менных отметок, а не их наличие. В программном коде, представленном на рис. 8.2, тестовый объект регистри- руется с таймером как получатель сообщений tick(). Такая возможность существу- ет, ибо класс TimeTester реализует интерфейс TimeObservable. Тестовый случай, подобный рассматриваемому, можно легко построить, поскольку тестовый объект получает событие или сообщение непосредственно, а не путем создания суррогат- ного объекта, который получает сообщение и только после этого уведомляет об этом тестовый объект. ОБЪЕКТЫ В ПОТОКАХ 1/1 ПОТОКИ В ОБЪЕКТАХ В объектно-ориентированных программах существуют всего лишь несколько безовых моделей потоков. У объекте имеется его собственный поток либо при необходимости его посещеет активный поток. Обычно в большинстве программ имеются примеры каждого такого подходе. Клесс Timer в Jeve-реализации игры «Кирпичики» являет собою пример объекте, у которого имеется собственный поток. Все другие потоки совместно используют «главный» поток. Объект, владеющий потоком, назывеет точки, в которых он может быть прерван другими потоками. При любом подходе должен быть механизм, который не допускает многим потокам рвботеть в ремкех одного и того же методе, известного как модификетор, в одно и то же время. public class TimerTester extends Tester implements TimeObservable{ protected boolean testScript2(){ if (newCUTQ ) { boolean result = testCase2 () ; logTestResult("Script2".result && classinvariant()); ((Timer)disposeOUT).pause(); disposeOUT; return result; }else{ OUT=null; return false; } } protected boolean testCase2(){ Timer OUT = (Timer) getOUT(); OUT.start () ; for (int i = 0; i < 100000; i++{ for (j = 0; j < 100000; j++() } return tickReceived; } Рисунок 8.2. Избранный программный код класса TimeTester
Глава 8. Тастированиа распрадаланных обьактоа СХЕМА №2 ОБЕСПЕЧЕНИЯ КОНТРОЛЕПРИГОДНОСТИ Посла проведанного обсуждения проблемы тастироаания потоков аы наверняка заметите, что класс Timer реализует интерфейс TimeObeerver. Прааило проектирования: В Java при единственном отношении наследования корнам каждой иерархии насладоаания должен быть некоторый интарфайс. Это позволяет построить срадстао тастирования. котороа также должно быть наслед- ником родительского тестового класса, чтобы реализоаать этот интерфейс. Это часто бывает полезно, как, напримар, а случае класса TimeTeeter, который должен быть зарегистрированным с OUT-объактом. В Java это эквивалентно долгосрочному пра- вилу проектирования, действующему в C++, согласно которому базовый класс любой иерархии должен быть абстрактным классом. СХЕМА №3 ОБЕСПЕЧЕНИЯ КОНТРОЛЕПРИГОДНОСТИ Эта схема на очевидна из программного кода, используемого в класса TimeTeeter, однако тастированиа класса Timer представляет собой очень трудную задачу. Это объясняется наличием следующего оператора: OUT = new Timer(newBricklesView()); Параметрами конструктора BrickleeView можат быть большая часть классов при- ложения: BrickleeGame, ArcadeGamePiece, StationaryObject, MovableObject, Puck, Paddle и Brick. Перечисленные классы должны быть включены в путь во время компиляции, прежде чем BrickleeView будет построен, даже если причины, в силу которых эти классы сгруппированы a BrickleeView, на имеют ничего общего с планироааниам действий во времени. Прааило проактирования: где только возможно, определите конструктор по умолча- нию, который можно использоаать ао время тастирования модулей, на требуя от него зависимости от большого числа других классов. Конструктор по умолчанию на обязательно должен быть общедоступным. В некоторых случаях создание конструктора по умолчанию в рамках класса, который подваргаатся тестированию, можат оказаться удачным рашанивм. В других случаях оно себя на оправдывает. В случае с классом Timer это невозможно, поскольку реализация класса Timer прадполагаат наличие в нам ссылок на объект BrickleeView и выполняет аварийный выход из главного цикла, когда эта ссылка принимает нулааоа значение. В связи с там, что этот объект выступает в роли параметра только по отношению к конструктору, целесообразно установить прадусловиа, требующее, чтобы ссылка на была нулевой. Модели потоков Девиз Java — «Написанное один раз выполняется где угодно». На практике по- лучается так: «Написанное один раз, выполняется где угодно после тестирования везде». Конкретным примером может послужить различие в поведении потоков Java в различных операционных системах. Тестовый набор любой программы на Java, которая создает потоки, должен включать тестовые случаи, ориентированные на различные операционные системы, выбранные для проверки ее различных по- ведений.
Глааа 8. Тестирование распределенных объектов ТЕСТИРОВАНИЕ ЖИЗНЕННОГО ЦИКЛА Подход, в основе которого лежит тестирование на протяжении жизненного цикла, предполегеет отбор последоветельностей тестовых случаеа с теким ресчетом, чтобы то, что подаергеется тестированию, функционировало с момента его построения до момента его уничтожения. Обычно в полном жизненном цикле существует множество путей. План тестирования должен выбирать представительные пути, тем самым обес- печивая максимальное покрытие. Что кесается клессов, то тестировение жизненного цикле ознечает выбор последоветельности тестов, которые выполняют построение классе, используют его при работе с некоторой последоветельностью сообщений, после чего они уничтожают объект. Эффективное тестирование жизненного цикле не только должно подтаерждеть правильность ответов. Оно также должно подтвердить, что тестируемый модуль корректно езеимодействует с окружеющей средой. Что кесеется клесса, то проверке, освобождены ли ресурсы, используемые классом, после его уничтожения, весьма полезна. Аналогично, проверка того, что другие элементы, с которыми взеимодейстаует тестируемый модуль, находятся в нужном состоянии, также несет полезную информацию. Например, когда выходит из строя сервер, «службе имен» брокера OBR (Object Request Broker - брокер объектных запросов] а технологии CORBA (Common Object Request Broker Architecture - ерхитектура распределенных объектных приложений) может оказаться резрушенной и ее придется еозаращеть в исходное состояние. Использование версии Windows, операционных систем Sun Unix и Mac OS по- зволяет получить профиль использования; в то же время варианты Unix и даже различные модели рабочих станций, на которых установлены различные версии, могут дать разные результаты. Выполнение аплет-версии игры «Кирпичики» на Windows-машине и на рабо- чей станции Sun приводит к возникновению различных поведений, одни из кото- рых правильные, а другие нет. Поток для класса Timer не обязательно теряет уп- равление процессором, благодаря чему становится возможным обновление отображение на экране. Тестирование жизненного цикла Мы уже рассматривали вопросы тестирования жизненного цикла в главе 6 и намерены продолжить их изучение в главе 9 как метода, применение которого возможно на различных стадиях разработки. Сначала потребуется определить, ка- кой жизненный цикл следует использовать, и только после этого на его основе разрабатывать тестовые случаи. Для распределенных систем такой жизненный цикл можно измерять продолжительностью существования компонентов инфра- структуры, экземпляры которых построены для поддержки системы. План тестирования системы должен предусматривать прогоны тестов на на- чальной стадии работы системы, когда никакие экземпляры еще не построены, наращивание возможностей системы, выполнение некоторой последовательности действий и полный останов системы. Чтобы определить, насколь успешно прошли испытания системы, следует выполнить три важных проверки: Завершилось ли каждое действие, предпринятое системой, полным успе- хом?
Глава В. Тастированив распределенных объектов Были ли все ресурсы, распределявшиеся системой, освобождены после оста- нова системы? Возможен ли успешный повторный пуск системы? (Или инфраструктура сохранила состояние, делающее повторный пуск системы невозможным?) Модель распределения Теперь мы хотим обсудить программы тестирования, которые использует стан- дартные инфраструктуры для распределенных систем. Базовая модель клиент/сервер Модель клиент/сервер, в условиях которой все множество клиентов получает доступ к серверу, является простейшей моделью распределения. Сервер является единственным процессом, и к нему с запросами может обращаться произвольное количество клиентских процессов. У этой модели имеется единственная точка от- каза благодаря тому обстоятельству, что все клиенты взаимодействуют с одним и тем же сервером. Такая модель дает общее представление о нескольких проблемах тестирования в распределенных системах, однако лишь в некоторых системах воз- никают проблемы, которые по сложности превосходят проблемы, характерные для данной модели. Дополнительные вопросы, ответы на которые должно дать тестирование: 1. Может ли сервер доставлять корректные результаты требуемому клиенту в условиях постоянной нагрузки, когда поступает некоторое умеренное число запросов на обслуживание одновременно в течение продолжительного пе- риода времени? Время от времени сервер может посылать ответы на запро- сы клиентов по неправильным адресам. Такой набор тестов может быть под- вергнут изменениям с тем, чтобы отражать профиль ожидаемых запросов, в котором количество запросов меняется в некотором бизнес-цикле. 2. Может ли сервер корректно манипулировать стремительно увеличивающей- ся нагрузкой? Сервер может довольно быстро деградировать по мере увели- чения нагрузки, либо вообще аварийно завершиться. Тестовый набор дол- жен выдавать большое число тестовых случаев с увеличивающейся интенсивностью поступления. Стандартные модели распределения Простая модель клиент/сервер была обобщена с целью обеспечить возможность устранения точки отказа в модели клиент/сервер. Многие серверы могут предос- тавлять одну и ту же службу, и клиент может выбирать, какому из серверов отдать предпочтение. Ранние реализации этой модели были подвержены ошибкам даже в руках высококвалифицированных разработчиков, поскольку приходилось пользо- ваться примитивными структурами каналов и сокетов, которые требовали управ- ления со стороны разработчиков. С приходом объектно-ориентированных мето- дов были разработаны модели, свободные от сетевых деталей, благодаря чему
Глава В. Тестирование распределенных объектов снизилось количество ошибок. Мы не будем возвращаться к прошлому и рассмат- ривать более примитивные реализации модели. Мы останемся на уровне объектов и будем полагать, что имеется коммерческая инфраструктура, в которой можно скрыть все подробности, касающиеся передачи данных. Для начала сделаем краткое введение в каждую из трех стандартных моделей, после чего обсудим, как каждая из них поддерживает или упрощает тестирование систем, построенных с использованием соответствующей модели. Несколько поз- же мы рассмотрим базовую инфраструктуру для Internet-приложений. Технология CORBA Технология CORBA (Common Object Request Broker Architecture) была разрабо- тана группой OMG (Object Management Group — рабочая группа по развитию стандартов объектно-ориентированного программирования) в качестве стандарт- ной архитектуры распределенных объектных систем. Центральным элементом этой архитектуры является брокер объектных запросов (OBR - Object Request Broker), который использует один объект системы для обмена данными с другими объектами системы. Стандартная инфрастуктура, предоставляемая технологией CORBA, обеспечивает выполнение служб, которые позволяют одному объекту на- ходить другие объекты, основываясь на запросах объектов, их местоположении и загрузке. Такая инфраструктура предоставляет службы по установке соединения между двумя объектами, реализованными на разных языках программирования, или между двумя объектами, исполняемыми на различных платформах. Многие поставщики предлагают программные продукты, которые формируют инфра- структуру для этой модели. Такая «стандартная архитектура» не дает полного опи- сания реализации, так что программное обеспечение, поставляемое различными производителями программных продуктов, обладает различными конкурентными возможностями, такими как большая производительность или отслеживание оши- бок в программе. Технология CORBA достаточно хорошо развита; этот программ- ный продукт выдержал несколько изданий, каждый раз в виде новых версий, бла- годаря чему его можно считать «заслуживающим доверия». Стандарт технологии CORBA основан на следующих предположениях: Будучи соединенными друг с другом посредством инфраструктуры, машины могут иметь различные операционные системы и различные схемы распре- деления памяти. Компоненты, которые формируют распределенную систему, могут быть ре- ализованы на различных языках программирования. Инфраструктура может изменить свою конфигурацию, руководствуясь распределением объектов и типами машин в сети. Технология CORBA обладает преимуществами в плане гибкости. Мы уделим основное внимание данной технологии при рассмотрении последующих приме- ров, хотя эти подходы с небольшими изменениями можно применять и в отноше- нии других примеров.
Главв В. Твстироввнив распрвдвлвнных объектов Дополнительные вопросы, ответы на которые должно дать тестирование: Зависит ли правильная работа системы от конфигурации инфраструктуры? В планах тестирования должны быть предусмотрены прогоны тестовых слу- чаев, в результате которых получается ожидаемый набор конфигураций тес- тируемой инфраструктуры. Можно ли сделать тестовые случаи более пригодными для многократного использования, построив их на базе служб стандартной инфраструктуры? Эта инфраструктура достаточно хорошо развита, так что структура тестовых случаев должна быть очень устойчивой, а реализация - продуманной. Тес- товые случаи должны разрабатываться с таким расчетом, чтобы инфраструк- тура использовалась максимально. Насколько эффективно интегрируется рассматриваемая инфраструктура в существующие приложения? Должен быть набор регрессионных тестовых случаев и средств тестирования, позволяющие тестировать новые версии этой инфраструктуры, прежде чем она будет интегрироваться в продукты. Инфраструктура DCOM Инфраструктура DCOM (Distributed Component Object Model — распределенная модель компонентных объектов) — это стандарт, разработанный и продвигаемый на рынок корпорацией Microsoft. Данная инфраструктура свободно распространя- ется с операционной системой Windows, и это обстоятельство обусловливает ее сравнительно низкую стоимость. «Стандартность» модели DCOM определяется стандартными интерфейсами, содержащими специальные методы, а не архитек- турной универсальностью. Каждый стандартный интерфейс предоставляет конк- ретный набор служб. Отдельный компонент может предоставлять службы несколь- ких интерфейсов или каждый из нескольких компонентов может предоставлять службы одного и того же интерфейса, но различными способами. Инфраструктура DCOM поддерживает первоначальные связи между компо- нентами, но не является выполняемой частью приложения. Это уменьшает коли- чество уровней, через которые должны проходить сообщения, благодаря чему уве- личивается пропускная способность. Тем не менее, применение этого стандарта ограничивается машинами, совместимыми с платформой Intel. Благодаря этому обстоятельству отпадает необходимость в любых видах трансляции и службах ин- терфейсов за счет уменьшения типов различных систем, которые могут быть включены в приложение. Инфраструктура DCOM представляет собой низко- уровневый метод, который предполагает наличие у разработчика знания мелких подробностей и требует от него принятия множества правильных решений, каса- ющихся использования существующих деталей. Появляются различные инстру- ментальные средства, которые автоматизируют те или иные процессы реализации, уменьшая тем самым число возможных ошибок.
Глввв В. Тестирование распрвдвлвнных объектов Дополнительные вопросы, ответы на которые должно дать тестирование: Правильно ли разработчики настраивают необходимые уникальные иденти- фикаторы в различных местах различных компонентов? Тестовые случаи должны быть написаны так, чтобы были задействованы все компоненты, чтобы можно было убедиться в том, что все необходимые соединения мож- но установить. Реализует ли каждый компонент все необходимые интерфейсы? Тестовые случаи еще раз должны использовать все имеющиеся компоненты, чтобы убедиться в том, что предоставляются все необходимые службы, и выпол- нить все предполагаемые функции. Обеспечивают ли реализации стандартных интерфейсов корректное поведе- ние? Отсюда следует, что должен существовать набор тестовых случаев, оп- ределенных для каждого стандартного интерфейса. Этот набор тестов может применяться для тестирования каждого сервера, который реализует соответ- ствующий интерфейс. Технология RMI Пакет RMI (Remote Method Invocation — Удаленный вызов методов) языка Java обеспечивает построение упрощенной среды, которая предполагает, что независи- мо от того, какие машины или какие типы машин соединены в сеть, все они будут функционировать как одна виртуальная машина в среде Java. Подобная однород- ная среда обладает структурой, характерной для технологии CORBA, однако она проще технологии CORBA в силу меньшей гибкости основных положений. В употребление вводится специальный регистрирующий объект, и все объекты, включенные в распределенную систему, должны знать, какой порт прослушивает этот регистратор с целью установки наличия в нем сообщения. Последняя версия RMI использует протокол ПОР (Internet InterORB Protocol - протокол взаимодействия брокеров объектных запросов через Internet), позволяю- щий совместно работать RMI- и CORBA-объектам. Более подробно на этих воп- росах мы остановимся при рассмотрении следующей обобщенной модели. Дополнительные вопросы, ответы на которые должно дать тестирование: Какие тестовые шаблоны технологии CORBA могут использоваться в систе- мах, построенных на базе технологии RMI? Структура тестовых случаев может быть такой же, как и у большинства тестовых случаев для среды CORBA. Сравнение и выводы Рассмотренные выше три типа моделей подчеркивают ведущую роль интер- фейсов в объектно-ориентированных системах вообще и в распределенных систе- мах в частности. Распределенные объекты оповещают о службах, помещая список своих интерфейсов в службе имен инфраструктуры. Отсюда следует, что функци- ональные тесты могут быть организованы по интерфейсам. В частности, в прило-
Глава 8. Тестироввнив распределенных объектов жениях DCOM многие классы способны реализовать один и тот же интерфейс, при этом обеспечивается высокая частота повторного использования тестовых случаев для тестирования конкретных интерфейсов. Системы распределенных объектов базируются на относительно небольшом числе стандартов. Что касается стандартных проектных образцов, то каждая рас- смотренная нами модель основана на формальном стандарте с большей или мень- шей степенью формализации. Тесты, в основу которых положены эти стандарты, потенциально обладают возможностью многократного использования в рамках одного проекта или в различных проектах в организации, занимающейся разра- боткой. Обобщенная модель с распределенными компонентами По мере все более подробного изучения методов тестирования распределенных систем, мы будем формировать обобщенное представление об архитектуре и инф- раструктуре системы. Конкретные примеры будут взяты из технологии CORBA. Мы даже отказались от терминов «клиент» и «сервер», поскольку для некоторых людей они связаны с жесткими архитектурными понятиями. В распределенных объектных системах любой поставщик служб неизбежно будет обращаться к дру- гим объектам, чтобы те выполняли для него конкретные виды служб. Базовая архитектура Рисунок 8.3 может служить иллюстрацией роли базовой архитектуры распреде- ленных систем. Основные события возникают после того, как объект, запрашива- ющий службу, или запросчик службы, посылает сообщение объекту, предоставля- ющему службу, или поставщику службы. Запрос сначала посылается замещенному объекту, который выступает как локальный объект по отношению к запросчику, откуда следует, что запросчик не выполняет никакой обработки семантики рас- пределения. Замещенный объект устанавливает соединение с инфраструктурой РисуНОК 8.3. Обобщенная архитектура
Глава В. Твстироааниа распределенных объектов связи и передает ей запрос. По сути дела, инфраструктуре связи может потребо- ваться создание экземпляра поставщика служб, однако в конечном итоге она по- лучает ссылку на поставщика требуемой службы от службы локатора объектов и направляет запрос по нужному адресу. Этот запрос может проходить через замес- тителя запросчика, так что поставщик служб также защищен от необходимости иметь дело с деталями распределения. Возвращаемая информация, если таковая имеется, следует по тому же маршруту, но в обратном направлении. На этом уровне все три базовые модели, по существу, одинаковы, хотя модель DCOM возвращает результат непосредственно запросчику службы. При рассмот- рении компонентов архитектуры не забывайте, что конкретный объект может быть одновременно как запросчиком, так и поставщиком служб, что довольно часто случается на практике. Запросчик служб Запросчик служб выступает в распределенной системе как стимул. Его пове- денческие аспекты предварительно прошли тестирование с применением методов тестирования классов, которые уже рассматривались выше, но с одним исключе- нием: синхронизация. Если запросчик посылает какое-либо асинхронное сообще- ние (односторонние сообщения в технологии CORBA), то на тестовые случаи возлагается задача исследовать влияние длительности промежутка времени, необ- ходимого для получения ответа. То есть, как только асинхронное сообщение будет отправлено, отправитель немедленно приступает к выполнению других задач. Ре- ализация отправителя может быть написана с расчетом получить ответ за время выполнения некоторого объема вычислений, однако та же реализация может ока- заться недостаточно хорошо написанной, чтобы ждать этот ответ, если он не по- лучен в ожидаемый момент времени. Тестовые случаи должны быть написаны с целью проверки этого взаимодействия в условиях различных нагрузок, благодаря чему в процедуры обмена данными вводятся различные задержки. Запросчик служб участвует также в различных проверках взаимодействий, ког- да поставщик был подвергнут тестированию на уровне класса посредством специ- альных методов, которые рассматриваются в следующем разделе. Основное вни- мание в рамках этого тестирования уделяется протоколу обмена данными между запросчиком и поставщиками. Из изложенного в главе 6 следует, что этот прото- кол описывает полный набор сообщений, которыми обмениваются два объекта при решении опознаваемой задачи. Это отдельная фаза тестирования, поскольку довольно часто имеется несколько поставщиков одной и той же службы. Набор тестовых случаев и отдельные тестовые случаи, предназначенные для тестирова- ния протоколов, могут использоваться повторно всякий раз, когда в реестр добав- ляется новый поставщик служб для заданного протокола. Набор тестовых случаев для тестирования протоколов реализует подход к тестированию взаимодействий на протяжении их жизненного цикла.
Глава В. Тастироаанив распределенных объектов Поставщик служб Поставщик служб является центральным звеном в распределенном взаимодей- ствии. Он реализует поведение и в ряде случаев возвращает информацию запрос- чику служб. Полный интерфейс поставщика может быть проверен с использова- нием базовых методов тестирования классов, которые были рассмотрены в предыдущих главах. Эти поведения, которые, как ожидается, могут быть вызваны другими распределенными объектами, требуют специального тестирования, кото- рое мы рассмотрим в следующем разделе. Поставщик регистрируется в инфраструктуре вместе с информацией о служ- бах, которые он предоставляет. В некоторых случаях поставщик может не быть объектом, активно ожидающим в памяти поступления запросов на его службы. Сначала он инициируется, и только после этого к нему направляются запросы. На этой стадии могут возникать временные расхождения. Любой поставщик служб, построение экземпляра которого осуществляется по запросу в динамическом ре- жиме, должен выполняться с использованием тестовых случаев, запускаемых из сценариев с порождением его экземпляра и без порождения такового. Заглушки и скелеты В процессе запросчика службы заглушка есть суррогат поставщика службы. Скелет — это суррогат на стороне запросчика. Заглушка освобождает запросчик от необходимости знания семантики инфраструктуры. Некоторые реализации таких инфраструктур обладают достаточным интеллектом, чтобы быть способными вы- полнять собственную реконфигурацию в зависимости от того, выполняются ли оба эти объекта в одном и том же процессе, в различных процессах на одной и той же машине или на различных машинах с различными архитектурами. В процессе собственной реконфигурации инфраструктура добавляет или уда- ляет заглушки и скелеты или вызовы других методов. Это меняет путь, по которо- му должен пройти запрос. Тестовые наборы, предназначенные для тестирования взаимодействия, должны быть разработаны так, чтобы быть способными выпол- нить необходимое количество тестов вдоль пути, соответствующему каждой воз- можной конфигурации. Локальные и удаленные интерфейсы Интерфейс распределенного объекта часто делится на интерфейсы для локаль- ных объектов и интерфейсы для удаленных объектов. Интерфейс для удаленных объектов (удаленный интерфейс) представляет собой спецификацию тех служб, которые могут быть затребованы объектом, находящимся за пределами процесса, в котором пребывает поставщик этих служб. Доступ к поведению, которое опреде- лено в локальном интерфейсе, разрешен только тем объектам, которые находятся в том же самом процессе. Тестирование локального интерфейса можно проводить с использованием обычных методов тестирования классов, которые уже обсуждались в предыдущих разделах. Тестирование удаленных интерфейсов можно выполнять с использова-
Глааа В. Тастирование распределенных объектоа нием средств тестирования локальных интерфейсов и рассматривать его как на- чальную стадию, а вот для тестирования в полном объеме необходима специаль- ная среда. Описание распределенных объектов Язык описания интерфейсов Спецификация поставщиков служб обычно сформулирована в языке IDL (Interface Definition Language — язык описания интерфейсов). Поскольку эти языки предназначены только для написания спецификаций, они гораздо проще языков программирования. В IDL-спецификации включены несколько разделов информации, которые могут оказаться полезными для целей тестирования: signature (сигнатура) — Основной частью IDL-спецификации является обычная сигнатура конкретного метода. Она содержит имя этого метода, типы каждого из его параметров и типы возвращаемых объектов, если тако- вые имеются. При построении тестовых случаев следует использовать стан- дартные способы выборки значений каждого из этих параметров. one-way (однонаправленный) - Это обозначение указывает на асинхронное сообщение. Тестовые сообщения с таким атрибутом требуют, чтобы тести- рование проводилось на всем протяжении жизненного цикла. Существует вероятность того, что запросчику потребуется запрашиваемая информация еще до того, как поставщик передаст ее. Существует также вероятность того, что это сообщение приведет к тому, что поставщик сгенерирует исключи- тельную ситуацию. Специальные тесты должны проверить, улавливаются ли эти исключительные ситуации именно тем объектом, который должен их улавливать. in, out (вход, выход) - Такой атрибут параметра определяет, должен ли по- ставщик предоставить эту информацию, или же должен ждать, когда этот параметр будет модифицирован поставщиком. Тестирование метода, кото- рый дает описание параметра out, предусматривает определение местона- хождения возвращаемого объекта (поскольку большая часть языков объект- но-ориентированного программирования не особенно изящно решают задачи подобного рода) и проверку, находится ли он в соответствующем состоянии. Традиционные предусловия, постусловия и инварианты Мы уже предлагали вашему вниманию методы построения тестов на основе традиционных предусловий и постусловий, так что повторяться здесь не будем. Распределенные компоненты должны разрабатываться в предположении, что они не знают своего местоположения относительно других компонентов; в то же вре- мя эти компоненты должны знать о наличии расширенного списка возможных ошибок.
Глава В. Тестирование распределенных объектов НЕЯВНОЕ ТЕСТИРОВАНИЕ Любой метод, посылающий постеещику зепрос не аызов того или иного метода, может получить от инфраструктуры сообщение об исключительной ситуации “Provider not found” (Постевщик служб не найден]. Резреботчики редко когде проявляют достаточно терпения, чтобы прописеть эту ситуацию как возможное постусловие каждого методе, который может стать причиной возникновения подобного исключения. Типичной является ситуеция, когде определяется набор исключений, причиной аозникновения которых могут стеть многие методы клессе. Это один из примеров проявления того, что мы назывеем неявными спецификециями. Этим спецификациям должны со- ответствовать неявные тестовые случаи. Что кесается респределенных систем, то в число неявных тестовых случаев должны быть включены следующие тесты: • Проверке того, что запросчик способен реагироветь на исключение "Provider not found". • Проверка того, что запросчик способен реагироветь не исключение "Provider busy" (Постевщик службы занят). • Проверка того, что запросчик способен реегироветь на нулевую ссылку постев- щика (разумеется, любой нулевой указатель представляет собой проблему, но некоторые инфраструктуры делегат указетель недействительным по истечении определенного периода времени его пребывания в неективном состоянии). • Проверка того, что запросчик способен реагироветь не нулевое знечение па- раметре "out". Указенные тестовые случаи должны обледеть кек можно более общим характером, тек чтобы их можно было применить к каждому методу, который удовлетворяет определению неявной спецификеции. Эти тесты должны быть включены в список тестов типа создавеемого класса. У кеждой «предметной облести» имеется собственный список. Объекты пользователь- ского интерфейсе получеют неявные спецификеции событий и отображений на экране, которые, в свою очередь, выступеют в списках как честь стратегии тестирования проекта и предоставляются разреботчикам и тестировщикам, проводящим проверку взаимодействия и функционирования компонентов системы. Постусловия расширяются с тем, чтобы учитывать в сценариях исключитель- ные ситуации, такие как, например, служба, которую нельзя получить от указан- ного поставщика, поставщик, который не отвечает вовремя, или запросчик указы- вает неверный адрес. Как и в случае любого другого постусловия, каждый такой его раздел, как, скажем, неправильный адрес, должен покрываться специальным тестовым случаем. Временная логика Время представляет собой одну из важнейших проблем в распределенных сис- темах, однако большая часть методов формулирования спецификаций не способ- на решить ее достаточно удовлетворительно. Мы смогли убедиться в том, что ло- гика временных интервалов полезна при установке временных отношений. Операторы временной логики позволяют формулировать принципы упорядоче- ния во времени и использовать их в целях анализа. Логика временных интерва- лов, в отличие от выбора конкретных моментов времени, позволяет выражать вре-
Глава 8. Тестирование распределенных объектов менные соотношения через промежутки времени. Например, оператор before(a,b) утверждает, что за промежуток времени перед тем, как произойдет событие Ь, зна- чение а истинно. Периоды времени, которые мы учитываем в своих рассуждениях, должны соот- ветствовать текущей решаемой проблеме. В компьютерных системах практикуют- ся два различных представления временных интервалов. Одним из них является реальное календарное время, которое представлено в объектах предметной облас- ти. Обычно такое время выражается в виде дат. Другой тип представления вре- менных интервалов есть время исполнения. В объектно-ориентированных систе- мах такой тип представления времени используется для описания жизненного цикла объектов. Этому представлению будет уделяться основное внимание. Операторы временной логики неявно использовались с давних пор. Оператор always является неявной частью любого инварианта класса. Вспомните хотя бы, что инвариант класса — это оператор, объявляющий свойства, которые всегда (т.е. always) истинны. Неявно предусловие означает условие before. В число тех немногих операторов, которые мы сочли для себя полезными, вхо- дят: before(a,b) а истинно прежде чем произойдет событие b until(a,b) а истинно до тех пор, пока не произойдет событие b after(a,b) а истинно после того, как произойдет событие Ь. Все операторы временных интервалов применяются к некоторому периоду времени. В связи с этим тест, который стремится проверить, что реализация удов- летворяет такому требованию, должен покрыть данный интервал. Итак, как тести- ровать нечто, подобное следующему инварианту? always(x>=0) Мы решаем эту задачу путем многократного тестирования правильности опе- ратора инварианта в процессе тестирования всех классов. Always интерпретирует- ся как «всегда, когда существует хотя бы один активный экземпляр объекта». Часть спецификации объектов класса Timer, в которой описано поведение, может объявлять следующее: После того, как экземпляр класса Timer инициирован, он рассылает сообщения tick() каждому зарегистрированному слушателю, пока не будет остановлен. Это довольно легко проверить на уровне класса, но с использованием специ- ального подхода. В случае класса Timer тестовое средство должно наследоваться от класса TimeObservable, чтобы его можно было зарегистрировать с экземпляром класса Timer. Тестовое средство должно выполнять следующие действия: Зарегистрироваться с экземпляром класса Timer. Проверить, что никакие сообщения tick() не были получены. Послать сообщение start() экземпляру класса Timer.
Глааа 8. Тастированиа распределенных объектов Проверить, что сообщения tick() поступают. Послать сообщение stop() экземпляру класса Timer. Проверить, что ни одно сообщение tick() не было получено. Этот базовый тестовый случай должен повторяться для различных временных интервалов между различными сообщениями. Объект StopWatchTimer будет ис- пользован с целью уведомления средства тестирования, когда следует переходить на выполнение следующего шага. Для того чтобы убедиться в том, что временные ограничения не нарушены, можно воспользоваться двумя подходами. Первый из них предусматривает ин- капсуляцию объекта, на который накладывается ограничение. Тогда любой доступ к состоянию, к которому применяются ограничения, может подвергаться текуще- му контролю. После осуществления доступа выполняется проверка правильное™ ограничения. Недостаток описанного подхода заключается в возможности изме- нения функционирования объекта через инструментальные средства. Мы рас- смотрим этот вопрос в следующем разделе. Второй подход, как это было показано ранее на примере объектов класса Timer, предусматривает выборки на протяжении интервала, который является объектом ограничения. Интервалы, на протяжении которых обычно производятся выборки, могут быть такими: Интервал, для которого заданы начальный и конечный моменты времени Интервал с момента создания экземпляра до заданного момента времени Интервал с заданного момента времени до уничтожения объекта. Обратите внимание, что когда мы говорим «заданный момент времени», то обычно имеем в виду, что происходит конкретное событие. Заданный момент вре- мени - это момент, когда происходит это событие. ИНВАРИАНТЫ КЛАССОВ И ОБЪЕКТОВ Здась мы хотали бы провасти различие мажду инвариантами классов и объектов, котороа аналогично различию между объактами-классами и объактами-зкземпляра- ми, о чам рачь шла вышв. Сущаствуат «инвариант класса», который соответствует объекту класса, и «инвариант объекта», который соответствует объектам экземпляра. По существу, инвариант асть любой оператор, который всегда должен иметь значение «истинно», когда субъект этого инварианта принимавт устойчивое состояние. Мы можам построить систему инвариантов. Напримар, проектный шаблон Singlaton тра- бувт, чтобы всегда был максимум один экземпляр класса. В этом случае инвариант класса принимает вид: number of instances <= 1 (количество экземпляров <= 1) Инвариант экземпляра должен соответствовать семантика прадматной области. Объакт-экзампляр на имаат представления о том, какое число экземпляров может быть построено, а аго инварианты на имают никакого отношения к этой проблама. Мы хотим прадостерачь, что использованная в этом раздала терминология на по- лучила широкого распространения в промышленных кругах, но мы надеемся, что она заставит обратить внимание на точность формулирования инвариантов. И 2,,
Глааа В. Тастироааниа распрадаланных объектов Временные тестовые модели В этом разделе рассматриваются три тестовых модели, описание которых изло- жено в неформальном стиле с целью экономии пространства данной книги. В тех случаях, когда в описании семантики или постусловий используются ог- раничения, сформулированные в терминах временной логики, возникает необхо- димость выполнения новых условий тестирования. В общем случае, временные ограничения устанавливают требования по времени выполнения процесса тести- рования. В результате объекты архитектуры PACT (Parallel Architecture for Class Testing — Параллельная архитектура тестирования классов), которые выполняют эти тесты, должны поддерживать собственные потоки управления с тем, чтобы на всем протяжении соответствующего интервала времени они могли выполнять свои задачи независимо от других обстоятельств. В большинстве случаев эти ин- тервалы представлены не показаниями часов (тем не менее, в системах реального времени это не исключено), а промежутками между возникновением некоторого события, таким как, например, окончание выполнения того или иного метода, и возникновением какого-то другого события. Объект архитектуры РАСТ должен построить объекты-наблюдатели с целью текущего контроля за состоянием тести- руемого объекта. Ниже дано описание трех определенных выше операторов, а также описание того, как нам удалось провести их успешное тестирование. Оператор Eventually(p) В конечном итоге постусловие Ь.х(), р принимает значение true, однако вре- менное ограничение есть часть постусловия а.у(). Оно устанавливает условие, ко- торое принимает значение true в некоторый момент в будущем. Термин «буду- щее» употребляется в отношении продолжительности существования постусловия а.у(). Все, что произойдет после а.у(), завершится в будущем а. Тестирование этого условия требует задержки принятия решения до тех пор, пока не будет выполнено р. Тестирование должно выполняться в будущем постус- ловия а.у(). В этой ситуации объект архитектуры РАСТ для а помещается в кон- текст, который будет существовать до тех пор, пока существует контекст для Ь. Объект архитектуры РАСТ обладает отдельным потоком. Периодически объект а архитектуры РАСТ активизируется и отправляет сообщения объекту Ь, дабы опре- делить, не завершил ли выполнение Ь.х(). Когда выясняется, что произошло вы- полнение постусловия Ь.х(), объект РАСТ регистрирует этот факт и использует результаты других частей постусловия а.у() для определения, выполняется ли все постусловие. Результат проверки также регистрируется. Объект РАСТ продолжает опрашивать объект b до тех пор, пока либо произой- дет выполнение постусловие Ь.х() и значение р можно будет вычислить, либо b будет удалено. Если объект РАСТ продолжает следить за тем, когда b будет удале- но, он зафиксирует отказ.
Главе 8. Тестирование распределенных объектов Рисунок 8.4. Оператор Eventually(p) Оператор until(p,q) До тех пор пока не выполнится b.x(), b находится в состоянии s(. Это условие можно представить в виде выражения until(sp b.x()). Для проверки, что это огра- ничение выполняется, потребуется провести тестирование, начиная с момента, в котором это утверждение все еще имеет силу, и до момента, когда произойдет событие Ь.х(). Объект РАСТ «выстреливается» в момент, когда указанное ограни- чение вступает в силу. Затем он периодически активизируется и проверяет это условие. В случае оператора until нам надо проверять, что условие «Ь находится в состо- янии s(» равно true в каждый момент времени до тех пор, пока (until) не произой- дет событие, указанное в спецификации. Пользуясь оператором eventually, мы всего лишь один раз вычисляем истинное значение соответствующего условия, в то время как в случае оператора until мы должны раз за разом вычислять значение этого условия, «пока» не (until) произойдет определенное спецификацией собы- тие. Если значение этого условия составляет true в каждый момент времени вплоть до завершающего события, то тестируемое ограничение удовлетворяется. Разумеется, мы не можем все время проверять это истинное значение, по- скольку тогда система не сможет выполнять никакой другой работы! Если мы бу- дем проводить проверку только в те моменты, когда происходят события, то не можем знать, какое значение имело рассматриваемое ограничение до данного мо- мента, при этом рассматриваемое ограничение не может сказать, что произойдет после того, как событие совершится. Тестировщик, проводящий тестирование объекта РАСТ, должен исследовать все, что происходит до момента возникнове- ния события. Однократная проверка едва ли окажется достаточной. По существу, мы устанавливаем интервал в момент создания объекта РАСТ. Чем короче интер- вал, тем точнее оценка. Оператор always(p) Временной оператор always используется намного интенсивнее остальных опе- раторов, поскольку каждый инвариант класса по существу представляет собой ог- раничение always. Ограничение always гласит, что в любой точке в коридоре b ло- гический оператор ограничения р принимает значение true. 11*
Главе 8. Тестирование распределенных объектов Рисунок 8.5. Оператор until(p,q) Как инвариант класса, оператор always можно рассматривать в качестве вре- менного интервала, который соответствует времени существования каждого эк- земпляра класса. Как уже отмечалось при исследовании оператора until, проводить непрерывное тестирование нельзя. Мы пойдем на компромисс и будем делать пе- риодические выборки и каждый раз вычислять истинные значения. В главе 5 речь шла о том, чтобы выполнять тестирование инвариантов классов в конце каждого тестового случая. Этот выборочный метод применяется по умолчанию: тестиро- вать при каждом вызове тестового метода. Потенциальная проблема заключается в том, что конкретный тестовый случай может потребовать вызова нескольких мето- дов. Вот почему в рамках того или иного метода тестового случая мы вызываем, причем неоднократно, метод оценки инвариантов, если этот тестовый случай не- посредственно обращается к определенному количеству методов для тестирования OUT-объекта. Каждый временной оператор привносит различные условия тестирования, но всякий раз, когда такой оператор используется, он следует одним и тем же базо- вым шаблонам. Базовый принцип, который мы здесь отстаиваем, заключается в том, чтобы обеспечить себе возможности для построения собственных тестовых шаблонов. Рисунок 8.6. Временной оператор always(p)
Глава 8. Тестирование распределенных объектов Условия испытаний Тестирование классов Тестирование классов распределенных компонентов часто требует построения специальной среды, подобной показанной на рис. 8.7. Назначение такой среды заключается в том, чтобы предоставить средства для перехвата сообщений, пере- даваемых OUT-объекту, с тем, чтобы стал возможным анализ таких проблем, как синхронизация сообщений, и регистрация изменений состояний. Данная оболочка представляет собой объект, который просто инкапсулирует OUT-объект. Мы использовали различные типы реализации с целью автоматичес- кого извлечения интерфейса OUT-объекта и копирования его в оболочку. Объект-оболочка может быть введен в контексты более крупных систем и по от- ношению этой крупной системы вести себя именно так, как ведет себя OUT- объект. В оболочке имеются реализации каждого метода интерфейсов OUT-объек- та на достаточно высоком уровне стандартизации. Каждый такой метод осуществляет любой требуемый вид регистрации и направляет сообщение реаль- ной реализации в OUT-объекте. Обратный путь возвращает управление оболочке. В это время метод проверяет состояние OUT-объекта, регистрирует результаты проверки и возвращает объект, поступивший с OUT-объекта. Такой подход вторгается только в крупные системы и не считается пригодным для тестирования данного контекста. Его целью является только тестирование OUT-объекта. Рассматриваемый подход полезен также и для тестирования переупорядочения асинхронных сообщений. Объект-оболочка может просто получить и удерживать сообщение! до тех пор, пока не будет получено сообщение2. Затем он может от- править сообщение2 по нужному адресу до того, как отправит сообщение!. Рисунок 8.7. Оболочка для тестирования класса
Глава В. Тестирование распределенных объектов Одно из достоинств этого метода заключается в том, что он позволяет прово- дить в других частях системы сложные вычисления, которые трудно выполнять в рамках средства тестирования, и предоставляет возможность их использования при тестировании классов. Оболочку можно построить автоматически, исключе- ние составляют реализации методов OUT-объекта, помещенного в оболочку, но даже эти методы используют функциональные средства класса оболочки для вы- полнения регистрации, проверки и других операций. Тестирование взаимодействий При тестировании взаимодействия двух объектов, которые, находясь в разных процессах, отделены друг от друга, используется конфигурация, подобная пред- ставленной на рис. 8.8. Как уже отмечалось в главе 6, тестирование полного про- токола представляет собой один из важнейших аспектов тестирования взаимо- действий. В распределенных системах одним из вопросов, подлежащих обязательному решению во время тестирования, является проверка, получены ли сообщения в указанном порядке, даже если они отправлены в последовательнос- ти, описанной в протоколе. В тестовой среде, показанной на рис. 8.8, присутствие распределенной тесто- вой архитектуры позволяет выполнять многие операции локально. Иначе говоря, когда тестовый драйвер, инициирует тестирование, он информирует тестовый драйвер2, какой тест был выполнен. Тестовый драйвер2 после этого может прове- рить на машине2 тот факт, что порция теста, которая выполнялась на машине2, была успешно завершена. Затем он информирует об этом тестовый драйвер,. Это существенно уменьшает объем информации, которая должна была перетекать из одной машины в другую, и ускоряет процесс тестирования. Машина 1 Тестовый драйвер 1 Объект 1 подвергается тестированию Машина 2 /* Тестовый драйвер 2 try{.. .} 1 catch (Exception еХ) Ч Объект 2 подвергается <“□ тестированию methodi (){ . Т throw new exception РисуНОК 8.8. Тестирование взаимодействий
Глава 8. Тастироаанив распрадалвнных объектов Тестовые случаи Большая работа была проделана, чтобы сделать инфраструктуры распределен- ной системы максимально абстрактными, так что пользователи не были слишком озабочены в отношении семантики распределения. Каждый поставщик программ- ных продуктов направляет свои усилия на то, чтобы их продукты отвечали со- ответствующим стандартам. Оба эти фактора делают возможным построение наборов тестовых случаев, отражающих специфику конкретных моделей, а затем и наборы тестовых случаев, ориентированных на специфику конкретных применений. Тестирование, отражающее специфику конкретных моделей Результатом каждой стандартной модели является ее собственный набор про- ектных шаблонов. В свою очередь, это приводит к появлению некоторого множе- ства тестовых шаблонов. Тесты для базовой модели клиент/сервер Выше уже приводилось описание двух типов тестов для модели клиент/сервер, но в то же время, существуют различные варианты базовой модели клиент/сервер. В описываемых ниже тестовых шаблонах в качестве тестируемого шаблона высту- пает широко используемый вариант, получивший название распределенные обрат- ные вызовы (distributed callbacks). Задача; Синхронный обмен сообщениями между двумя объектами преобразу- ется в асинхронный обмен сообщениями путем добавления объекта Callback. Клиент создает объект Callback и передает ему запрос и адрес сервера. Объект Callback синхронно передает этот запрос серверу. Как только ответ получен, объект Callback направляет его объекту Client. Контекст: Используется программный код тестируемого проекта, поскольку проектировщик предпочитает заниматься другой работой, пока готовится ответ на это сообщение. Вполне возможно, что исходный поток завершит свою работу еще до того, как ответ будет готов. Факторы влияния: Функциональные тесты можно считать успешными при их однократном выполнении, однако условия состязаний могут привести к противо- речивым результатам, т.е. при повторном выполнении одного и того же теста можно получить совершенно другие результаты. На обнаруживаемость отказов в условиях состязаний влияют многочисленные факторы. Рисунок 8.9. Добавление обратных вызовов в шаблон клиент/сервер
Глввв 8. Тестирование рвспрвдвлвнных объектов Решение: Постройте тестовые наборы, в которых каждый тестовый случай вы- полняется многократно. Такой тестовый набор должен настроить факторы таким образом, чтобы условия состязания обеспечивали большую обнаруживаемость. После каждого теста система должна возвращаться в свое первоначальное состоя- ние. Тестирование должно содержать следующие тесты (см. рис. 8.10): Тест, в котором сервер возвращает ожидаемый результат практически не- медленно. Тест, в котором клиент удаляется, прежде чем будет получен обратный вызов. Тест, в котором сервер генерирует исключительную ситуацию. Тест, в котором сервер удаляется, прежде чем вернет значение. Тесты для обобщенной модели распределения Теперь вернемся к обобщенной распределенной архитектуре и рассмотрим не- которые из тестов. Чтобы сделать это возможным, мы составили тестовые планы для объектов Provider и Requester (см. рис. 8.11 и рис. 8.12). Эти планы не ориен- тированы на семантику каждого из указанных выше объектов, они направлены на исследование основных функций каждого компонента. Тестирование каждого предположения Различные модели распределений выдвигают различные предположения отно- сительно типа приложения или среды разработки. Они должны представлять со- бой основную задачу тестов. Некоторые из них выполняются на стадии целенап- равленной проверки, в то время как другие должны быть отложены до появления выполняемых файлов. Рисунок 8.1 □. Тестирование распределенного шаблона обратного вызова
Глава 8. Твстированив распределенных объектов План тестирования компонентов Имя Компоненте Поставщик служб Идентификационный номер TBS Разреботчик(и): Dave Sykes Тестироащик(и): John D. McGregor Назначение денного компонента Данный компонент предназначен для использоввния в интерфейсе пользова- теля в приложениях, разрвбвтывавмых компвнивй. Он является чвстью библио- теки, приобретенной у компвнии NoTest Softwere Company («Компания, которая не проводит тестироввния программного обеспечения»]. Трабоаения целенепреаленной проаерки Цвпвнапрввлвнная проверка модели проектируемой распрвдвлвнной системы должна проварить, облвдавт ли полнотой нвбор служб поставщика служб по отношению к вго роли как объекта предметной облвсти в модели анализа. Спе- цификация постввщика служб должна сделать общедоступными тв виды служб, которые соответствуют потребностям объвктов-звпросчиков служб. Сигнатуры ме- тодов должны быть корректными по отношению к модели предметной облвсти. Построение и сохранение тестоаых набороа Тестовые наборы должны быть подготовлены в соответствии с проектными стандартами. Класс ProvideTeater должен содержать тестовый драйвер. Этот клвсс нвслвдувт от клвссв GenerelModeTeater, который спвцивльно првднвз- нвчвн для используемой модели рвспрвдвлвния. Каждый поставщик служб, поставляющий тот или иной протокол, можвт использовать одни И ТВ жв тестовые случаи; твким образом, они должны быть сгруппированы в отдельный объект и агрегированы в классе ProvideTeater. Тестоаые случаи, ориентироаенные не спецификацию Каждый поставщик служб постввлявт набор служб нв основании модели рвс- првдвлвния. Тестовые случаи, основанные нв этих службах, находятся в клвссе GeneralModeTeater. Тестовые случаи, основанные нв службах, отражающих специфику применения, должны иметь тестовые случаи в класса ProvideTeater. Тестоаые случаи, ориентироаенные не реализацию Эти тестовые случви должны соответствовать обычным критериям покрытия путей и программных кодов. Тестоаые случаи, предназначенные для тестирования взаимодействия и функционирования компонентов Поставщик служб должен вступить во взвимодвйствив с представительной выборкой объектов звпросчикв служб, которые будут запрашивать эти службы. Каждый важный протокол должен подвергаться тестированию при участии, по мвньшвй мврв, одного звпросчикв служб, принимающим участие в протоколе. Тестоаые случаи, ориентированные не состояния Поставщик служб должен находиться в одном из нескольких состояний, когдв звпросчик служб обращается к нему с запросом, и в то жв время может получать запросы от многих поставщиков служб. Постввщикв служб слвдувт проверять нв всем протяжении вго жизненного цикла с использованием асах необходимых для этой цели протоколов. Рисунок 8.11. План тестирования компонентов поставщика служб.
Глввв В. Твстироввнив рвспрвдвлвнных объектов План тестирования компонентов Имя компоненте Запросчик служб Идентификационный номер TBS Разреботчик(и): Deve Sykes Тестировщик(и): John D. McGregor Назначение данного компоненте Звпросчик служб в рвспрвдвлвнных системах выступает в ропи инициатора конкретных действий, звпрвшиввя службы от объектов постввщикв служб. Он осуществляет управление информацией, которая возвращается от поставщика служб в ответ на запрос. Требования целенаправленной проверки Целенаправленная проввркв модели проектируемой рвспрвдвлвнной системы должна проверить, что звпросчик служб имввт доступ к полному набору служб постввщикв служб, в которых он нуждается. Если звпросчик служб пользуется службами многих поставщиков служб, проводимая проввркв должнв поквзать, что различные поставщики служб обеспечивают получвнив непротиворечивых результатов одной и той жв службы. Проввркв должнв продвмонстрироввть, что квждый метод звпросчикв служб получввт требуемую службу в нужное время. Построение и сохранение тестовых неборов Тестовые наборы должны быть подготовлены в соответствии с проектными ствндвртвми. Клвсс ProvideTeater должен содержать тестовый дрвйввр. В этом клвссв првдостввпяются опврвции, необходимые для прогона функциональных и структурных тестовых спучввв, в твкжв тестовых случаев, предназначенных для твстироввния взвимодвйствия и функционироввния компонентов. Отчвтнвя информация должнв соответствовать проектным ствндартам. Тестовый класс должен быть доступен для разработчиков приложения с тем, чтобы стало возможным проввдвнив твстироввния взвимодвйствия и функционирования компонентов в их приложениях Тестовые случаи, ориентированные на спецификацию Сформулируйте предусловия и постусловия для квждого метода задокументи- рованного API-интврфвйсв. Выполните тестовые случаи для квждого раздала квждого постусловия всех без исключения методов. Тестовые случен, ориентированные на реализацию Эти тестовые случви должны соотввтствоввть обычным критериям покрытия. Воспользуйтесь методами отбора и построения твстов, описания которых даны в главах 5 и 6. Тестовые случаи, предназначенные для тестирования взеимодействия и функционироввния компонентов Основные тесты взвимодвйствия и функционироввния компонентов проводятся на поставщиквх, службами которых пользуется звпросчик служб. Основное внимвние должно быть сосредоточено не полных протоколах обмене денными между зап- росчиком служб и каждым из вго поставщиков служб. Потрвбувтся встроить искусственные задержки с целью исследования влияния синхронизации. Тестовые случаи, ориентированные на состояния Запросчики служб пврвходят в одно из состояний, характерных для компонентов распределенных систем, такие как состояние, в котором производится подклю- чение к инфраструктура, или состояние отключения от инфраструктуры. Полный тестовый набор должен провести проварку этих состояний, равно квк и твс- тироввнив семантики рассматриваемого приложения. Рисунок 8.12. План тестирования компонентов запросчика служб.
Глава В. Твстированив распраделвнных объектов Проблемы языковой зависимости Модель RMI предполагает, что та часть системы, для которой она использует- ся, написана исключительно в языке Java. Технология CORBA не делает никаких предположений относительно языков не только многих, но даже всего лишь двух взаимодействующих запросчиков служб и поставщиков служб. Даже для тех слу- чаев, когда компоненты, реализованные на разных языках программирования, взаимодействуют через инфраструктуру, должны быть написаны специальные тес- ты. Программные коды инфраструктуры подвергаются тестированию, они пра- вильно выполняют передачу данных, тем не менее, программный код приложения может оказаться некорректным. В зависимости от инфраструктуры, программисты обладают той или иной возможностью управления (отсюда, собственно говоря, и вытекает вероятность возникновения ошибок) и силу этого обстоятельства обяза- ны вручную позаботиться о том, чтобы типы данных, используемые в обоих клас- сах, были совместимы. Документация по инфраструктуре может оказаться весьма полезной, если в ней описано, какие имеются возможности в этом плане. Мы недавно столкнулись с ошибками, когда передавали массив данных от запросчика служб на Java поставщику служб на C++, причиной которых были неточности, допущенные в документации. Тестовые случаи не только обнаружили неисправ- ность, но и указали на источник проблемы. Во время целенаправленной проверки проверяющий персонал должен убе- диться в том, что были выполнены правильные преобразования данных. Техноло- гия CORBA, например, использует набор типов, которые в значительной степени совместимы с большинством типов данных C++. Различия между типами данных в CORBA и в Java намного больше. Java не обеспечивает непосредственной под- держки параметров “out”. Проверка должна продемонстрировать, что возвращае- мые объекты обрабатываются должным образом. Проблемы независимости от платформы По существу, все модели распределений не зависят от платформ, на которых они выполняются, хотя в настоящее время модель DCOM используется главным образом на Intel-совместимых платформах. В то же время, исключительно важное значение приобретает более сложная проблема развертывания среды. Неявные требования, предъявляемые к размеру пространства памяти или к быстродей- ствию процессора, могут послужить причиной различий в работе одного и того же программного продукта на разных типах машин. Одним из методов, который, как мы убедились, оказался полезным, заключает- ся в том, чтобы совместить тестирование развертывания системы с выпуском про- граммного продукта. В этом случае каждый пользователь может самостоятельно прогнать эти тесты после установки программного продукта с тем, чтобы опреде- лить, корректно ли функционирует приложение. Более подробно эти вопросы бу- дут рассматриваться в главе 9.
Глава 8. Тастироаанив распределенных объактое Тестирование инфраструктуры Инфраструктура, поставляемая производителями программного обеспечения, принадлежит к категории программных продуктов, которым можно «доверять», т.е. ее не обязательно подвергать подробному тестированию. Однако имеют место ситуации, которые не поддаются контролю со стороны производителя программ- ного обеспечения и которые могут нанести вред инфраструктуре. Например, ске- леты и заглушки, необходимые для реализации технологии CORBA, автоматичес- ки генерируются компилятором, ориентированным на IDL-спецификации. Зачастую разработчики редактируют такие реализации по умолчанию. После этого такой программный код не заслуживает нашего доверия. В этом случае потребует- ся провести тестирование, по меньшей мере, тех фрагментов, которые подверга- лись изменениям. Проверка на совместимость Когда выпускается новая версия инфраструктуры, нужно выполнить проверку на совместимость с целью определения, требуется ли модификация приложения. Обычно такая проверка выполняется специально назначенной группой лиц, при- нимающих участие в разработке проекта. Такой же вид тестирования должны пройти новые версии структур и даже новые версии инструментальных средств. Тестирование восстановления после отказов Одним из критических факторов, характеризующих распределенные системы, является возможность частичных отказов системы по причине выхода из строя машины, на которой размещена система. В рамках тестирования развертывания системы, потребуется построить типы тестовых случаев с использованием распре- деленного средства тестирования, показанного на рис. 8.8. Следует построить та- кую системную конфигурацию, в которой имеется «главная» машина, где функ- ционирует часть инфраструктуры, куда относится локатор (такое возможно далеко не для всех систем) и для которой на специальной машине выполняется экземп- ляр сервера. Как только этот сервер будет зарегистрирован в рассматриваемой инфраструктуре, тестовый драйвер на машине, на которой работает сервер, выво- дит на ее экран диалоговое окно, предлагающее тестировщику отключить от этой машины сетевой кабель. Как только пользователь щелкнет в упомянутом окне на кнопке ОК, этот тестовый драйвер пошлет соответствующее сообщение главному тестовому драйверу. Главный тестовый драйвер инициирует последовательность действий, в которой предпримет попытку наладить связь с сервером, ставшим те- перь недоступным. Способность распознать ситуацию, когда сервер недоступен, и выполнить ее обработку должным образом есть одно из неявных требований, ко- торое обсуждалось в разделе «Неявное тестирование». Правильность реализации зиждется на квалификации конкретных разработчиков, а не на подробной специ- фикации.
Глааа В. Тестироаание распределенных объактов Динамическая модификация инфраструктуры Реализация инфраструктуры, ориентированной на технологию CORBA, обла- дает средствами, с помощью которых она может быть модифицирована во время выполнения программ. Один из поставщиков программных продуктов, например, обеспечивает возможность вводить или удалять «фильтры» с путей между запрос- чиком служб и поставщиком служб во время выполнения. Такие модификации приводят к изменению конфигурации системы, при этом могут быть затронуты синхронизация и выполняемая ветвь. Поскольку такие модификации, как прави- ло, возникают в необычных ситуациях, должны быть построены такие тесты, ко- торые проверяли бы каждую из возможных конфигураций доступных динамичес- ких компонентов. Тестовые случаи для проверки логических ошибок Виды логических ошибок, которые могут возникать в распределенных систе- мах, практически не отличаются от логических ошибок, которые имеют место в последовательных системах, за исключением, разве что, одного-двух видов. Различные последовательности событий В условиях, когда процессы обмениваются между собой сообщениями в асин- хронном режиме, события могут происходить в различных последовательностях. Запросчик служб может отправить несколько запросов за короткий период време- ни и не ждать завершения ни одного из них. Порядок, в котором запросы возвра- щаются, может существенно изменяться от одного выполнения приложения к другому. Если структура приложения такова, что нет разницы, в каком порядке получены ответы, задачей тестирования становится проверка максимально воз- можного количества комбинаций. Можно воспользоваться методами статистичес- кой выборки, которые обсуждались в главе 6, и определить минимальное количе- ство необходимых тестов. Запрашиваемые объекты недоступны Многие системы дают пользователям возможность вводить имена поставщиков служб и других ресурсов. При вводе имен пользователи могут допускать ошибки или запрашивать ресурс, который когда-то был доступен, но в момент запроса уже не существовал. Это вполне ординарная ситуация при работе с браузерами Internet. Она несколько отличается от предыдущего случая, когда объект зарегист- рирован как ресурс, но недоступен в силу выхода из строя машины. В случае ча- стичного отказа инфраструктура возвращает нулевой указатель, поскольку в дан- ной ситуации инфраструктура может инициировать исключение. Этот тип отказов и тестовые случаи, обеспечивающие его обнаружение, имеют смысл толь- ко в случае, когда идентификатор поставщика служб запрашивается в динамичес- ком режиме. Цель тестирования здесь заключается в том, чтобы определить, было ли исключение зафиксировано в надлежащем месте запросчика службы и способ- но ли приложение аккуратно завершить операцию и предоставить пользователю
Глввв 8. Тестирование рвспрвдвлвнных объектов еще одну возможность ввести адрес поставщика служб либо выдать какой-то дру- гой подходящий ответ. ------------------------------------------------------------------------------ ОБОБЩЕНИЕ ТРЕБОВАНИИ К ТЕСТОВЫМ СЛУЧАЯМ Используйте тестовые нвборы, обеспвчивеющив следующие покрытия: 1. Кеждого методе квждого стендертного интерфейса 2. Квждого SYN-пути 3. Кеждой ветви логического упревлвния. Многократно примвняйтв тестовые случеи, верьируя следующие фекторы: 1. Зегрузку приложений, работающих в тех жв самых системах 2. Зегрузку пользовательского вводе в итоговую систему 3. Соединения между мешинеми 4. Конфигурвции инфрвструктуры. Максимальный вариант распределенной системы - Internet Internet представляет собой очень большую и динамическую распределенную систему. Серверы добавляются и удаляются в этой сети непрерывно. Приложе- ния, которые существуют в этой среде, должны быть способны функционировать в условиях частичных отказов в виде отсутствующих систем и несуществующих адресов машин. Некоторые из этих систем образуют базовые коммерческие среды для компаний, эксплуатирующих собственные Web-сайты. Такие системы элект- ронной коммерции предъявляют жесткие специальные требования к надежности и безопасности. В этом разделе мы рассмотрим проблемы, характерные для сред упомянутого типа. Мы не предлагаем читателю полный обзор, мы просто наброса- ем схему того, как такие системы следует тестировать. «Web-страницы», которые выводятся на экран браузером, содержат данные в виде маркированного текста с использованием языков HTML или XML, а также поведение в виде сценарных функций, реализованных на языке сценариев, таком как JavaScript. Эти страницы часто позволяют выполнять ввод данных пользова- теля, производить вычисления и получать вывод. Браузер представляет собой рас- ширяемое приложение, которое обладает способностью распознавать, когда дос- тавляемые им данные требуют дополнительной прикладной программы для их представления в требуемом виде. Подобного рода «сменные» модули динамически расширяют набор функциональных средств до такой степени, что браузер изменя- ет среду выполнения программ. Что означает тестирование Web-страницы? Это означает тестирование того, что на экран пользователя выводятся нужные данные, что ввод принимается и на- правляется соответствующему приложению, пребывающему в режиме ожидания, и что все действия выполняются в соответствии с замыслом. Сбои отображения происходят в следующих случаях:
Глава В. Тастироаание распределенных объактов На экране отображается не те шрифты, которые выбраны разработчиком Неправильно выбраны координаты, из-за чего изображение не видно на эк- ране Ошибка в выборе языка либо различные языки выбраны для представления на экране различных разделов текста Несоответствие конфигурации платформы и атрибутов отображения браузера. Существуют фрагменты программных кодов, встроенных в отображаемый файл, а также отдельные файлы сценариев. Браузеры могут непосредственно вы- полнять программы, написанные в таких языках программирования как, скажем, Java. Java-версия игры «Кирпичики» выполняется в браузере в виде аплета. Брау- зер и стандартные сменные модули принадлежат к категории программ, «пользую- щихся доверием». Объектами тестирования является содержимое отображаемых дан- ных плюс вложенные и внешние файлы сценариев. Возможны следующие отказы: Неправильная или недостаточная санкция на выполнение программного кода, содержащегося в специальном файле Невозможность создания файла в требуемый момент Обычные отказы, связанные с любой из функций, например, вычисление неправильного ответа Невозможность обнаружить место нахождения ресурсов во время выполне- ния программ, например, отсутствие необходимых битовых образов. При помощи одиночной страницы в браузер могут быть добавлены сразу не- сколько фрагментов программного кода. Такими фрагментами могут быть средство просмотра PDF-файлов, видеопроигрыватель RealPlayer и даже брокер Visigenics CORBA ORB. Будучи однажды загруженным через страницу, программный код может оставаться таковым на все время загрузки других страниц и добавления сменных модулей. Невозможно подвергнуть тестированию все комбинации про- грамм, обеспечивающих взаимодействие. Тем не менее, можно построить тест, который обеспечивает загрузку различных комбинаций программ в среду браузе- ра. Вы должны проанализировать списки ресурсов, используемых вашими страни- цами, и построить всевозможные пути через страницы, которые требуют загрузки различных комбинаций сменных программных модулей. РАЗЛИЧИЯ В ТЕСТИРОВАНИИ ИНТЕРФЕЙСОВ GUI И API Ранаа уже обсуждалось, чвм отличается тастироаанив приложений, начиная от GUI, от тестироаания приложений, начиная с их API-интерфайса. В этой ситуации уровень API выбирается не слишком часто, поскольку функции, вложенные в Web-страницы, визуально ориентированы таким образом, что приманание интерфейса GUI требует меньших усилий. Отображаемый такст можвт подвергаться синтаксическому анализу и проверке на правильность. Посла этого производится постровниа тастов, обеспе- чивающих выполнение тестового сценария с целью достичь уровня покрытия, срав- нимого с уровнем, характерным для языков программирования.
Глааа 8. Тестирование распределенных объектов Web-серверы Время от времени браузер и Web-страницы функционируют в сочетании с сер- вером приложения при вызове других приложений (см. рис. 8.13). Такие серверы представляют собой параметризованные программные приложения, поставляемые производителями программных продуктов; следовательно, после приемочных ис- пытаний они попадают в категорию программ, «пользующихся доверием». В со- став этого сервера входит база данных, в которой хранятся как содержимое стра- ниц, так и данные, полученные пользователями системы. Такие системы автоматизируют определенные аспекты службы пользователей. Страница может принимать входные данные в специальный бланк заказа пользо- вателя, создавать запись клиента, вызывать приложение, которое автоматически генерирует учетный номер и пароль, после чего пересылать эту учетную информа- цию пользователю. Такие системы разрабатываются по модульному принципу, посему они облада- ют высокой степенью модульности. Прикладные программы могут претерпевать изменения, при этом изменения не коснутся Web-сервера, а Web-страницы под- вергаются только незначительной модификации. Изменения, затрагивающие Web-страницы, представляют собой простые изменения данных, которые часто хранятся в формате XML. Для тестирования таких систем характерны два аспекта. Во-первых, справля- ются ли тестовые сценарии с возложенными на них задачами? За весьма короткое время появилось множество браузеров и принадлежащих им языков написания сценариев. В результате, пока вы не развернете браузер в управляемую среду, в который каждый работает с одной и той же версией, прогон тестов должен осуще- ствляться на различных сочетаниях браузера и языка сценариев. Фреймы и табли- цы могут служить примерами средств, которые радикально изменяются при пере- ходе из одной среды в другую и приводят к большому разбросу во время отображения. Многие разработчики предусматривают альтернативные пути в за- висимости от того, поддерживает ли данный браузер фреймы или нет. Следует запланировать выполнение тестовых случаев, способных обеспечить покрытие упомянутых ситуаций. Рисунок 8.13. Архитектура Web-cepeepa
Главв В. Тестироввние распределенных объектов Во-вторых, корректны ли текущие данные и представлены ли они в форме, в какой их готовы принять приложения и Web-страницы? Тестирование этого ас- пекта требует сценариев, в которых отображения данных Web-сайта производится в сочетании с типами данных, которые будет вводить пользователь. Такой анализ подобен процессу, который описан в главе 9 в разделе «Использование тестового драйвера для построения тестовых случаев». Проводится анализ типов данных каждого ввода, определяются эквивалентные классы, и производятся выборки из каждого класса. Тестирование Internet-приложений на протяжении их жизненного цикла В рассматриваемом контексте тестирование на протяжении жизненного цикла означает тестирование на наборе пользовательских транзакций. Эти транзакции должны быть выбраны таким образом, чтобы был использован полный набор сер- верных приложений. Перспектива жизненного цикла обычно охватывает несколь- ко различных платформ, в том числе и систему пользователя, Web-сайт и прило- жения сторонних разработчиков. Например, в случае систем электронной коммерции мы начинаем тестирование с предоставления материалов пользовате- лю. Насколь точны эти материалы? Многие из Web-страниц ориентированы на ис- пользование информации, представленной в виде дерева XML. Возможна ситуа- ция, когда программа некорректно связывает описания и цены. После предоставления материалов следует прием заказа пользователя. При этом Web- страница получает и обрабатывает события и данные, для чего требуется база дан- ных, которая применяется для управления запасами и хранения в виде записей данных о заказах пользователей. Это приложение должно предоставить пользова- телю возможность вносить изменения в заказы путем добавления или удаления тех или иных элементов. Другие приложения используются для заказа товаров у поставщиков с тем, чтобы они могли выявлять новые заказы, группировать това- ры по конкретным поставщикам и реализовать электронное взаимодействие с приложениями поставщика. Соответствующий тестовый набор должен быть спо- собен распознавать многочисленные взаимосвязанные жизненные циклы на базе действующих субъектов системы. Клиентский действующих субъект обладает жизненным циклом, достаточным для формирования того или иного заказа, в то время как действующий субъект поставщика обладает жизненным циклом, обес- печивающим возможность построения различных типов заказа. В рамках этого примера интенсивная тестовая деятельность проводились в двух измерениях. Во-первых, завершенные системы, разрабатывавшиеся различ- ными коллективами программистов, должны взаимодействовать. Само программ- ное обеспечение Web-сайта объединяет в себе несколько различных технологий. Рассматриваемая в качестве примера система, по всей вероятности, использует технологии HTML, JavaScript, XML и Java. При этом возникают проблемы совме- стимости типов данных, синхронизации событий, а также проблема необходимо- сти ограничения их воздействия на работу системы.
Глааа 8. Тестирование распределенных объектов Один из наиболее важных тестов Web-приложения предусматривает построе- ние тестовых случаев, которые проверяют работу Web-сайта в условиях предельно допустимой нагрузки. Существуют инструментальные средства, упрощающие за- дачу клонирования Web-страниц и вызова на сервере множества операций. Тест предельно допустимой нагрузки должен быть спроектирован так, чтобы в точнос- ти воспроизводить ожидаемый профиль использования. Должны быть запланиро- ваны тесты способности сервера ограничивать количество соединений в управля- емом режиме (отображение на экране соответствующего сообщения), а не в катастрофическом режиме (полный отказ сервера). По-видимому, наиболее важной функцией Web-страницы является распозна- вание ситуации, когда сделанный ею запрос не выполнен. Браузеры устанавлива- ют специальные таймеры, чтобы отслеживать каждое соединение с другим ресур- сом, таким как, например, другие страницы и серверы. Поскольку браузер принадлежит к категории программ, «пользующихся доверием», мы не подвергаем тестированию возможность распознавания ситуации, когда выявляется неудачная попытка получения ресурса. Вместо этого мы проверяем, корректно ли сценарии обрабатывают отказ подобного рода данной Web-страницы. Что мы еще не успели сказать? Здесь мы не рассматривали таких важных областей, как характеристики произ- водительности и безопасности. Количество инструментальных средств, предназ- наченных для решения этих проблем, непрерывно возрастает в силу их важности для электронной коммерции. По мере того как вторая, большая волна развертыва- ния сайтов электронной коммерции набирает силу, эти проблемы все больше бу- дут выдвигаться на первый план. Исследовательские работы в данном направле- нии продолжаются, и уже разработаны более совершенные модели, описывающие характеристики производительности систем. Вполне возможно, что уже в следую- щем издании этой книги мы затронем эти проблемы. Резюме Распределенные объекты — это прежде всего объекты, которые нуждаются во всех типах тестирования, как и другие объекты. Одно из главных различий между распределенными и нераспределенными системами определяется важностью зада- чи синхронизации. На синхронизацию оказывают влияние последовательность операторов в методе, алгоритмы планирования операционной системы, количе- ство объектов и отношения между ними. Главный вопрос заключается в том, до- статочно ли хорошо конструкция системы отражает временные соотношения, ус- тановленные в процессе проектирования. Основной вопрос, касающийся тестирования, звучит так: обладает ли реализация достаточной гибкостью, дабы корректно функционировать перед лицом всех предъявляемых требований. Мы провели исследования временной логики как описательного средства и оснащен- ности программного обеспечения средствами контроля, позволяющими проводить исследования результатов выполнения различных последовательностей.
Глава 8. Тастированиа распределенных объектов Упражнения 8-1 Проведите исследования инфраструктуры распределения, используемой в текущем проекте. Определите условия, в которых эта инфраструктура меняет собственную структуру, и каким образом изменяется путь запроса. Определите набор условий, при которых достигается покрытие всех возможных конфигураций. 8-2 Постройте тестовый набор для тестирования одновременно выполняемого прило- жения путем указания точек, в которых возникают SYN-события, и определения SYN-последовательностей для выполнения тестовых случаев. 8-3 Проведите исследование программного обеспечения, которое вы разрабатывали с использованием проектных шаблонов, и выберите один из этих шаблонов для последующего изучения, или же прочтите описание проектного шаблона. Как вы тестировали или намерены тестировать программное обеспечение, разработанное с применением тестовых шаблонов? Составьте описание в стиле тестовых шаблонов. 8-4 Определите Web-сайт, подлежащий тестированию. Взяв за основу приложение Web- сайта, опишите набор действий на Web-сайте, представляющий обычный сеанс от начала до конца, в котором может участвовать посетитель сайта. Задействуйте максимально возможное количество страниц с входными данными. Напишите те- стовый драйвер, который обеспечивает покрытие всех этих возможностей.
Глава Тестирование систем ► Вы хотите составить плвн тестироввния? См. раздел «Состввление плана тестироввния системы» ► Вы хотите знать, как можно построить тестовыа случаи на базе случаав использования? См. раздел «Случаи использования как источники тестовых случаев» ► Вам нужно знать, как специфические свойства конкретной программы алияют на характер тастирования? См. раздел «Тестирования систем различных типов» ► Вы хотите знать, как выразить качество тестирования в числовых показателях? См. раздел «Измерение тестового покрытия» Мы достигли такой точки в процессе разработки, в которой слово тестирование используется в его обычном смысле. Однако и этот смысл изменился. Понятие си- стемное тестирование обычно относится к тестированию завершенного прило- жения, при этом предполагается, что в нем представлены все поведенческие ас- пекты, необходимые для приложения. Мы рассмотрим несколько более широкое определение, которое охватывает законченные комплектующие модули, способ- ные предложить некоторый набор функциональных возможностей конечного пользователя. Мы уже рассматривали этот уровень тестирования в главе 4 для случая, когда программный код еще не был написан. В данной главе мы исходим из предположения, что выполняеый код уже реализован. После того, как разработка программного продукта завершена, этот уровень TAPTirnnnQuua пагридтпикяртга vav «пНятмиамне» ляблты ыя mvnnv we имрилшхлл iiCpwOticuid. pa3pdUUiihi<kU& v j uu j прйощп.\йв; 1н;ап J ; нриоцгШл ъ *' план разработки обязательно должны координировать подобное взаимодействие. 340
Глава 9. Твстированиа систам Слова «система» и «приложение» одни используют как синонимы, а другие — как самостоятельные понятия. Когда «система» и «приложение» используются как независимые понятия, система означает «полную» среду, в которую входят разра- батываемая программа, т.е. приложение, и операционная среда, включающая опе- рационную систему, виртуальные машины, Web-браузеры и аппаратное обеспече- ние. Мы не будем делать различия между этими двумя терминами. Мы будем их использовать как синонимы, означающие полную среду. В самом начале мы утверждали, что тестирование - это поиск дефектов. Дей- ствительно, это правда, но отнюдь не вся правда. Основное внимание тестирова- ния на этой стадии процесса разработки смещается от поиска дефектов, которые приводят к отказам программ (хотя некоторые из них время от времени будут обнаруживаться), в сторону дефектов, которые представляют собой отклонения фактического функционирования системы от требований, предъявляемых к этой системе. Напомним, что мы приняли эту точку зрения еще в главе 4, в которой рассматривались вопросы тестирование моделей анализа на системном уровне и проектных моделей. Тогда мы проверяли, являются ли модели анализа и проект- ные модели полным, непротиворечивым и правильным решением задач, сформу- лированных в виде требований. Этот ранний этап испытаний приводит к умень- шению количества дефектов, вызванных «отклонениями от требований», обнаруживаемых на этапе тестирования, который носит название «завершения разработки». Этот этап тестирования может оказаться видом многоуровневой деятельности. Если речь идет о программном обеспечении, которое будет выполняться на про- цессоре, оснащенном различными специальными видами аппаратных средств, то потребуется выполнить тестирование этого программного обеспечения на модели- рующей программе того или иного типа, прежде чем переходить на фактические аппаратные средства. Границы «системы» также претерпевают изменения по мере продвижения разработки. В конечном итоге «системные» тесты применяются к продукту, полученному в результате интеграции нескольких вложенных приложе- ний в единую систему. Например, во многие современные автомобили встроены такие регулирующие органы, как солнцезащитные устройства или механизмы ав- томатического управления окнами. Подобные регулирующие органы в ряде случа- ев обмениваются данными и получают данные от датчиков из тех мест, где нет устройств управления. Существуют и другие виды тестирования, которые проводятся в этих точках процесса разработки программного продукта. Они имеют прямое или косвенное отношение к требованиям. Тестирование характеристик производительности, бе- зопасности, загрузки и развертывания системы — это специальные виды испыта- ний, которые проводятся при наличии некоторых особых условий. В этой главе мы рассмотрим эти виды деятельности в контексте объектно-ориентированных систем.
Глава 9. Тестированив систем Многие контракты на разработку предусматривают приемочные испытания, ко- торые устраиваются заказчиком до официального окончания разработки. Этот вид тестирования проводится в среде разработки на площадке заказчика. Заказчик проводит тестирование и определяет, удовлетворительно ли работает программ- ный продукт с его точки зрения. Приемочные испытания представляют собой специальный вид системного тестирования. В этой главе мы продолжаем работы, начатые в главе 4, только теперь мы пред- полагаем наличие готового программного кода. Мы рассмотрим стратегии отбора наиболее эффективных тестовых случаев, и кроме того, рассмотрим, как влияет выбор той или иной стратегии на повышение надежности системы. Но сначала потребуется составить план. Составление плана тестирования системы План тестирования системы представляет собой более формальный и более всесторонний документ, нежели планы тестирования конкретных компонентов, которыми мы руководствовались до сих пор. На рис. 3.14 показан список разде- лов плана тестирования, выполненного в формате, рекомендуемом IEEE (Institute of Electrical and Electronics Engineers — Институт инженеров по электротехнике и электронике). Мы не собираемся рассматривать все эти разделы, а рассмотрим только некоторые, причем часть из них сформулируем применительно к приложе- нию «Кирпичики». В этом параграфе будут затронуты два раздела плана, в после- дующих параграфах мы перейдем к рассмотрению наиболее важных разделов плана тестирования. На рис. 9.1 приводится пример сокращенного плана тестиро- вания игры «Кирпичики», при этом используется формат, какой был использован в предшествующих главах. Свойства, подвергающиеся и не подвергающиеся тестированию Некоторая часть аттестационных испытаний выполняется каждый раз, когда поставляется очередной комплектующий модуль. Вместо того, чтобы включать в план тестирования разделы, предусматривающие тестирование свойств, мы вос- пользуемся графиком, представляющим собой копию плана интегрирования ком- плектующих модулей в систему с указанием даты тестирования каждого из этих комплектующих модулей. Такой план обычно формулируется на базе профилей использования и в силу этого обстоятельства он также является планом тестиро- вания. Для игры «Кирпичики» мы составили план, который, как следует из рис. 3.2, предусматривает три комплектующих модуля. Сначала мы разработали базовую инфраструктуру, а также тестовые случаи “Move paddle”(Перемещение лопатки) и “Lose Puck” (Утрата шайбы). Второй комплектующий модуль содержит тестовые случаи “Break brick” (Разбить кирпич) и “Win/Lose” (Выиграть/проиграть). Третий комплектующий модуль обусловливает наличие тестового случая “Pause” (Пауза). Тестовый план предусматривает подтверждение корректной работы системы в конце каждого приращения.
Главе 9. Тестирование систем План тестирования компонентов Имя компонента Игра «Кирпичики» Идентификационный номер TBS Разработчик (и): D.Sykas.J. McGregor Тестировщик(и): D.Sykas.J. McGregor Назначение данного компонента Двннов приложение представляет собой развлвквтвльную игру. Это означает, что такая игре должна обладать достаточной динамикой, чтобы поддерживать к нвй интерес пользователя. Оне должна быть точной. Кирпич ломается, когда в него ударяет шайбе. При перемещении мыши должна перемещаться лопатка. Требоаания целенаправленной проаерки Проварки нв рвнней стадии разработки проводят анализ структуры игры. Те- стовые случаи цвленвправленной проверки должны быть рассчитаны на не- сколько настольных игр и должны быть способны показать, могут ли классы приложения представлять эти игры. Объектом последующих проверок должны быть детали завершения каждого случая использования игры. Построение и сохренение тестовых небороа Тестовые сцвнврии целанапрввлвнной проварки соответствуют структуре мо- дели случввв использовения. По мврв внесения изменений в каждый случай использования изменениям подвергаются твкжв и связанные с ними тестовые случаи. Каждый тестовый сценарий специализируется на построании тестовых случаев, предназначенных для процессе тестироввния выполняемой системы. Изменения, внесенные в случаи использования, распрострвняются на тестовые сцвнврии и на тестовые случаи. Тестовые случаи, ориентированные на спецификецию Тесты спецификаций суть тесты, в основу которых положены модали случаев использования. Согласно проведанному ранее анализу, они происходят от те- стовых сцанвриав. Тестовые случаи, ориентированные на реализецию Хотя обычно считаатся, что в основу системных испытаний положены требо- вания, мы можем построить тесты, ориентированные на реализацию, по усло- виям которых пути уствнввливвются между видимыми извне модулями, такими как DLL-библиотеки или файлы с расширением .jar. Тестовые случаи, предназначенные для тестирования взаимодействия и функционировения компонентов Нв системном уровне взвимодвйствив происходит с операционной средой. Что касается версии игры «Кирличики» в виде Java-аплвта, то один из самых важных тестов взаимодействия првдусматривавт покрытие аплвта вща одним окном с последующим снятием этого покрытия. Тестовые случен, ориентированные на состояния Модель состояний рвссмвтривавмой системы показана на рис. 9.2. Механизм достаточно прост, так что все пути могут быть покрыты. РисуНОК 9.1. План системного тестирования игры «Кирпичики».
Глава 9. Тестирование систем РисуНОК 9.2. Диаграмма состояний игры «Кирпичики» Критерии приостановки тестирования и требования возобновления тестирования Поскольку персонал, проводящий тестирование, не относится ни к категории отладчиков, ни к категории разработчиков, то если в тестируемой системе обнару- жено недопустимо большое число дефектов, имеет смысл приостановить тестиро- вание еще до выполнения всех запланированных тестов. Обычно мы начинаем тестирование с одного из случаев использования, запланированного для текущего приращения, после чего переходим к тестированию следующего случая использо- вания. Если прогон тест на каком-либо случае использования завершается не- удачно, мы переходим к тестированию следующего случая использования. Тести- рование приостанавливается, если не найдется ни одного случая использования, тестирование которого завершается успешно. Тестирование возобновляется, когда будет выполнена определенная работа, в результате которой значительный про- цент случаев использования успешно проходит тестирование. Дополнительные стратегии, используемые при отборе тестовых случаев Существуют два возможных способа отбора тестовых случаев для тестирования системы. Один из подходов предлагает задуматься над тем, какие типы дефектов может содержать в себе программный продукт, и написать тестовые случаи, спо- собные их обнаружить. Второй подход требует определиться с тем, как система будет использована, и построить тестовые случаи, которые проведут испытание системы на всех этапах использования. В следующем разделе мы опишем каждый из этих подходов и покажем, как ими пользоваться в целях достижения эффек- тивной стратегии тестирования. В конечном счете, мы отдадим предпочтение вто- рому подходу при определении, сколько тестовых случаев необходимо для тести- рования каждого случая использования, а затем обратимся к первому подходу с тем, чтобы сориентировать отбор тестовых случаев на достижения максимальной эффективности выявления дефектов.
Глава 9. Тастированиа систам Профиль использования Традиционный подход к тестированию системы предусматривает построение функционального разреза. Этот разрез представляет собой листинг относительной частоты использования в системе каждой функции конечного пользователя. На- пример, в игре «Кирпичики» играющий пользуется мышью, перемещая ее со сто- роны в сторону, и это наиболее часто используемая операция. В то же время вы- зов справки или паузы путем нажатия кнопки мыши могут служить примерами крайне редко используемых операций. Этот же подход применяется при вычислении показателей надежности различ- ных фрагментов программного обеспечения. Надежность есть мера продолжитель- ности безотказного функционирования программного обеспечения в конкретной операционной среде. Функциональный разрез представляет собой один из спосо- бов описания операционной среды. В то же время, трудно получить описание функционального разреза, если система не проработала после установки в течение некоторого времени. В главе 4 мы определили профиль использования (use profile) как точную оцен- ку функционального разреза. В профиле использования употребляется приоритет операции, но не частота использования. Имеется возможность вычислить приори- тет случая использования с большей точностью, нежели подсчет частоты исполь- зования конкретных операций, которые могут быть задействованы несколькими различными категориями пользователей. Можно вычислить приоритет случая ис- пользования, учитывая оценку его частоты использования и критичности для каждого конкретного действующего субъекта, объединив их в единый показатель для конкретных случаев использования. Построение такого профиля использова- ния производится на базе диаграмм действующего субъекта и диаграмм случаев использования. Классификация ODC Классификация ODC (Orthogonal Defect Classification - ортогональная класси- фикация дефектов) — это метод, разработанный корпорацией IBM с целью сбора информации о типах неисправностей, которые имеют место в разрабатываемых программных системах. Этот метод полезен при сборе и анализе тестовой инфор- мации с тем, чтобы направить усилия совершенствования процесса разработки в нужном направлении. Однако наши намерения заключаются в том, чтобы вос- пользоваться стандартной классификацией, разработанной авторами ODC, в каче- стве основы для отбора тестовых случаев (см. главу 4, раздел «Ортогональная классификация дефектов как селектор тестовых случаев»). На рис. 9.3 представлен список шести категорий причин отказов, распознавае- мых методом ODC. Мы хотим быть уверенными в том, что нам удалось построить тестовые случаи, которые позволяют задействовать каждый из этих триггеров де- фектов, Некоторые из категорий, такие как Пуск или Нормальный режим, вряд ли удастся избежать. В то же время категория Исключение напоминает нам, что каж- дая исключительная ситуация на системном уровне должна быть покрыта тесто-
Глава 9. Тастированиа систем выми случаями аналогично тому, как мы охватывали покрытием каждую исклю- чительную ситуацию на уровне компонентов. Слово Восстановление в названии этой категории также напоминает, что ожидаемые результаты захвата исключи- тельной ситуации должны быть четко описаны. Однако не всегда возможно про- должать выполнение операций в результате возникновения некоторых исключи- тельных ситуаций, например, таких как “File not found” (файл не найден). Каждая хорошо спроектированная программа должна быть способной справиться с такой ситуацией. Рабочий объвм/повышвнная нагрузка Нормальный режим Восстановление/исключение Пуск/повторный пуск Конфигурация аппаратных средств Конфигурация программных средств Pl/ICyHOK 9.3. Триггеры отказов на системном уровне в классификации ODC Категории Аппаратные средства и Конфигурация программного обеспечения менее очевидные, однако не менее важные области, подлежащие тестированию. Напри- мер, в случае персональных компьютеров выделение памяти для размещения про- граммного обеспечения может стать источником трудно решаемых проблем, если механизм виртуальной памяти может либо вообще отсутствовать, либо, по мень- шей мере, оказаться недостаточным. У нас были клиенты, которые очень горди- лись тем, что в распоряжение каждого из их разработчиков была предоставлена современная среда разработки. К сожалению, у некоторых из их заказчиков по- добной среды не было, в силу чего нормальное выполнение программы оказалось невозможным, поскольку разработчики никогда не сталкивались с ситуациями, когда программе не хватало памяти. Программа не могла распознать и обработать исключительную ситуацию, связанныую с нехваткой памяти. План тестирования системы должен предусматривать применение некоторого диапазона машин, оснащенных различными аппаратными средствами. В разделе «Конфигурация системы» приводится пример взаимодействий, возникающего между программным обеспечением и драйверами аппаратных средств. Подобным образом конфигурации программного обеспечения также могут служить причиной возникновения различных проблем. Многие программы могут оказаться сбитыми с толку порядком, в котором библиотеки расположились в пути поиска. Посколь- ку это не есть дефект самой программы, следовательно, это дефект установочного процесса или документации, сопровождающей программу.
Глааа 9. Тастированиа систам КОНФИГУРАЦИЯ СИСТЕМЫ Мы разработали собственную версию игры «крестики-нолики» с использованием брокера ORB технологии CORBA, которая поставляется в рамках комплакта JDK (Java Development Kit - набор инструментальных средств разработки для Java]. Служба имен, которая поставляатся с комплектом JDK, выполняется как отдальный процесс, в котором регистрируются серверы и запросы клиентов на определение местонахож- дения серверов. Мы создали версию этой игры и подвергли ее тестированию. Игра была установлана на небольшом портативном компьютере, в состав аппаратных средств которого входила комбинация модема и сетевой карты. Игру удалось запу- стить в работу, но оказалось, что она не воспринимает ваод. Соответствующий процесс работал, не прекращаясь, а мы занялись чам-то другим. Когда мы о най вспомнили, игра находилась в состоянии ожидания, когда игрок выберет нужный квадрат. Затем, когда квадрат был выбран, игра снова остановилась. После того как портативный компьютер был подключен к сати и игра возобновилвсь, онв начала выполняться с приемлемой скоростью. Последующие тасты показали, что объект службы имен менял сеое поведение с каждым изменением состояния модема и сетевой карты, при этом его поведение отличалось от его поаедания на другой машина, на которой сетевая карта вообще отсутствовала. Конфигурация системы содержит множество элементов. Часто некоторые из них на первый взгляд не имеют никакого отношения к процессу тестирования, однако анвлиз возникающих впоследствии дефактов показывает, что причиной их возникновения явились взаимодействия именно этих элементов. На работу системы могут повлиять различные версии операционных систем, в том числа и системы иностранного про- исхождения, отличающихся от отечественных версий, шрифты и языковая метрика и даже переменные окружения. Для уменьшения количества тестируемых комбинаций факторов можно воспользоваться схемами тастирования с использованием ортого- нальной матрицы. Случаи использования как источники тестовых случаев Чтобы иметь возможность выполнять тестирование на соответствие требовани- ям, мы должны построить тестовые случаи на базе случаев использования, кото- рые описывают требования. Как уже отмечалось ранее, необходимо определить, сколько нужно использовать тестовых случаев из каждого случая использования, после чего построить сами эти тестовые случаи. И хотя эти вопросы рассматрива- лись в главе 4, здесь будет уделено внимание ряду дополнительных деталей. Построение профилей использования Построение профилей использования начинается с действующих субъектов на диаграмме случаев использования. Профиль действующих субъектов, представ- ленный на рис. 9.4, описывает действующего игрока игры «Кирпичики». Там, где имеется один действующий субъект, этот профиль должен соответствовать полю частоты использования в случаях использования. Но интерес представляют и пользу приносят случаи, в которых участие принимают несколько действующих субъектов. Очень редко все эти действующие субъекты используют систему одним и тем же способом. Поле частоты случая использования представляет собой ком- позицию значений частоты использования отдельных профилей действующих субъектов.
Глава 9. Твстированиа систем Имя? Игрок Аннотация: Отсутствует Описание: Игрок управляет игрой Уроаань подготоаки игрока: Средний Профиль использования действующего субъекта Назааниа случая использоаания Частота исхода Выигрыш Средняя Проигрыш Средняя Пврвмещвнив шайбы Высокая Пауза в игра Низкая Рисунок 9.4. Профиль действующего субъекта На рис. 9.5 частота Случая использования №1 может быть некоторым средним значением частот использования, представленных в профилях действующих субъектов А и В. Возможно, вы захотите придать больший вес частоте действую- щего субъекта А, нежели частоте действующего субъекта В. Поле частот использо- вания для каждого случая использования построено на базе этих профилей дей- РисунОК 9.5. Отображение случаев использования на действующих субъектов
Главв 9. Тестирование систем Этот подход весьма полезен для систем, которые ни разу не устанавливались. Он дает более точную оценку того, как действующий субъект будет использовать систему, по сравнению с простым угадыванием того, каким окажется совокупный результат отдельных случаев использования. После развертывания системы часто- ты случаев использования могут быть скорректированы на базе фактических дан- ных и задействованы при регрессионном тестировании. Использование сценариев для построения тестовых случаев Случай использования обычно содержит многочисленные сценарии, которые могут быть преобразованы в тестовые случаи. Раздел случаев использования, обо- значенный как Система ответила действием — обычное дело; именно такие разде- лы являются основными источниками тестовых случаев. Имеют место атрибуты данных, которые требуют такого же анализа, что и любые параметры методов. В параграфе «Граничные условия» дано описание одного из типов анализа, который можно задействовать в данной ситуации. Мы приводим здесь лишь несколько строк, чтобы дать более подробное представление о табличном подходе. Процесс выделения отдельных значений из переменных, представленных в случае использования, предусматривает выполнение четырех действий: 1. Идентификация всех значений, которые доставляются действующими субъектами, содержащимися в модели случая использования. 2. Выделение классов эквивалентности значений каждого типа входных дан- ных. 3. Построение таблиц, в которые помещен список комбинаций значений из различных классов эквивалентности. 4. Построение тестовых случаев, в которых сочетаются одна перестановка зна- чений с необходимыми внешними ограничениями. Поскольку рассматриваемый на протяжении всей книги пример не может обес- печить достаточного для наших целей количества вводов, рассмотрим несколько переменных из примера системы управления персоналом, впервые упомянутой в главе 3. В случаях использования этой системы употребляются три переменных. Каждый служащий представлен в системе именем и переменными, показывающи- ми, является ли он новым сотрудником фирмы или уже работает в ней в течение определенного времени, и уровнем полномочий, санкционированных системой безовасности. На рис. 9.6 показаны классы эквивалентности этих трех перемен- ных. В таблице на рис. 9.7 каждой из этих переменных отводится отдельный стол- бец. В эти столбцы помещены значения из различных классов эквивалентности рассматриваемых переменных. Каждая строка таблицы представляет собой описа- ние конкретного теста. Раздел Альтернативные действия случая использования является одним из наи- более часто используемых разделов. Остальные случаи обычно используются реже.
Глааа 9. Тестироеание систем Имя переменной Тип объекта Классы эквивалентности Имя Строка 1. Имя, которое выходит за пределы максимальной длины строки. 2. Имя, которое в точности соответствует максимальной длине строки. 3. Полное имя с остающимся пустым пространством. 4. Пустое имя. Служащий Штатная единица 1. Специально созданная штатная единица 2. Ранее существовавшая штатная единица Авторизация Код безопасности 1. Санкционирован только локальный доступ. 2. Санкционирован доступ в масштабах всей системы. Рисунок 9.9. Спецификация входных значений для системы управления кадрами Наряду с этим, раздел Расширения представляет случаи успешного использова- ния, в рамках которых система достигает целей, указанных в главном сценарии, и при этом выполняет дополнительные поведения. Этим сценариям уделяется при тестировании меньшее внимание в расширенном случае использования, нежели в исходном случае использования. В разделе Расширения представлены сценарии, в которых виды использования, описанные в обычных случаях использования, остаются недостижимыми. Эти случаи использования покрываются в рамках полного тестирования системы; в то же время они не покрываются в регрессионных тестовых наборах, если речь не идет о критически важных проблемах для жизни и безопасности людей. Количество тестовых случаев, которые необходимо построить, зависит от зна- чения атрибута частоты использования каждого случая использования. Один из способов оценки соответствующего числа тестовых случаев заключается в том, что вычисляется произведение количества различных входов и количества классов эквивалентности каждого типа ввода с целью получения максимального количе- ства перестановок: Имя Штатная единица Авторизация Полное имя с остающимся пустым пространством Ранее существовавшая штатная единица Санкционирован только локальный доступ Полное имя с остающимся пустым пространством Новая штатная единица Санкционирован только локальный доступ Рисунок 9.7. Перестановки входных данных для системы управления персоналом
Глава 9. Тестирование систем с., о2 о3 ... оп, где Е, — количество классов эквивалентности для /-того ввода. Это выражение дает максимальное количество тестовых случаев, которые могут быть созданы при использовании данного подхода. На практике количество тестовых случаев может быть ограничено, если прини- мать во внимание важность того или иного случая использования или объем дос- тупных системных ресурсов. Можно предпринять пробную попытку либо вос- пользоваться подходящими статистическими данными для определения, какой объем ресурсов необходим для выполнения типичного случая использования. Если известно количество случаев использования, то можно получить оценку трудозатрат, необходимых для реализации проекта в полном объеме. Раздел ожидаемых результатов в тестовом случае При тестировании сложных систем одна из наиболее трудных задач заключает- ся в том, чтобы определить результаты, ожидаемые от прогона конкретного теста. Телекоммуникационные системы, программное обеспечение управления косми- ческим кораблем, информационные системы многонациональных корпораций — это случаи систем, для которых построение тестовых данных и тестовых результа- тов обходится особенно дорого. Для каждого тестового случая, необходимость ко- торого была обоснована сценариями, рассмотренными в предшествующем разде- ле, должно быть приложено описание результатов, которые тестировщики ожидают получить во время прогона соответствующих тестов. В процессе разработки последнего проекта мы приступили к построению сце- нариев и стали на их базе строить тестовые случаи. Только лишь после того, как мы обрели способность оценить результаты тестирования, стало очевидным, что ни коллектив разработчиков, ни его руководство не имели понятия, каким долж- но быть точное поведение системы в условиях каждого из этих сценариев! Некоторые методы могут оказаться полезными для снижения затрат усилий на разработку ожидаемых результатов. Первый из них предусматривает построение результатов в инкрементальном режиме. По условиям этого подхода тестовые слу- чаи создаются с целью покрытия некоторого поднабора случаев использования системы, возможно, только некоторых процедур ввода данных. В последующих случаях покрытия расширяются с целью проверки использования системы в пол- ном объеме. С расширением тестовых случаев, тестовые результаты также расши- ряются. Все это можно расширить за счет включения итеративной разработки тестовых случаев. Работу в этом режиме мы начинаем с написания небольших тестовых случаев, после чего постепенно увеличиваем его размеры и сложность тестовых случаев и продолжаем этот процесс до тех пор, пока тесты станут реалистичными с позиций промышленной среды. В системе управления базами данных можно начать с базы данных, содержащей пятьдесят записей, и постепенно увеличивать это число до нескольких тысяч. Результаты, ожидаемые на каждом новом уровне,
Глава 9. Тестирование систем должны включать любые взаимодействия, которые возникают в силу появления новых случаев использования. Например, присутствие одной записи может пре- пятствовать выбору другой, которая была выбрана в процессе выполнения преды- дущего теста. Второй подход заключается в разработке тестовых случаев большого цикла (grand tour test cases), в котором каждый тестовый случай генерирует данные, кото- рые служат входом для следующего тестового случая. По условиям такого подхода каждый тест переносит тестовые данные через весь жизненный цикл. Полученное при этом состояние базы данных используется в качестве входного для следующе- го теста. Этот метод особенно эффективен при тестировании жизненного цикла после того, как тестирование нижнего уровня позволило выявить большую часть дефектов, вызывающих отказы. Если выстроить тестовые случаи в соответствую- щую последовательность, то после успешного выполнения тестового случая 1 ус- танавливается такое состояние базы данных, которое ожидается как входное для тестового случая 2. Очевидная проблема в условиях очерченного подхода заклю- чается в том, что неудачное выполнение тестового случая 1 оставляет базу данных в состоянии, которого мы не ожидали, в результате чего мы не можем выполнять прогон тестового случая 2 или, по меньшей мере, привести базу данных в рабочее состояние, прежде чем прогнать тестовый случай 2. Игра «Кирпичики» В главе 4 мы начали тестирование игры «Кирпичики» на системном уровне с проведения целенаправленной проверки. Чтобы подготовиться к ней, мы назна- чили случаям использования приоритеты, взяв за основу частоту использования и меру критичности случая использования. На рис. 4.23 представлены результаты такого анализа. К тому моменту, когда первый комплектующий модуль будет го- тов для тестирования с использованием компьютерных программ, потребуется перепроверить результаты анализа с целью определения, имеет ли смысл измене- ние рейтинга отдельных случаев использования. Мы продолжим пользоваться рейтингами, вычисленными в главе 4. При наличии такого простого набора вводов анализа требуют только несколько входных параметров. На рис. 9.8 показаны результаты анализа и некоторые до- полнительные факторы, такие как, например, версия операционной системы. На рис. 9.9 приводится поднабор перестановок этих переменных. В каждой строке представлен тестовый сценарий. Мы будем использовать этот набор строк для одной операционной среды Java, такой как Appletviewer, в качестве тестового набора для версии C++. В первой строке, когда не потеряно ни одной шайбы, ожидаемый конечный результат показывает, что игра выиграна. Однако в игре «Кирпичики», если нет перемещений лопатки, все шайбы должны быть потеряны (в конечном итоге). Та- кую ситуацию назовем недостижимым тестовым случаем (infeasible test case). Дру- гими словами, такой тестовый случай невозможен, хотя он и генерируется неко- торым алгоритмом. Существует еще один недостижимый тестовый случай, по условиям которого теряются только три шайбы и нет никакого движения.
Глава 9. Тестирование систем Имя переменной Тип объекта Классы эквивалентности Количество потерянных шайб Целое ни одной, все, некоторые Движения лопатки События типа перемещения мыши мышь неподвижна, медленное перемещение, позволяющее успеть к моменту соударения, быстрое перемещение с последующим ожиданием шайбы Участок лопатки, о которую ударяет шайба Перечислимый тип левая треть лопатки, правая треть лопатки и центр Операционная среда (только версия Java) Перечислимый тип Appletviewer, Netscape Navigator, Internet Explorer Операционная система Перечислимый тип Windows 95/98, Windows NT, Windows 2000, Unix, Linux РисуНОК 9.8. Спецификации входных значений для игры «Кирпичики» Количество потерянных шайб Перемещение шайбы Участок лопатки, о который ударяет шайба Операционная среда 0 шайба неподвижна левый Appletviewer 0 шайба неподвижна центральный Appletviewer 0 шайба неподвижна правый Appletviewer 0 быстрое перемещение левый Appletviewer 0 медленное перемещение левый Appletviewer 3 шайба неподвижна правый Appletviewer 3 быстрое перемещение центральный Appletviewer 3 медленное перемещение центральный Appletviewer все шайба неподвижна правый Appletviewer все быстрое перемещение правый Appletviewer все медленное перемещение правый Appletviewer 3 шайба неподвижна левый Netscape 3 быстрое перемещение левый Netscape 3 медленное перемещение левый Netscape Рисунок 9.9. Перестановки входных данных для игры «Кирпичики» '2 ,71
Глааа 9. Твстированив систем Имеется еще одна переменная, которая оказыва- L, (З4) ет влияние на ход игры и на выбор той части ло- 1 2 3 4 патки, о которую ударяет шайба. В зависимости от 1 1 1 1 1 того, в какую часть лопатки попадает шайба — в 2 1 2 2 2 левую, правую или в центр — производятся раз- 3 1 3 3 3 личные вычисления. Однако, анализ последова- тельности соударений лопатки с шайбой порождает 4 2 1 2 3 большое количество различных возможностей. Тес- 5 2 2 3 1 тировщик может провести содержательный анализ 6 2 3 1 2 этого оцениваемого фактора в ситуации, когда не- 7 3 1 3 2 сколько соударений происходят в каждой из ука- В 3 2 1 3 занных выше частях лопатки. Мы включим его в 9 3 3 2 1 Рисунок 9.10. Матрица для ОА TS-тестирования рассматриваемую таблицу в силу того, что собира- емся воспользоваться системой OATS (Orthogonal Array Testing System — система тестирования с ис- пользованием ортогональной матрицы), дабы уменьшить объем тестов, необходи- мых для решения этой задачи. В нашей таблице перестановок, показанной на рис. 9.9, присутствуют четыре коэффициента.' Каждый коэффициент в рассматриваемом случае представляется тремя уровнями. На рис. 9.10 показана ортогональная матрица, которая соответ- ствует поставленной задаче. На рис. 9.11 присутствует та же матрица, в которой вместо целых чисел проставлены значения, характерные для решаемой задачи. В приведенных девяти случаях предполагается, что коэффициенты не зависят друг от друга. Тестовый случай Количество потерянных шайб Перемещение лопатки Участок лопатки, о который ударяет шайба Платформа 1 все нет движения левый Appletviewer 2 0 быстрое центральный Netscape 3 0 медленное правый IE 4 все нет движения центральный IE 5 3 быстрое правый Appletviewer 6 3 медленное левый Netscape 7 все нет движения правый Netscape 8 все быстрое левый IE 9 все медленное центральный Appletviewer Рисунок 9.11. Отображение классов эквивалентности на матрицу 1 См. обсуждение свойств матрицы OATS в главе 6, раздел «Тестирование с использованием ортого- нальной матрицы».
Глввв 9. Тестирование систем Случаи 1 и 4 являются недостижимыми, поскольку если нет движения, все шайбы будут утеряны. Между двумя этими коэффициентами существует зависи- мость, однако, это отнюдь не абсолютная корреляция. Заменяя число на значение все, мы все еще можем воспользоваться этими тестовыми случаями для покрытия некоторой части рассматриваемых перестановок. Тестирование инкрементальных проектов Все меньше и меньше разработок программного обеспечения выполняют пост- роение систем в полном объеме из сырых продуктов на языке программирования и всеобъемлющих наборов требований. Многие занимаются переделкой моделей проектов, в рамках которой некоторые фрагменты существующих систем заменя- ются обновленными, зачастую расширенными, функциональными средствами. Присутствующее на рынке программное обеспечение (COTS, commercial-off-the- shelf) используется в тех случаях, когда предоставляется возможность применять в программном продукте какую-то часть функциональных средств той или иной существующей системы независимо от того, является ли эта система абсолютно новым или модернизированным программным продуктом. В качестве одного из примеров мы можем назвать проект, работа над которым производилась во время написания данной книги. Цель этого проекта была связа- на с распознаванием речи. Еще одним примером может послужить использование компонента, обеспечивающего применение крупноформатных электронных таб- лиц, состоящего из нескольких классов, а вот один из последних примеров связан с задействованием коммерческого сервера приложений в качестве среднего уровня в рамках трехуровневой архитектуры. Итак, может и должно ли все это повлиять на используемые нами тестовые случаи и на требуемые уровни покрытия? Любой программный продукт, который приобретается у независимого произ- водителя программного обеспечения в качестве вспомогательного средства для построения другого программного продукта, должен быть подвергнут приемочным испытаниям, проводимым приобретающей стороной (то бишь, покупателем). Компоненты, которые приобретаются у сторонних производителей, подобные, скажем, библиотекам, и компоненты, которые поставляются в составе программ- ных продуктов, такие как среды разработки, обязательно должны подвергаться те- стированию. Эти вопросы будут обсуждаться в главе 10. Что касается проектов, целью которых являются разработки не в полном объе- ме, как, например, обновление существующего программного обеспечения, основ- ные фазы тестирования сохраняются, однако их значение претерпевает измене- ние. В течение ряда лет мы принимали участие в нескольких таких проектах. Полученный нами опыт позволяет утверждать, что для каждого подобного проекта должен быть составлен план тестирования, в котором должен четко указываться объем тестирования, проводимого на каждой фазе. Эти объемы определяются ка- чеством обновляемого программного продукта, глубиной этого обновления и ка- чеством, которое требует от него предметная область. 12*
Глеве 9. Твстировенив систем Особенности тестирования существующего программного обеспечения после его обновления Если речь идет о тестировании существующего (или т.н. унаследованного) программного обеспечения, в процессе реконструкции которого некоторая часть существующих функциональных средств была заменена новыми программными кодами, то особое внимание следует уделить двум видам тестирования. Прежде всего, потребуется провести целенаправленную проверку с целью обеспечения приемлемого качества интерфейсов, которые создаются для изоляции нового фрагмента системы от остального программного кода, не подвергавшегося модер- низации. Назначение этих интерфейсов состоит в том, чтобы, по меньшей мере, служить реализацией функциональных средств, которые ранее были представлены программами, которые впоследствии были заменены. Эти интерфейсы с суще- ствующим программным обеспечением можно считать полными, если они могли реализовать все поведенческие аспекты, необходимые для того, чтобы расширен- ное программное обеспечение могло реализовать собственное поведение. Такие интерфейсы непротиворечивы, если доступ к поведению, реализуемому унаследо- ванными программами, производится через единственную точку входа в интер- фейсах. Интерфейсы корректны, если они точно передают поведение унаследо- ванного программного кода. Другой вид тестирования, которому следует уделить повышенное внимание, сводится к проверке взаимодействия и функционирования компонентов. При этом основное внимание уделяется точкам, в которых новый программный код фактически взаимодействует с существующим программным кодом. В зависимос- ти от того, каким образом были реализованы изменения, есть шанс столкнуться с проблемами совместимости даже на уровне примитивных типов данных, если в интерфейсах использованы различные языки программирования. Протоколы между унаследованным программным обеспечением и расширен- ным программным кодом должны быть идентифицированы, задокументированы и протестированы. При этом имеется в виду двухсторонний обмен обычными сооб- щениями и исключительные ситуации, которые генерируются одним из них и перехватываются другим. Отвлекаясь на минутку от затронутой темы, отметим, что широко распространенный шаблон проектирования Facade формализует про- токолы в объекте. Мы принимаем такой подход к использованию, так что если в дальнейшем какие-либо другие части унаследованного программного обеспечения преобразуются в объекты, то протокольные объекты будут отброшены, а вместо них будет построен новый набор на базе нового интерфейса между существую- щим программным обеспечением и программным кодом объекта. На обоих уровнях возникают приблизительно одни и те же проблемы: 1. Совместимость диапазонов значений, используемых в качестве параметров на стыке с существующим программным обеспечением в обоих направлениях 2. Совместимость нового и существующего конечных автоматов 3. Полнота интерфейсов 4. Корректность новых вычислений.
Глааа 9. Тестироаание систем План системного тестирования должен выделить интерфейсы, создаваемые в рам- ках проекта, и должен включать план всестороннего тестирования этих интерфейсов. Тестирование множественных представлений В настоящее время лишь немногие системы реализуются на каком-то одном языке программирования с единственным представлением данных. Скорее всего, такие системы представляют собой некоторую комбинацию компилируемых сер- веров, вполне возможно, написанных на C++, и интерпретируемых клиентов, ре- ализованных, возможно, на языке Java, экранное представление которых вычис- ляется динамически, в то время как их последующие представления извлекаются из базы данных. Данные заказчика структурированы в соответствии с требовани- ями реляционной архитектуры, и они должны быть повторно упакованы с таким расчетом, чтобы стала возможной объектно-ориентированная архитектура логики вычислений. В перспективе тестирования особый интерес представляют следующие свой- ства таких систем: 1. Взаимодействия двух моделей данных 2. Взаимодействия фрагментов, написанных в различных языках 3. Взаимодействия статических и динамических фрагментов программ. Первый шаг при тестировании этих свойств предусматривает процесс всесто- роннего тестирования программных модулей. Каждый фрагмент системы и, преж- де всего, ее динамические компоненты, должны быть испытаны на их родном язы- ке. Второй шаг состоит в тестировании взаимодействий на стыке различных языков программирования и представлений. В ходе этого вида тестирования ос- новное внимание должно уделяться переходам от одного типа данных параметров к другому, которые имеют место, когда программы написаны в различных языках.2 Мы реализовали игру крестики-нолики, задаваясь различными условиями ис- пользования этой игры. Мы включили в нее стратегический компонент, который определяет следующий шаг, который может выбрать оператор этой компьютерной игры. Этот стратегический модуль реализован на языке C++, а при помощи ин- терфейса между Java и C++ результаты возвращаются в главную программу на языке Java. Первоначальное тестирование позволило выявить некоторые дефекты, такие как проблемы выравнивание по границам слов, причиной которых стал разный подход к преобразованию типов данных в различных языках программирования. СОВЕТ Проведите тестировение интерфейсов мвжду двумя языкеми программировения с тем, чтобы проверить, что осуществляется передеча полного объекте. Во-первых, убедитесь в возможности устеновки каждого соединения в любом направлении. Во- вторых, подтвердите прееильность результатов тестировения путем всесторонней проверки преобразованных объектов с тем, чтобы убедиться, что ничто не пропу- щено и не искажено. Измерьте покрытие за счет охвета всех примитивных типов каждого языке программирования. В данном случае тестирование главным обрезом ориентировано на реализацию.
Главе 9. Тестирование систем Один из примеров тестирования динамических аспектов системы предполагает отображение одного типа представления в другой. Для систем на базе языка XML первый шаг заключается в применении программы синтаксического анализа, что- бы проверить, что выбрана корректная форма самого языка XML. Затем та часть программной логики, которая манипулирует описанием на XML, тестируется в предположении, что само описание корректно. Это подтверждение правильности входных данных при помощи программы синтаксического анализа XML с концеп- туальной точки зрения ничем не отличается от использования средств прямых запросов языка SQL (Structured Query Language — язык структурированных запро- сов) для проверки правильности результатов тестирования в системах, построен- ных на основе реляционной базы данных или программы просмотра объектов объектно-ориентированной базы данных. То есть, сначала нужно убедиться в пра- вильности вводов и только после этого переходить к проверке правильности вы- водов. Другим примером смешивания системных типов может служить достаточно распространенный подход использования реляционной базы данных в качестве серверной СУБД системы, у которой имеется объектно-ориентированная интер- фейсная часть. Интерфейс между двумя этими моделями может стать источником некорректного отображения по мере того, как данные, поступающие с реляцион- ной стороны, группируются в объекты. Тестовый набор должен включать средства проверки правильности вновь создаваемых объектов. СОВЕТ Выполните вса интерфейсы между двумя првдстввлвниями данных путам выяалания существующих тестовых случаев, которые всв вместе создают представление квждого клвссв зквиввлвнтности. Проверьте (1], что отобрвжвнив одного представ- ления на другое выполняется корректно, (2] что всв можно отобразить в любом направлении (полнота] и (3) что каждый элемент в любой момент времени отобра- жается одинаково (непротиворечивость). Что необходимо подвергнуть тестированию Тестирование на соответствие функциональным требованиям Проверка функциональных требований является традиционным «системным тестированием», этот вид деятельности мы уже рассматривали выше. Она основа- на на построении тестовых случаев на базе случаев использования. Проверка качественных системных атрибутов Проектные документы и рекламные проспекты довольно часто провозглашают претензии на качество продукта, которые на поверку оказываются несостоятель- ными. Добротная организация разработок программного обеспечения предусмат- ривает методы подтверждения всех системных «требований», включая и претен- зии на придание программному продукту особых качеств. В этом разделе мы рассмотрим особенности тестирования системы с целью подтверждения наличия у системы качеств, на которые она претендует.
Глава 9. Тастирование систам ---------------------------------------------------------------------------- КРАТКОЕ ОПИСАНИЕ МЕТОДА - ПРОВЕРКА УТВЕРЖДЕНИИ О НАЛИЧИИ СООТВЕТСТВУЮЩЕГО КАЧЕСТВА 1. Первввдите каждую пратанзию на наличие того или иного качества в атрибуты, допускающие измерение, которые изменяются в соответствии с изменением качественного атрибута или служат определением качественного атрибута. 2. Постройта тестовые случаи, которые способны обнаружить наличие или отсут- ствие этих измеряемых атрибутов. 3. Выполните указанные тастоаыа случаи и проанализируйте результаты их прогона. 4. Обобщите эти результаты с целью определения, оправданы ли претензии на обладание продуктом конкретным качеством. Существуют два вида претензий, с которыми организация, разрабатывавшая программный продукт, может выдвигать в отношении этого продукта. Первый вид претензий представляет интерес только для организаций, занимающихся разра- боткой программных продуктов. Например, утверждение, что «программный код допускает многократное использование». Второй тип претензий представляет ин- терес для пользователей системы. Например, утверждение о том, что система яв- ляется более полной, нежели другие системы подобного класса, предлагаемые на текущий момент на рынке программных продуктов. Вполне понятно, что не все эти претензии могут подвергаться проверке через тестирование. Большая часть такого рода заявлений относительно качества лучше всего может быть проверена путем изучения проекта, чем путем выполнения программного кода. Метод целенаправленной проверки, рассмотренный в главе 4, представляет собой метод исследования такого рода атрибутов системного уровня. Одним из типов претензий, обоснованность которых может быть подтверждена за счет выполнения программного кода, являются заявления, касающиеся харак- теристик производительности. Разработчики программных компонентов могут ут- верждать, что характеристики производительности их баз данных остаются при- емлемыми в условиях стремительного увеличения числа транзакций. Чтобы подтвердить эту претензию, системные тестировщики выполняют следующее ис- пытание под нагрузкой: 1. Перевести термины «приемлемые характеристики производительности» и «стремительное увеличение» в числовые показатели. «Приемлемые характе- ристики производительности» переводятся в число транзакций за секунду при заданном размере записи 1024 байт. «Стремительное увеличение» пере- водится в числовые показатели путем определения кривой увеличения как «квадратичного увеличения в течение 10 минут». 2. Тестировщики должны создать новые данные либо выбрать и подготовить статистические данные для их последующего использования в процессе те- стирования. Потребуется разработать тестовый фрейм, способный достав- лять максимальное число транзакций в секунду. Такой фрейм содержит из- мерительные средства, облегчающие сбор данных, характеризующих время обслуживания.
Главе 9. Тестирование систем 3. Выполнить прогон тестов. Провести сбор тестовых результатов и замеры временных характеристик. 4. Персонал, проводящий тестирование, дает заключение о том, насколько обоснованным является соответствующее притязание на качество. Разумеется, этот вид тестирования не будет применяться в каждом проекте, тем не менее, он является важным аспектом аттестации программы. Тестирование механизма развертывания системы Тестирование механизма развертывания вашего приложения — это не обяза- тельно новая процедура, однако она приобретает дополнительную значимость для конфигурируемых систем и для тех систем, для которых необходимо динамичес- кое взаимодействие с окружением. Тестирование механизма развертывания про- водится с целью убедиться, что пакет, в который заключена система, обеспечивает необходимые условия для установки и настройки программного продукта, и что он доставляет программный продукт в рабочем состоянии. Наиболее дорогостоя- щей частью этого процесса является выбор и ввод в действие опций. Самый первый тестовый случай производит полную установку программного продукта. Этот тест сам по себе может показаться самодостаточным, тем не менее, между опциями обычно существуют взаимодействия. Если даже какие-либо вари- анты не установлены, то библиотеки или драйверы могут понадобиться для дру- гих вариантов, но они могут не быть скопированными в инсталляционные биб- лиотеки. Для регистрации зависимостей между опциями можно использовать матрицу взаимодействий (см. главу 6). Затем могут быть разработаны тестовые случаи, которые будут предпринимать попытки установить только какую-то одну из двух опций. В том случае, когда обе опции не являются независимыми, ожида- емым результатом должно быть нормальное функционирование системы. Может быть получено множество возможных комбинаций, особенно если рассматривают- ся различные платформы, на которых будет устанавливается система. Это стан- дартная ситуация для применения системы OATS, но в данном случае мы не бу- дем включать сюда еще один, на этот раз более подробный пример, поскольку мы уже включили два примера. Коэффициентами являются устанавливаемые опции, уровни которых либо установлены либо не установлены. В случае более сложных опций такими уровнями могут быть традиционные; типовая установка, заказная установка и полная установка. Вывод о корректности функционирования системы может быть сделан после проведения регрессионного тестирования. Набор регрессионных тестов может быть усечен за счет удаления любых тестов, которые используют неустановленные опции.
Глава 9. Тестирование систем КРАТКОЕ ОПИСАНИЕ МЕТОДА - ТЕСТИРОВАНИЕ МЕХАНИЗМА РАЗВЕРТЫВАНИЯ 1. Выделить категории платформ, на которых будет разворачиваться система. 2. Определить по маньшей мерв одну систему каждого типа, которая оснащвна типичной операционной средой, но в то же время рассматриваемая систама на ней не установлена. 3. Установить рассматриваемую систему, используя для этой цели механизм раз- вертывания. 4. Выполнить прогон рвграссионного набора системных тестов и оценить получен- ные результаты. Тестирование после развертывания системы Естественное расширение тестирования механизма развертывания системы заключается в добавлении в тестируемый программный продукт функциональных средств самопроверки. Тестирование механизма развертывания предусматривает проверку системы в лабораторных условиях, в то время как испытание с помощью функциональных средств самопроверки проверяет фактическое развертывание си- стемы в операционной среде заказчика. Отдельные компоненты самопроверки в архитектуре системы разрабатываются с расчетом на вызов функциональных средств конечного пользователя программного продукта и оценку результатов те- стирования. Это обеспечивает пользователю возможность выполнения набора тес- тов всякий раз, когда возникают сомнения относительно «санитарного состояния» приложения. Тестовый набор, предназначенный для такого рода проверок, есть поднабор регрессионных тестового набора системы. Тестовый набор сосредоточивает свои усилия на тех частях системы, которые могут быть затронуты изменениями, про- исходящими в окружении системы. Мы будем считать, что система «изнашивает- ся» во времени по причине изменений, имеющих место в ее взаимодействии с окружением, примерно так же, как со временем изнашивается механическая сис- тема из-за трения между ее компонентами. По мере того, как устанавливаются все более новые версии стандартных драйверов и библиотек, несоответствия возрас- тают вместе с ростом вероятности возникновения отказов. Каждая новая версия DLL-библиотеки привносит возможность появления новых областей нестыковки стандартных интерфейсов или появления состояний гонок между этой библиоте- кой и приложением. Функциональные средства самотестирования должны обеспе- чить выполнение тестов, которые исследуют работу интерфейсов между этими программными продуктами. Тестирование взаимодействий окружения Разработка последнего проекта выполнялась на платформе Windows NT, осна- щенной оперативной памятью 256 Мб и жестким диском емкостью 20 Гб. Когда рассматриваемая система была подвергнута бета-тестированию, она не смогла ра-
Глава 9. Тестирование систем ботать на некотором диапазоне операционных систем. Она с треском провалилась под Windows 95, но и под управлением ряда других операционных системам, предложенных в нескольких конфигурациях, также отказалась работать. Суще- ствовала зависимость между размерами оперативной памяти и доступным про- странством памяти на жестком диске, обусловленная размером пространства сво- пинга, выделяемого операционной системой. Мы провели исследование этой проблемы, для чего был разработан набор тес- товых случаев, подобных показанным на рис. 9.12. Поскольку машины, на кото- рых проводилась разработка системы, обладали большей мощностью, чем боль- шинство машин, на которых рассматриваемая система скорее всего будет развернута, вопросы манипулирования памятью не были исследованы должным образом. Одна из проблем заключается в том, что каждое открытое окно увеличи- вает потребность в пространстве свопинга даже в тех случаях, когда хватало опе- ративной памяти для всего окна. Это задача не возникала до тех пор, пока мы случайно не выполнили программу в условиях, когда жесткий диск был почти полностью заполнен, т.е., когда распределение пространства памяти для свопинга было фактически невозможно. Такого рода отказ заставил нас построить специ- альный набор тестовых случаев, предназначенных для исследования отказов этого типа (см. далее раздел «Больше правды»). КРАТКОЕ ОПИСАНИЕ МЕТОДА - ОПРЕДЕЛЕНИЕ КОНТЕКСТА 1. Опишите область действия контекста, например, единственнея платформа или распределенная среда неоднородных машин. 2. Выделите атрибуты системы, которые оказывают влияние на функционирование системы, текие как объем оперативной памяти платформы или другие одновре- менно выполняемые приложения. 3. Выполните анализ каждого из этих атрибутов и выделите обычные классы эквивалентности. 4. Постройте комбинации значений атрибутов, которые обеспечивают хорошее покрытие рессметривеемого контексте. Тестовый случай № Доступность дисковой памяти Доступность оперативной памяти Ожидаемые результаты Фактические результаты 1 Высокий уровень Высокий уровень Нормальная работа Нормальная работа 2 Высокий уровень Низкий уровень Нормальная работа Нормальная работа с низким быстродействием 3 Низкий уровень Высокий уровень Нормальная работа Отказ 4 Низкий уровень Низкий уровень Отказ Отказ РисуНОК 9.12. Тестовые случаи для проверки взаимодействия оперативной и дисковой памяти
Главе 9. Тестирование систем БОЛЬШЕ ПРАВДЫ Итак, мы, мягко говоря, вводили вас в заблуждение, когда утверждали, что основной целью тестирования является обнаружение отказов тестируемой системы. Мы ближе подошли к истине, когдв добавили к этому, что тестирование предназначено также и для того, чтобы определить, удовлетворяет ли система предъявляемым к ней требованиям. Теперь ещв немного правды. Тестирование может дать некоторые сведения, которые будут полезны для восстановления системы после отказа. Тес- товые случаи, представленные на рис. 9.12, построены с таким расчетом, чтобы проводить систематические исслвдоввния истинных причин отказов. Возвращая эту таблицу проектировщикам, тестировщики тем самым ускоряют выполнение диагно- стического процесса. Проводите тестирование в различных ситуациях, которые может создавать пользователь — например, выполните рассматриваемое приложение параллельно с Microsoft Word, Lotus Notes или другими прикладными программами. Тестирование системы безопасности Тестирование влияния, оказываемого системой безопасности на приложение, для объектно-ориентированных систем не является каким-то специализирован- ным, однако для этого случая характерны некоторые особенности. Существуют три категории проблем, которые можно классифицировать как проблемы безопас- ности: 1. Способность приложения обеспечить доступ уполномоченным на это лицам и предотвратить доступ лиц, не обладающих должными полномочиями. 2. Способность программного кода осуществить доступ ко всем ресурсам, ко- торые ему нужны для успешного выполнения. 3. Способность приложения предотвратить несанкционированный доступ к другим системным ресурсам, не имеющих отношения к приложению. Мы не будем вдаваться в проблемы 1 и 3, которые рассматриваем как возмож- ные “прорехи” брандмауэра и как традиционное программное обеспечение под- держки учетных записей/паролей. СОВЕТ Попробуйте воспользоваться специальными клавишами как средством доступа к лазвйкем нв уровне системы, чтобы обойти средства защиты, такие как, например, защита с помощью паролей. Воспользуйтесь произвольным режимом тестирования, набирая различные комбинации клавиш CTRL, ESC, ALT и других клавиш в попытках выйти на уровень, на котором возможен доступ к данным. В частности, модульность исполняемых модулей и динамические аспекты про- грамм являются источником некоторых проблем безопасности. Выше мы кратко обсудили (см. раздел «Тестирование после развертывания системы») ситуации, когда приложение было развернуто, а файлы скопированы в несколько различных каталогов. Большая их часть будет размещена каталоге, построенным для самого
Глава 9. Тастированиа систем приложения; однако некоторые из них должны быть скопированы в специальные подкаталоги системных каталогов. Если эта процедура выполняется системным администратором, то эти файлы могут получить полномочия, отличные от тех, которыми пользуются обычные пользователи. Такое приложение может начать выполняться без каких-либо проблем, более того, оно может быть успешно ис- пользовано для решения конкретных задач. Неудачно может завершаться только некоторая часть операций, а если конкретнее, то неудачей могут закончиться только некоторые операции для некоторых пользователей. Уровень тестирования, который уместен в таких случаях, предусматривает прогон достаточного количе- ства тестовых случаев, чтобы был задействован, по меньшей мере, один ресурс из каждого каталога и один пользователь из каждого класса безопасности. В среде Java используется файл разрешений, который не зависит от системы безопасности операционной системы. Разрешения могут потребоваться для досту- па к любому числу системных ресурсов или ресурсов приложения. Опять-таки, неадекватные разрешения вообще никак не проявятся до тех пор, пока не будут подвергнуты тестированию явным образом. Типы тестирования Тестирование в предельных режимах Тестирование в предельных режимах (т.н. стресс-тестирование) означает заста- вить работать операционную систему в условиях, приближенных к исчерпанию всех ресурсов, необходимых для функционирования системы. Это может быть наполнение оперативной памяти объектами, заполнение жесткого диска записями либо заполнение внутренних структур данных. Один из наиболее популярных тестов заключается в быстром перемещении мыши со стороны в сторону. Это мо- жет привести к тому, что очередь событий типа перемещения мыши может ока- заться переполненной. Если обработке этой ситуации не уделено надлежащего внимания, может произойти аварийное завершение программы. Объектно-ориентированные системы обычно выводятся в предельный режим путем построения большого количества классов. Выбирайте те классы, которые с большой степенью вероятности порождают в нормальном режиме функциониро- вания системы большое число экземпляров. Используйте генераторы случайных чисел или другие устройства с целью варьирования значений параметров, ибо это хорошая возможность тестирования конструкторов на широком наборе классов. Объекты часто бывают значительно больше, чем вы себе представляете. Объект, который содержит ссылку на 30-секундный машинный видеоклип, содержит ссылку на объект (обычно занимающую 4 байта), однако общая память, необходи- мая для построения экземпляра этого объекта включает память, необходимую для хранения некоторой порции видеоклипа. Объектно-ориентированные системы ча- сто приводят к использованию памяти в предельном режиме при нормальном функционировании системы, поскольку разработчики не уделяют достаточного внимания реальным размерам объектов. На протяжении жизненного цикла разра-
Главв 9. Твстированив систам ботки тестирование начинается с использования небольшого числа объектов при тестировании отдельных модулей, нормального числа объектов во время комплек- сных и системных испытаний и чрезмерно большого числа объектов на поздних стадиях системных испытаний, когда система стала устойчивой в рабочем режиме. Одним из наиболее часто недооцениваемых предельных режимов является ес- тественное увеличение информации, которая накапливается по мере увеличения времени работы системы. Поскольку компания использует автоматизированную систему бухгалтерского учета и накапливает данные за несколько лет, то вполне естественным кажется стремление пользователей расширить анализ. Таким обра- зом, руководитель отдела, который раньше составлял бюджет по наитию, теперь просит систему загрузить данные за последние пять лет. Это может привести к существенному снижению производительности системы и даже к ее отказу. Этот режим работы в условиях предельной нагрузки должен быть применен во время тестирования на протяжении жизненного цикла. Тестирование на протяжении жизненного цикла Жизненный цикл системы может оказаться для системы достаточно продолжи- тельным и по этой причине трудным для моделирования в среде тестирования. Сначала идут жизненные циклы предметной области. Вторыми следуют жизнен- ные циклы компьютерного приложения. Жизненные циклы предметной области соответствуют ключевым процессам в предметной области. Например, в случае системы бухгалтерского учета можно выполнить прогон последовательности тестов, которые покрывают полный фи- нансовый год для конкретного набора счетов. Это начинается с инициализации счетов за год, проводки последовательности транзакций и выполнение других операций, прежде чем будут закрыты счета за год. Тестирование жизненного цик- ла должно включать реалистичное возрастание нагрузки системы. График должен учитывать время на подготовку тестовых данных или для написания программы, преобразующей существующие данные в соответствующий формат, воспринимае- мый тестируемой системой. Мы убедились в том, что это часть процесса тестиро- вания требует наибольших затрат времени, существенную помощь при этом могут оказать заказчики и специалисты в предметной области. Жизненный цикл приложения начинается с его установки и завершается его удалением. Это означает, что мы хотим подвергнуть тестированию программу ус- тановки и программу удаления установленной системы. Начальными условиями являются обычная машина (на которой программа будет установлена), которая не использовалась при разработке программного продукта. Выполнение программы установки должно иметь своим результатом готовое к использованию приложе- ние. Выполнение после этого программы удаления должно по существу вернуть систему в состояние, в котором она находилась до установки приложения. Коли- чество файлов на диске и доступное пространство памяти должны вернуться к своему первоначальному состоянию, в противном случае исход тестирования счи- тается неудачным.
Глава 9. Тастированиа систем КРАТКОЕ ОПИСАНИЕ МЕТОДА - ТЕСТИРОВАНИЕ В ПРЕДЕЛЬНЫХ РЕЖИМАХ В рамках тастироаания а предельных режимах выполняются следующие действия: • Выявить парамвнныв ресурсы, увеличивающие объем рвбот, которые должна выполнить система. • Если эти ресурсы связвны между собой квкими-нибудь отношениями, построить мвтрицу, в которой отображены различные сочвтвния уровней использовения ресурсов. • Построить тестовые случен, которые используют квждов сочвтвнив. • Выполнить прогон тестовых случеев и деть оценку рвзультвтоа. ПРОБЛЕМЫ РЕАЛЬНЫХ ДАННЫХ ПРИ ТЕСТИРОВАНИИ Когдв имеются большие объемы использованных рвнвв денных, которые, возможно, оствлись квк результат функционирования предыдущей версии системы, многие склонны считать, что зто свмый легкий способ получить тестовые двнныв. Время, необходимое для внвлизв этих данных фактически во внимание нв принимается. Однвко для каждого тестового случая зти данные должны быть исслвдоввны с целью определения ожидаемых результатов прогоне тестовых случввв нв этих данных. Что касввтся тестов, вовлекающих бизнвс-правила и бвзы данных, то зто можвт ока- заться весьма трудоемкой операцией, требующей больших затрат времени. Можвт случиться твк, что гораздо быстрее подготовить двнныв, обладающие специальными свойствами, нажали использовать реальные двнныв. При построении тестовых данных потребуется выполнить следующие действия: • Выполнить анализ существующих данных с целью выявления шаблонов. • Построить тестовые двнныв в соответствии с этими шаблонами, для которых ожидввмыв результаты определяются с меньшими звтрвтвми времени и усилий. • Построить тестовые случаи, которые используют полученные тестовые данные а контвкств полного жизненного цикла. • Выполнить прогон тестовых случввв и дать оценку рвзультвтоа. Тестирование характеристик производительности Первоначально объектно-ориентированные системы пользовались репутацией медленно действующих по своей природе. Следовательно, системы, для которых производительность играла важнейшую роль, оставались вне рамок объектно-ори- ентированного подхода. Однако произошли два события, которые в корне поме- няли как действительное положение вещей, так и сложившееся мнение об этих системах. Во-первых, усовершенствовались инструментальные средства. Компиляторы языка C++ генерируют более качественные программные коды. Оптимизированы виртуальные машины Java. Было проведено множество исследований, которые позволили оптимизировать старые и предложить новые структуры компиляторов и среды выполнения программ. Мы помогли своим клиентам успешно установить системы, применив для этих целей распределенные объектные технологии во встроенных средах реального времени.
Глава 9. Тестироааниа систем Во-вторых, по мере того, как пользователи углубляют свои познания в области объектно-ориентированных технологий, они обретают способность точно обосно- вать необходимость тех или иных проектных решений. Объектно-ориентирован- ные системы часто работают медленнее, чем должны (в абсолютном смысле), по- скольку другие цели проектирования получили более высокие приоритеты. Проектирование, в рамках которого основное внимание уделяется достижению максимальной производительности системы, выполняется в ключе, отличном от проектирования, в котором основные усилия направлены на достижение макси- мальной гибкости или простоты эксплуатации системы. «Тестирование» характеристик производительности во многом напоминает из- мерение надежности того или иного компонента программного обеспечения. Наи- более важный аспект заключается в определении и создании контекста, в котором будет измеряться производительность. Под контекстом мы понимаем описание окружающей среды, в которой будут проводиться измерения производительности. В описании должно быть уделено внимание количеству пользователей, зарегист- рированных в системе, конфигурации используемой машины и некоторым другим факторам, которые могут повлиять на поведение системы. Могут существовать многие контексты с различными целями и различными критериями. Контекст должен иметь особый смысл для пользователей программы и должен включать те аспекты программ, которые имеют для пользователя особое значение. Те атрибуты системы, которые имеют отношение к характеристикам произво- дительности, в различных системах принимают разные значения. В некоторых системах пропускная способность, измеренная в числе транзакций в минуту, мо- жет оказаться наиболее важным аспектом, в то время как в других она всего лишь способность достаточно оперативно реагировать на отдельные события. В игре «Кирпичики» фигурируют два аспекта производительности: скорость, с которой происходит обновление графических изображений, и быстрота, с которой обнару- живаются соударения и обновляется изображение на экране. Тестовые случаи, которые производят замеры скорости обновления графичес- ких изображений, практикуют максимально возможную загрузку системы. Каж- дый тестовый случай помещает на экран максимальное количество кирпичей и предполагает максимальные уровни ввода. Лопатка движется взад-вперед очень быстро. Вследствие этого производится максимальный объем вычислений и об- новлений изображений на экране. Ожидаемый результат таких испытаний - неза- метные «мерцания» графических изображений на экране. Перемещение изображе- ния лопатки по мере того, как мышь перемещается со стороны в сторону, должно быть плавным и соответствовать положению мыши. Как уже говорилось в разделе «Тестирование взаимодействий окружения», дру- гие приложения, выполняемые параллельно с прогоном тестов, могут повлиять на результаты тестирования, особенно в перспективе тестирования. Определение контекста для тестовых случаев должно также содержать описание состояний вы- полнения других приложений. Такой тест должен проводиться с использованием обычной нагрузки системы.
Глава 9. Тестирование систем КРАТКОЕ ОПИСАНИЕ МЕТОДА - ТЕСТИРОВАНИЕ ХАРАКТЕРИСТИК ПРОИЗВОДИТЕЛЬНОСТИ 1. Определить контекст, в котором будут производиться измерения харвктвристик производительности. а. Двть описвнив тестируемого приложения. Ь. Сформулировать условия выполнения в смысле используемой платформы. с. Двть описвнив других приложений, которые будут выполняться во время проведения тестирования. 2. Указать предельные значения в условиях данного контекста. 3. Определить как ожидввмый результат, что можно считать приемлемыми харак- теристиками производительности. 4. Выполнить твстированив и двть оценку полученных результатов. Тестирование систем различных типов Существует множество аспектов системы, которые влияют на то, как будет проводиться ее тестирование на максимальную эффективность. Мы все еще при- держиваемся мнения, что тестирование представляет собой процесс поиска. По- иск в системах различных типов производится различными способами, а его объектами служат различные вещи. Мы обобщим некоторые идеи, которые были предложены в различных частях данной книги, и добавим к ним ряд новых. Реагирующая система Объектно-ориентированные методы интенсивно использовались при построе- нии систем, которые управляются пользовательским вводом. Одной из важных характеристик таких систем является то, что в программах существует множество различных ветвей. Каждое последующее выполнение программы отличается от предыдущего. В результате зачастую получается очень сложный конечный авто- мат. Один из методов был описан в главе 7. Конечный автомат может быть раз- ложен по линиям отношения наследования и отношения «реализует» (“implements”). Интерфейс может определить конечный автомат, даже если тот не реализован. Построение тестовых случаев производится на базе разложения ко- нечного автомата, после чего эти тестовые случаи можно компоновать. Что касается реагирующих систем, то для них обычно имеется возможность выделить сопутствующие конечные автоматы. Часто они происходят от унаследо- ванных классов или от составных объектов. В частности, они происходят от пото- ков «слушателей» в интерфейсе, которые ожидают момента, когда пользователь возбудит событие, связанное с мышью, событие типа нажатие/отпускание кнопки или другие события ввода. Такие сопутствующие конечные автоматы можно сна- чала тестировать по отдельности, а затем все вместе. Измерение покрытий, которое особенно полезно для реагирующих систем, принадлежит к уровню покрытия «всех событий». Это делает возможным измере-
Глава 9. Тастирование систем ние покрытий на стороне ввода. Как таковое, оно позволяет нам определить, что система не делает ничего из того, для чего она не предназначена. Покрытие всех событий в такой системе, как игра «Кирпичики», означает покрытие таких собы- тий, как события, порожденные манипулированием мышью, системой управления окнами, операционной системой. Для обеих версий игры «Кирпичики» события, порождаемые перемещениями мыши и нажатиями кнопок, одни и те же, а потому тестовые случаи могут использоваться для всех версий. В версии Java-аплетов добавляются события, которые являются следствием запуска, останова и обновле- ния аплетов. Для каждого из такого события должны быть предусмотрены соответ- ствующие тестовые случаи, как показано на рис. 9.13. Встроенные системы Программы, которые выполняются на некоторой части аппаратных средств (например, зашитое в ПЗУ), называются встроенным программным обеспечением. Это программное обеспечение функционирует в условиях ограничений, наклады- ваемых на память и характеристики производительности (подчас довольно жест- кие). К такому типу систем применяется специальный набор объектно-ориенти- рованных проектных шаблонов. Среда разработки также обладает некоторыми особенностями, в частности, в том, что в качестве исходной среды выполнения программ часто используется специальная моделирующая программа, которая сначала компилируется с одним генератором кодов, а затем, на более поздних ста- диях, с другим генератором кодов. Первый из них ставит вопрос о том, насколько точно моделирующая программа соответствует реальной окружающей среде, а за- тем вопрос, насколько оба генератора отличаются друг от друга. Некоторые слож- ные проблемы компиляции, подобные реализации шаблонов в C++, обуслов- ливают генерацию двумя разными компиляторам объектных кодов, которые существенно отличаются друг от друга. Генератор событий Особое событие Ожидаемый результат Мышь LeftMouseButtonPress (нажатие левой кнопки мыши) Лопатка движется вертикально по отношению к положению пресса Мышь LeftMouseButtonPress Лопатка неподвижна Мышь RightMouseButtonPress (нажатие правой кнопки мыши) Всякое движение прекращается Мышь RightMouseButtonPress Движение возобновляется Appletviewer Пуск Шайба начинает движение Appletviewer Стоп Шайба прекращает движение Рисунок 9.13. Тестовые случаи, обеспечивающие покрытие событий
Глава 9. Тастированив систем Когда тестирование выполняется на двух различных компиляторах/окружени- ях, тип использованного тестирования зависит от природы тестовых случаев. Если эти тестовые случаи намерены потребовать установки большого числа заглу- шек на другие компоненты окружения, то, по-видимому, имеет смысл проделать минимальный объем тестирования на модели окружающей среды и максимальный объем в фактической окружающей среде. Если большого количества заглушек не требуется, или, возможно, вся система компонуется в моделирующей среде, то для выполнения всестороннего тестирования предпочтение отдается моделирую- щей среде. Обычно моделирующая сторона инструментальных средств содержит больше инструментальных средств, нежели аппаратная сторона. В любом случае повторное использование тестовых случаев представляет собой достаточно важную проблему. На этом этапе целесообразно писать тестовые слу- чаи на языке реализации системы, а не на языке подготовки сценариев. Персонал, осуществляющий отладку, должен иметь в своем распоряжении языковые сред- ства, которые работают как в моделирующей программе, так и в целевых аппарат- ных средствах; следовательно, персонал может применять тестовые случаи в усло- виях обоих окружений. Встроенные системы в большей степени управляются состояниями и более чувствительны к обеспечению безопасности, чем другие виды систем. Во многих случаях они не могут остановить выполнения, независимо от типа ошибки. Это означает, что существуют эффективные функциональные средства восстановле- ния, ассоциированные с такого рода системами. Это обстоятельство, по существу, обусловливает необходимость некоторого количества тестов, которые вводят в си- стему дефекты и определяют, не противоречит ли восстановление работоспособ- ности системы после ошибки спецификации поведения. Для построения тестов из конечного автомата для встроенной системы вос- пользуемся методом, црименяемым для тестирования протокола обмена данными. Этот метод получил название покрытие п-канального переключателя (n-way switch cover) [Chow87], Рассмотрим конечный автомат, показанный на рис. 9.14. Базо- вый набор тестов покроет любой переход конечного автомата по меньшей мере хотя бы раз. Эти же тесты определят, какие переходы не предусматриваются реа- лизацией рассматриваемого конечного автомата. Следующий уровень покрытия предназначается для охвата различных комбинаций специальных переходов в со- стояние из одного из параллельных наборов состояний со специальными перехо- дами в состояние из другого параллельного набора состояний. Такие системы часто включают в себя режим «самотестирования», о котором шла речь в начале данной главы. Вместо того, чтобы рассматривать это как про- блему тестирования, мы будем считать это частью поведения системы, которое подлежит тому же типу тестирования, что и любое другое поведение. Механизм самотестирования предназначен для выявления частей системы, которые не фун- кционируют должным образом. Чтобы подвергнуть тестированию поведение са- мотестирования, тестировщик должен внести ошибку в конфигурацию, соответ- ствующим образом изменив файл разрешений или удалив тот или иной ресурс.
Главв 9. Тестирование систем Затем должен быть выполнен прогон тестового случая, который вызывает режим самотестирования. В этом случае ожидаемым результатом будет отказ системы. Многоуровневые системы Многоуровневые и распределенные системы — это фактически одна и та же тема. Многоуровневая (multitiered) система - это просто распределенная система, обладающая особым архитектурным стилем. Очень часто при употреблении этого термина, он относится к системах, в которых воплощена конвейерная архитекту- ра. У клиента в качестве внешнего интерфейса имеется интерфейс GUI (Graphical User Interface - графический интерфейс пользователя) и некоторый набор средств вычислительной логики, сервер приложений или несколько слоев серверов в сере- дине и серверная база данных на другом конце. Объем вычислений, который мо- жет быть выполнен на клиентском уровне, не остается одним и тем же. Клиент и интерфейс GUI, выполняющие минимальный объем работы, называются «тонким» клиентом (thin client). Рисунок 9.14. Конечный автомат сотового телефона
372 Глава 9. Твстироввнив систем Такие системы охватывают поразительное разнообразие технологий. Порция GUI клиента может быть основана на технологии WWW и быть написана на языке JavaScript. Точная форма отображения может изменяться в зависимости от дан- ных, получаемых в формате XML. Интерфейс GUI может содержать Enterprise Javabeans, и если это на самом деле так, то в качестве среднего слоя может высту- пать сервер приложений. Распределенные системы Мы уже обращались к этой теме в главе 8 в перспективе инфраструктуры. В перспективе требований между распределенными и нераспределенными системы разница невелика. Различия на уровне спецификации заключается в том, требует- ся ли от системы, чтобы она выполняла следующее: 1. Выполняет поиск альтернативного сервера, когда ожидаемый сервер не най- ден. 2. Уведомляет пользователя и вносит в свою работу паузу в процессе ожида- ния устранения последствий сетевых проблем. 3. Завершает работу. Вопрос заключается в том, чтобы в спецификации системы оговаривались эти возможности. После этого можно разрабатывать тесты, покрывающие эти возмож- ности. Измерение тестового покрытия Напомним, что покрытие есть мера, показывающая степень доверия, которое мы испытываем по отношению к проводимому тестированию. Что подлежит покрытию? Имеется большое количество атрибутов системы, которые можно использовать для измерения покрытия. По существу, имеются две категории таких атрибутов: вводы и выводы. То есть, можно измерить, сколько возможных вводов было ис- пользовано в тестовых случаях, и можно измерить, сколько выводов из числа тех, которые может выдать эта система, были выданы во время тестирования. Традиционно покрытие рассматривалось с позиций перспективы тестирования. Типичными следует считать такие метрики, как процентное содержание покрытых строк программного кода или число альтернатив некоторого решения, которые были осуществлены. Мы постарались покрыть все случаи использования системы, взяв в качестве основы нашу модель случаев использования. Такой подход хоро- шо работает, когда нужно убедиться в том, что программный продукт делает все то, что он по замыслу должен делать. Если мы сможем получить все требуемые выходы, то этот факт свидетельствует о том, что продукт делает именно то, для чего он предназначен.
Главе 9. Тестирование систем По мере того как системы все больше интегрируются в системы, сопряженные с опасностью для жизни людей, или выполняющие важные задания, появляется все больше оснований, чтобы поставить вопрос: «Делает ли система что-либо из того, чего она делать не должна?» В таких случаях покрытие должно измеряться через число вводов. Могут ли вводы вызвать в таком случае катастрофический резуль- тат? Ответить на этот вопрос позволяют проводившиеся выше обсуждения вход- ных таблиц решений и классов эквивалентностей. В числе возможных метрик можно указать процент возможных событий, которые генерируются интерфейсом или процент значений класса эквивалентности, которые были использованы. Когда покрытие измеряется? Сбор данных о покрытии производится непрерывно на протяжении всего пе- риода тестирования. Действительно, классы РАСТ предусматривают объединение тестовых случаев в специальные группы - тестовые наборы, которые предназначе- ны для формирования определенных видов покрытий. Каждый тестовый случай выбирается с какой-либо специальной целью и может быть непосредственно выб- ран для покрытия конкретного аспекта системы. Поскольку процесс разработки инкрементальный, показатели покрытий меняются по мере внедрения дополни- тельных функциональных средств. Например, у тестового случая имеется основ- ной сценарий и некоторое множество альтернативных ветвей, расширений и ис- ключительных ситуаций. Обычно эти разнообразные виды использования постепенно находят себе применение в процессе наращивания. По мере того как добавляются все новые тестовые случаи с целью покрытия различных ветвей слу- чаев использования, степень покрытия возрастает. План тестирования конкретно- го случая использования документально фиксирует соответствие между тестовы- ми случаями и покрытие случая использования, которое они обеспечивают. Покрытие может измениться и после того, как программный продукт вышел в свет. В частности, оно может даже уменьшиться! Когда выпускается новая версии библиотеки DLL, в этой библиотеке могут быть определены дополнительные клас- сы, которые динамически интегрируются в приложение. Количество возможных ветвей увеличивается, и если не будут введены дополнительные тестовые случаи, покрытие приложения уменьшится. Когда используется покрытие? Мы отвечаем на это вопрос так: «Во время выпуска». Другими словами, покры- тие есть составляющая критерия принятия решения выпуска в свет программного продукта. Мы не выпускаем программный продукт до тех пор, пока не достигнем целей, для решения которых и предназначено покрытие (в их число не входит соблюдение объявленной заранее даты выпуска программного продукта). Отчет о системных испытаниях связывает уровень покрытия с качеством поставляемого программного продукта, измеренного процентным отношением дефектов, обнару- женных после поставки продукта. Этот документ периодически обновляется и отображает новейшую информацию.
Глава 9. Тестирование систем Классификация ODC Ранее в этой главе для отбора тестовых случаев мы использовали триггеры от- казов, определение которых дано в классификации ODC (Orthogonal Defect Classification - ортогональная классификация дефектов). Сейчас мы хотим вос- пользоваться воздействиями дефектов (см. рис. 9.15), чтобы проверить качество тестирования важных аспектов системы при помощи выбранных тестовых случа- ев. Категории воздействий дефектов суть атрибуты системы, на которые дефекты оказывают неблагоприятное влияние. Во время анализа результатов сеанса отлад- ки приходится искать ответ на вопрос, обнаружены ли в результате тестирования дефекты в каждой пораженной области. Отчет о тестировании системы должен содержать список всех этих категорий и показывать, какие факты в результатах тестирования свидетельствуют об их наличии. Средства этого альтернативного подхода к традиционному покрытию тестовы- ми случаями позволяют обеспечить покрытия атрибутов, наиболее важных для успешной работы приложения. Вся трудность заключается в том, что принимая эту обратную картину внешнего мира, мы не можем быть уверены, то ли данная программа не оказывает никакого воздействия на конкретную область, то ли про- веденное нами тестирование оказалось недостаточно глубоким и всесторонним. На рис. 9.15 мы предоставляем тестировщикам некоторую дополнительную ин- формацию для размышления. Это не какой-либо конкретный алгоритм, обяза- тельный для исполнения, но иметь в своем распоряжении этот список при состав- лении плана тестирования системы и при оценке покрытия, как показал наш собственный опыт, весьма полезно. Дополнительные случаи Java-версия игры «Кирпичики» разрабатывалась на персональном компьютере, после чего была установлена на компьютере с операционной системой Unix. Пос- ле установки пользователи сообщили, что игра не функционирует. Приложение сразу же выдавало сообщение «Запас шайб израсходован, вы проиграли». При внимательном рассмотрении выяснилось, что в общем игра работает правильно, однако битовые изображения лопатки и шайбы оказались недоступными. Следо- вательно, пользователь ничего не мог видеть, и каждая шайба сразу же падала на пол! Разработчики решили эту проблему, обеспечив доступ к соответствующим битовым изображениям, после чего провели тестирование развертывания прило- жения на свободной машине, чтобы убедиться, что внесенные исправления рабо- тают. Распределенная программа игры крестики-нолики использует оболочку JavaHelp. Когда данная программа создавалась на машине разработчиков, они не знали, что система справочной информации автоматически не включила себя в путь класса. Тестирование развертывания этого приложения обнаружило этот де- фект посредством покрытия тестами всех событий, в том числе таких событий, как выбор позиций в меню и перемещение мыши.
Глава 9. Тестирование систем Классификация ODC воздействий дефектов Типы тестирования, используемые для обнаружения дефектов Что делать, если не обнаружено никаких дефектов, способных вызвать данный тип воздействия Возможности См. раздел «Тестирование на соответствие функциональным требованиям». Если не найдено никаких функциональных дефектов, проведите переоценку критериев покрытия случаев использования. Покрывать исключительные ситуации и альтернативы? Удобство и простота использования Поскольку это общая проблема тестирования, мы предлагаем обратиться к источнику [Niels94] Выясните, все ли основные экраны были подвергнуты тестированию. Характеристики производительности См. раздел «Тестирование в предельных режимах». Проверьте, достаточно ли высокой была нагрузка программного продукта. Если нет, увеличьте нагрузку, если да, переходите к другим пунктам плана. Надежность Это воздействие ощущается на всех видах тестирования; однако отрицательное тестирование осуществляет проверку надежности. Никаких специальных действий, кроме вычисления показателя надежности на основании результатов прогона полного тестового набора, не предпринимается. Развертывание См. раздел «Тестирование развертывания системы». Если программный продукт не был развернут в рамках исследуемых систем, добавьте эти тесты в тестовый набор, а если был, то переходите к другим пунктам плана. Эксплуатационная надежность См. раздел «Модели для тестирования дополнительных свойств». Никаких специальных действий не предпринимается. Документация См. все, что касается целенаправленной проверки, глава 4. Выяснить, связывала ли целенаправленная проверка документацию пользователя и оперативную справочную систему. Миграция/ переносимость Эта проблема носит более общий характер, поэтому мы отсылаем читателя к источнику [Brow77] Убедитесь в том, что все приемлемые цели включены для рассмотрения и анализа. Стандарты См. раздел «Тестовые случаи, основанные на интерфейсах». Выделите стандартные интерфейсы. Были ли применены тесты, основанные на этих интерфейсах? Если да, то переходите к другим пунктам плана. Целостность/ безопасность См. раздел «Тестирование системы безопасности». Если достигнут уровень безопасности всех каталогов, переходите к другим пунктам плана. РисуНОК 9.15. Воздействие дефектов по классификации OD С
Глава 9. Тестирование систем Резюме То, как следует проводить тестирование приложения, определяет не столько логическая структура объектов, сколько технологии, которые инкапсулируют и разворачивают эти приложения. Модульность пакетирования и динамическая природа компиляции исполнительного программного кода также влияют на вы- полняемые виды тестирования. Бесконечный на первый взгляд массив тестов, прогон которых возможен, дол- жен быть ограничен набором тестов, прогон которых должен осуществляться с учетом функциональных приоритетов проекта, как это следует из профиля ис- пользования, и уровнем желаемого качества, которое обеспечивается уровнем по- крытия, предусматриваемым планом тестирования проекта. В этой главе мы пытались затронуть лишь некоторые характеристики и атрибу- ты приложения, которые оказывают влияние на тестирование, и дать краткое опи- сание методов тестирования, ассоциированных с каждым из них. И наконец, мы проанализировали отношение покрытия и видов воздействий дефектов на прило- жение. Упражнения 9-1 Если вы будете выполнять упражнения, используя для этой цели собственный пример, рассмотрите следующие возможности: • Составьте план тестирования своего примера, используя для этой цели шаблон полного плана тестирования. • Выберите в данной главе такие разделы, которые подходят по смыслу для вашего приложения и напишите соответствующие типы тестов. • Сравните собственные тестовые случаи с воздействиями дефектов по классифи- кации ODC с целью определения областей, не охваченных тестированием. 9-2 Рассмотрите наш рабочий пример игры «Кирпичики». • Заполните те разделы плана тестирования, которые еще не были сформулиро- ваны. • Добавьте новые тесты к тем, спецификации которых уже были разработаны. • Сравните эти тестовые наборы с воздействиями дефектов по классификации ODC с целью определить области, не охваченные тестированием. 9-3 Проведите исследование типовых событий, которыми манипулируют интерактивные системы. Напишите тестовый шаблон, который показывает персоналу, осуществ- ляющему системное тестирование, как построить всесторонние и эффективные наборы тестов для этих событий.
Глава Компоненты, каркасы и линии продуктов ► Желаете разобраться с отличиями между тестированием компонентов и объектов? Обратитесь к разделу "Тестирование компонентов и объектов" ► Требуется понять, как создавать тестовые случаи из интерфейсов? Обратитесь к разделу "Тестирование в рамках проекта линии продуктов" ► Необходимо разобраться с методикой тестирования структур? Обратитесь к разделу "Процесс тестирования структур" ► Хотите понять, как организовать тестирование а рамках проекта линии продукта? Обратитесь к разделу "Тестирование в рамках проекта линии продуктов" С каждой из тем, обозначенных в заглавии главы, связаны собственные про- блемы тестирования, и в этой главе мы обязательно обратимся к ним. Вместе они представляют собой возможности для разработчиков и сложные проблемы для те- стировщиков. В предыдущих главах много внимания уделялось вопросам упро- щения многократного использования и гибкости проектирования на уровне объектов. В этой главе мы снова обратимся к этим темам, но на этот раз на уровне в контексте разработки некоторого множества приложений за счет композиции компонентов в соответствующую структуру. Под линией продуктов (product line) мы понимаем последовательность логичес- ки связанных программных приложений, созданных на базе общей архитектуры, которая разработана таким образом, что допускает модификацию некоторым пре- допределенным способом. Это можно рассматривать как некоторую организаци- онную структуру, в рамках которой группы разработчиков разбиваются на после- довательность проектов построения логически связанных приложений. Эти группы пользуются такими методами проектирования и реализации, которые по- зволяют им добиваться высокой производительности и качества. Базовую архи- тектуру можно рассматривать как некоторый тип структуры приложения. Про- граммные компоненты совместно используются всеми группами разработчиков, 377
Глава 10. Компоненты, каркасы и линии продуктов которые «навешивают» на структуру различные программные средства и комбина- ции программных компонентов. Структура (framework), или каркас, представляет собой частично завершенное программное приложение, которое разработано с таким расчетом, чтобы ее можно было модифицировать некоторыми заранее известными способами. Такова страте- гия реализации архитектуры, в рамках которой используются методы проек- тирования, подобные проектным шаблонам, и методы реализации, подобные полиморфизму. Такое объектно-ориентированное понятие, как структура, предус- матривает наличие множества абстрактных классов в стратегических областях. Разработчики приложения наделяют эти классы специфическими свойствами, благодаря чему приложение приобретает необходимое поведение и реализует стандартное поведение там, где это возможно. Более общее представление струк- туры замещает абстрактные классы интерфейсами, которые способны реализовать отдельные классы, или более сложными, в некоторых случаях даже не объектно- ориентированными, компонентами. Компоненты (components) для некоторых программистов, не сумевших решить всех своих проблем путем использования объектов, стали своего рода “серебря- ной пулей”. Мы использовали этот термин в различных местах данной книги, но нигде не давали его точного определения. Вы его не найдете и в этой главе. Этот термин употребляется для обозначения различных технологий реализации. Мы использовали и продолжаем использовать термин компонент в отношении более- менее широкого набора функциональных возможностей, которые обеспечиваются набором объектов. Компонент реализует набор интерфейсов, который определяет общедоступное поведение. Компонент использует технологию упаковки, позво- ляющую инкапсулировать и сокрыть реализацию. Большая часть обсуждений, касающихся распределенных объектов, проведен- ных в главе 8, в частности, размышления об интерфейсах, применимы и к компо- нентам. Мы обсудим некоторые различия между объектами и компонентами в контексте структур и линий. Вместе три упомянутых идеи приводят к образованию технологии, обеспечи- вающей получение программных приложений, обладающих высоким качеством и невысокой стоимостью. Мы рассмотрим, как эти три фактора влияют друг на дру- га и как можно оптимизировать процесс тестирования при разработке среды, ис- пользующей преимущества этих подходов. Модели компонентов Одно из стандартных определений компонента было дано Клеменсом Шипер- ски (Clemens Szypersky) [Shyp98J: Программный компонент — это композиционный модуль, обладающий ин- терфейсами, определенными в виде контрактов, и только явными контекст- ными зависимостями. Программный компонент может быть развернут са- мостоятельно и использован в качестве композиционного элементы сторонними проектными организациями.
Глава 10. Компоненты, квркасы и линии продуктов Это определение применимо как техническим, так и к рыночным аспектам «компонента». Если даже в дальнейшем мы будем пользоваться предложенным нами менее формальным определением, мы воспользуемся определением, данным Шиперски, с целью проведения продуктивного обсуждения тестовых компонентов. Компонент есть некоторая «порция» функциональных возможностей, скрытая в некотором пакете, о наличии которой оповещают один или большее количество интерфейсов. Полная спецификация компонента представляет собой список служб (в контексте компонента мы будем пользоваться термином служба вместо метода), доступных пользователям компонента. Каждая служба определяется пре- дусловиями и постусловиями в соответствии с изложенным в предыдущих главах. Таким образом, построение тестовых случаев с целью проверки того или иного компонента практически ничем не отличается от их построения для тестирования классов, за исключением того обстоятельства, что каждая служба обычно пред- ставлена большей порцией функциональных возможностей, нежели метод, и большим количеством параметров. Это в свою очередь означает большее число перестановок значений. Конкретный компонент обычно реализует несколько ин- терфейсов, при этом каждый интерфейс может быть реализован несколькими ком- понентами. Распределенные компоненты Мы уже обсуждали этот вопрос в главе 8, поскольку инфраструктура, которая необходима для взаимодействия компонентов, может быть расширена до поддер- жки распределения компонентов, причем для этого потребуются незначительные трудозатраты. Мы также обсуждали некоторые из этих аспектов в главе 9 с пози- ций «системной» перспективы. Здесь внимание будет сосредоточено на «компо- нентных» аспектах распределенных объектов. Распределенные среды были первыми программными продуктами, предостав- ляющими достаточно полную инфраструктуру, которая позволяла отделить функ- циональные возможности от их способности обмениваться данными с другими модулями. Такие системы предоставляют широкий ассортимент служб, которые «бесшовно сращивают» компоненты, основанные на некотором наборе предполо- жений, с компонентами, основанными на некотором наборе интерфейсов. Неко- торые из этих инфраструктур поддерживают взаимодействие компонентов, реали- зованных на различных языках программирования, выполняются на машинах с различными архитектурами или постоянно находятся в средах различных опера- ционных систем. В число основных моделей компонентов входят спецификация технологии CORBA (Common Object Request Broker Architecture) [OGM98] группы OMG (Object Management Group), предложенная корпорацией IBM, модель DCOM (Distributed Component Object Model) корпорации Microsoft [Redm97] и модель EJB (Enterprise JavaBeans) корпорации Sun [МоНаОО]. Мы уже приводили описа- ния моделей CORBA и DCOM в главе 8, так что теперь сосредоточимся на изуче- нии модель EJB.
Глава 10. Компоненты, каркасы и линии продуктов Модель компонента Enterprise JavaBeans Компонент Enterprise JavaBeans является первым из всех Ьеап-компонентов Java. Bean-компонент — это кластер, состоящий из одного или большего числа классов, которые взаимодействуют с целью реализации поведения, описанного в спецификации bean-компонента (bean буквально означает “боб”). В основу моде- ли bean-компонента положено следование разработчиков конкретным проектным шаблонам, а не соблюдение требований, чтобы каждый компонент осуществлял реализацию одного стандартного интерфейса или наследовал от общего базового класса. Модель bean-компонента обеспечивает стандартный подход к тому, как компоненты взаимодействуют друг с другом и как они включаются в приложения. Модель bean-компонента добавляет уровень этапа разработки между проекти- рованием и реализацией классов и его внедрением в приложение. Каждый Ьеап- компонент определяет некоторый набор свойств. Свойство есть атрибут, который может принимать конкретное значение из некоторого набора значений. Напри- мер, bean-компонент может выбрать конкретный объект из некоторого пула дос- тупных объектов в силу того, что этот объект реализует алгоритм сортировки. Значения свойств bean-компонента хранятся в специальном файле свойств. Ис- пользуя инструментальные средства разработки, разработчик, в отличие от класса, может конкретизировать bean-объект, присваивая его атрибутам конкретные зна- чения. Такие специализированные bean-компоненты должны быть подвергнуты тестированию с целью проверки, что значения свойств выбраны корректно. Bean-компоненты взаимодействуют с другими bean-компонентами через собы- тия и шаблоны взаимодействия. Каждый тип bean-компонента описывает набор событий, которые он генерирует, и набор событий, на которые откликается. BeanBox [White97] - это простой bean-компонент, который позволяет разработчи- кам порождать экземпляры своих bean-компонентов и выполнять их, посылая стандартные события экземпляру в окне и наблюдая за его реакцией. Последнее утверждение исследуется в разделе «Компонент TestBox из ВеапВох». Enterprise JavaBeans представляют собой bean-компоненты, которые принима- ют участие в распределенных взаимодействиях с другими Ьеап-компонентами, используя при этом протокол RMI (Remote Method Invocation - Удаленный вызов методов), который был кратко описан в параграфе «Технология RMI» главы 8. Эти bean-компоненты часто используют пользовательский интерфейс, основанный на технологии WWW, и сервер приложения. Возможно, самым важным аспектом bean-компонентов с позиций тестирова- ния является их ориентация на шаблоны. Два наиболее часто используемых шаб- лонов при разработке bean-компонентов являются шаблон слушателя и шаблон изменений с возможностью запрета (vetoable-change pattern). В шаблоне слушате- ля реализован механизм, подобный регистрации событий и ретрансляции.
Глава 10. Компоненты, каркасы и линии продуктов КОМПОНЕНТ TESTBOX ИЗ BEANBOX ВвапВох особенно хорошо проявляет себя кек твстоввя среда для тах Ьевп-компо- нвнтов, которые относятся к квтвгории визувльных. Тестировщик можат использовать этот блок для вызове в интврвктивном режиме появления событий, после чего нвблюдать их результаты. Имеется также возможность специвлизироввть ВевпВох и многокрвтно выполнять нвборы соответствующих тестов. Даже для Ьвап-компо- нвнтов, которые нв принадлежат к числу визувльных, ВвапВох, укомплектованный несколькими простыми дополнительными модулями, может стать весьма полезной тестовой средой. Каждому событию выделяется простой метод, обеспечивающий выдачу визуальной подсквзки и уведомляющий о том, что Ьввп-компонент получай и отрввгироввл на событие. Зтв схамв четко работает, поскольку существует вполне определенный набор событий, на которые реагируют Ьаап-компоненты. Для других типов компонентов мы расширили подход ВевпВох до того, что будем называть компонентом TastBox. Каждый компонент TastBox реализован с таким расчетом, чтобы он был способен взаимодействовать со специальным набором методов, которые описаны в определении интврфвйсв. Создание блока TastBox для каждого интерфейса, предназначенного для широкого использования, представляется выгодной инввстицивй ресурсов. Мы объединили идеи блока TastBox с подходом, предусматривающим использованив тестовых классов архитектуры PACT (Parallel Architecture for Component Tasting - Параллельная архитектура тестирования ком- понентов]. Каждый тестовый клвсс содержит тестовые случаи, осуществляющие аызов методов компонента TastBox. Тестирование компонентов и объектов В перспективе тестирования между компонентами существует много общего и совсем немного различий. Общими являются следующие свойства: 1. Четко определенная спецификация — Технологии, используемые для инкап- суляции функциональных средств в некоторый компонент, поддерживают определение спецификаций. Спецификации, составленные в виде стандар- тизованных выражений на языке OCL упрощают задачу написания тесто- вых случаев. Спецификация компонента представляет собой агрегацию спе- цификаций всех служб, которые предоставляет этот компонент. К этому добавляются инварианты, которые накладывают свои ограничения на общее состояние компонента. Предусловия отдельных служб сопровождаются спе- цификацией интерфейса “requires” (“требует”) компонента. Интерфейс “requires” дает описание внешних поведений, к которым рассматриваемый компонент должен иметь доступ, дабы корректно выполнять свои функции. За подробностями построения тестов на базе предусловий, постусловий и инвариантов мы отсылаем читателя к главе 5. 2. Динамическое подключение (plug and play) — Компонентная технология по- зволяет проводить в приложениях «горячую замену» компонентов. Она осу- ществляется в соответствии с принципом полиморфной подстановки. Ин- терфейсы, реализуемые этим компонентом, представляют собой «типы», которые относятся к иерархии обобщенных представлений, и используются для определения правильных подстановок. «Эксперименты», статистически
Глава 10. Компоненты, каркасы и пинии продуктов направляемые системой OATS (Orthogonal Агтау Testing System - система тестирования с использованием ортогональной матрицы), которые осуще- ствляют тестирование поднаборов возможных конфигураций компонентов, позволяют достигать плотного тестового покрытия при минимальном коли- честве тестовых случаев. Более подробно вопросы выборки полиморфно за- меняемых модулей рассматриваются в главе 7. 3. Стандартная схема взаимодействий - Методы проектирования и разработки применительно к компонентам соответствуют подходу разработки, ориен- тированной на образное представление. Определенную пользу приносит здесь применение тестовых шаблонов. Примеры тестовых шаблонов можно найти в главе 8 и ниже в этой главе. Между компонентами и объектами имеются некоторые различия. Обычно они касаются возможностей функциональных средств и используемой технологии. К их числу относятся: 1. Компоненты суть кластеры объектов. Тестирование компонентов включает многие элементы тестирования взаимодействий, имеющих место в рамках кластера объектов. 2. Компонент обладает большими размерами, нежели объект. Для программно- го кода компонента труднее построить покрытие в условиях функционального подхода на базе спецификации этого компонента, чем для объекта. 3. Обычно компонент обладает большей степенью автономности по сравнению с объектом. Упаковка компонента обычно удовлетворяет всем его специфи- кациям “requires”. То есть, компонент обычно поставляется с ресурсами, библиотеками, графическими образами и данными, необходимыми для ра- боты. Проблемы могут возникнуть в тех случаях, когда ресурсы, подобные DLL-библиотекам, востребованным этим компонентом и упакованным вме- сте с ним, видимы и вступают в конфликт с существующими версиями тех же DLL-библиотек, используемых другими компонентами. В разделах «Тес- тирование механизма развертывания системы» и «Тестирование после раз- вертывания системы» в главе 9 речь шла о методах, которые можно было использовать на уровне компонентов так же, как и на системном уровне. Процессы тестирования компонентов Мы остановим свое внимание на трех процессах, которые используют компо- ненты на различных этапах их жизненного цикла. 1. Компонент подвергается тестированию по мере его разработки. 2. Компоненты тестируются при их включении в более крупные агрегации. 3. Компонент тестируется перед его использованием. В каждом из этих процессов нас интересуют некоторые свойства компонента, которые могут стать причиной отказов.
Глава 10. Компоненты, каркасы и линии продуктов Процесс тестирования компонентов на стадии разработки Процесс разработки включает описание, разработку и тестирование компонен- та как изолированной сущности. В число триггеров дефектов входят следующие: 1. Взаимодействия — Обычно компонент представляет собой агрегацию из не- скольких объектов. Ошибка в одном из объектов может породить целую се- рию ошибок во многих объектах компонента. Воспользуйтесь рассмотрен- ными в главе 6 методами определения тестовых случаев, которые исследуют межклассовые взаимодействия объектов в рамках компонента. 2. Параллелизм — Большинство компонентов инкапсулируют некоторое мно- жество потоков. Воспользуйтесь методами, рассмотренными в главе 8, кото- рые предназначены для тестирования взаимодействия потоков. Используй- те классы РАСТ, включающие множество потоков, для одновременного вызова методов в интерфейсе компонента. Процесс тестирования компонентов на стадии интеграции Интеграция некоторого набора компонентов включает по меньшей мере три специальных свойства, которые могут инициировать отказы: 1. Последовательность сообщений — Ранее упоминавшийся протокол представ- ляет собой последовательность сообщений, которыми обмениваются два компонента. Требуется построить тестовые случаи, образующие покрытия каждого протокола, в котором принимают участие оба компонента. При этом возможна генерация исключений между компонентами. 2. Синхронизация — Состояние гонок может возникнуть, когда объединяются два компонента, манипулирующие собственными потоками. Сообщения могут остаться неотосланными либо не поступать вовремя. Тестовые случаи должны провести исследование влияния неоправданно затянувшегося вре- мени ожидания на взаимодействие двух участвующих в нем компонентов. (Может случиться так, что этот недостаток характерен для вашей собствен- ной домашней сети.) 3. Коммуникации — Компоненты, построенные в рамках различных моделей, таких как CORBA и СОМ, обмениваются данными через адаптеры, которые часто называются шлюзами или мостами. Используются также отображения примитивных типов данных специализированных языков программирова- ния на представления в модели компонента. Тестовые случаи должны быть построены таким образом, чтобы они охватывали каждый путь обмена дан- ными в программе. Процесс тестирования компонентов на стадии приемочных испытаний Если компоненты поступают из других коммерческих источников или если вы рискнете задействовать свободно распространяемые программные средства, необ- ходима их тщательная отладка с целью оценки их качества. По-видимому, следует
Глава 10. Компоненты, каркасы и линии продуктов проводить их всестороннее тестирование, при этом особое внимание потребуется уделить следующим вопросам: 1. Проверка функционирования в экстремальных точках спецификации каж- дой службы. Необходимо также проверить, насколь устойчивым является тестируемый компонент, задавая значения, выходящие за пределы этих эк- стремальных точек. 2. Проверка совместимости с нестандартными частями инфраструктуры. Если компонент построен на базе инфраструктуры CORBA, следует убедиться в том, что компонент выполняет соответствующие вызовы для подключения к ней и использует ORB, поскольку отсутствует стандартный API-интер- фейс. 3. Проверка компонента под нагрузкой. Простое тестирование программы мо- жет не позволить выявить неэффективность алгоритма или запросы чрез- мерно большого объема памяти. Следует, по меньшей мере, воспользовать- ся представительными объемами данных, которые имитируют реальную работу системы, и если позволяет время, испытать систему в условиях на- грузки, близкой к предельной, тем самым проверив ее запас прочности. Тестовые случаи, основанные на интерфейсах Подход РАСТ применяется к интерфейсам так же, как и к классам. Иначе гово- ря, каждый интерфейс должен сопровождаться тестовым классом, который опре- деляет набор функциональных тестовых случаев для проверки служб, перечислен- ных в интерфейсе. Тестовый класс для каждого средства реализации интерфейса агрегирует тестовые случаи, определенные для этого интерфейса. Имеются две причины того, почему отношением между тестовыми классами является агрегиро- вание, а не наследование. Во-первых, многие языки программирования не под- держивают множественное наследование, а кроме того, к одному классу может применяться множество интерфейсов. Во-вторых, интерфейс не дает реализации, в нем дана только спецификация. Тестовый класс выступает в роли посредника и проходит через запросы тестов у экземпляра тестового класса интерфейса. Такое повторное использование тестов из интерфейсов даже еще более продук- тивно для «стандартных» интерфейсов. Некоторое число международных, ком- мерческих и специальных стандартов, таких как CORBA и ODBC, применяются в системах, основанных на использовании компонентов. Возможности тестов, ори- ентированных на интерфейсы ORB, принятые к исполнению той или иной ком- панией, должны стать доступными для более широкого сообщества разработчиков этой компании. Компонент TextBox создается для каждого компонента специаль- ного типа и используется совместно с другими тестовыми классами (см. раздел «Сравнение компонента TextBox и тестового класса»). Такой обмен данными меж- ду стандартными тестовыми случаями представляет собой службу, которая может предоставляться группой обеспечения качества компании. Предоставление под-
Глава 10. Компоненты, каркасы и линии продуктов робных тестов этих стандартных задач особенно полезно, поскольку во многих из них имеют место асинхронные взаимодействия потоков. — СРАВНЕНИЕ КОМПОНЕНТА TEXTBOX И ТЕСТОВОГО КЛАССА Компонент TextBox предоставляет сраду выполнения тестируемого объекта. Он пред- лагает специальные службы, которые характерны для специального типа компонентов. Например, TextBox в рамках CORBA обеспечивает доступ к брокеру ORB и другим службам. Тестовый класс представляет собой агрегацию всех тестов компонента. Блок TextBox использует тестовый класс для предоставления тестовых случаев. Блок TaxtBox может работать со множеством различных компонантов и с соответствую- щими им тестовыми классами. Технология упаковки компонента используется для инкапсуляции всех фраг- ментов, составляющих компонент. DLL-библиотеки и архивы JAR (Java Archives — архивы Java) представляют собой две технологии упаковки, получив- шие весьма широкое распространение. Во время создания пакетов очень важно, чтобы прогон тестовых случаев выполнялся таким образом, чтобы каждый пакет был использован, по меньшей мере, один раз. Это особенно важно в случае дина- мических файлов JAR и других видов динамических ресурсов, подобных Web- страницам или сценариям. Протокол обмена между двумя компонентами — это последовательность сооб- щений, которыми они обмениваются. Это можно рассматривать как дальнейшие ограничения, накладываемые на оба компонента. Компонент не только уведомля- ет о службах, которые он предоставляет (через интерфейс), и что для этого требу- ется (через его предусловия), но он также должен описать последовательность, в которой в соответствии с ожиданиями должны происходить эти взаимодействия. Определение общей последовательности получаем путем комбинирования пред- лагаемых и требуемых спецификаций этих двух компонентов. Один набор тесто- вых случаев, определенный на базе этих протоколов, должен выполнить интегри- рование двух компонентов, используя для этой цели полный протокол. Рассмотрим взаимодействие двух компонентов в случае, когда один из них управляет некоторой частью аппаратных средств, а другой взаимодействует с пользователем (см. рис. 10.1). Компонент пользователя желает, чтобы оборудова- ние реализовало службу, на что будет затрачено несколько секунд, в то же время компонент пользователя не хочет блокировать оборудование на этот период вре- мени. Пользователь может потребовать завершения операции с тем, чтобы компо- нент пользователя был в состоянии получать и обрабатывать события. Компонент пользователя Запустить в работу Запущено Остановить Остановлено ◄ Компонент оборудования 2-7! Рисунок 10.1. Пример протокола 13
Глава 10. Компоненты, каркасы и линии продуктов Указанный компонент пользователя посылает асинхронное сообщение компо- ненту оборудования с требованием начать работу. В некоторой точке компонент оборудования, который обслуживает некоторое множество клиентов, отправляет обратно асинхронное сообщение с уведомлением о том, что он начал функциони- ровать. Аналогичный обмен сообщениями происходит, когда компонент пользо- вателя желает, чтобы оборудование остановилось. Эти четыре сообщения появля- ются в виде группы. Проектировщик, включающий эти компоненты в свой проект, должен знать, что собой представляет такая группировка, поскольку не существует соглашений о ее обозначении. Два сообщения из этой группы пред- ставляют собой запрос на предоставление службы одному компоненту и два дру- гих сообщения — на предоставление службы другому компоненту. Компонент может принимать участие сразу в нескольких протоколах, для чего нужно, чтобы соответствующие наборы его методов были включены в определе- ние каждого из этих протоколов. Служба, предоставляемая компонентом, может быть включена в несколько определений протоколов. Тестовый набор этого ком- понента должен содержать тестовые случаи, в которых класс Tester играет роль другого компонента протокола. Это означает, что, например, в Java класс Tester может реализовать несколько интерфейсов. Это позволяет объекту класса Tester передать себя тестируемому компоненту, после чего вызывать службы, описанные в протоколе. Реальный пример - компонент GameBoard В Java-версии разработанной нами игры крестики-нолики игровое поле реали- зовано как bean-компонент Java с именем GameBoard. Помимо всего прочего это означает, что игровое поле написано как более универсальный модуль, чем пользовательский интерфейс для игры в крестики-нолики. Он написан так, чтобы было возможным конфигурирование игрового поля для любой игры, для которой требуется прямоугольная сетка для определения позиции. Это также означает, что данный «компонент» содержит несколько классов. Имеется класс GameBoardlnfo, который представляет собой стандартное свойство bean-компонента, и класс GameBoardPosition, который придает абстрактный смысл понятию каждой ячейки на игровом поле. Bean-компонент GameBoard проектировался в соответствии с двумя стандарт- ными проектными шаблонами Java и JavaBean. Способность выбрать клетку на игровом поле игры крестики-нолики реализована как изменение с возможностью запрета (vetoable change). В условиях такого проектного шаблона объекты регист- рируются с тем, чтобы получать уведомления об изменениях, и у них есть воз- можность отменить или запретить это изменение. Если изменение не запрещено, оно выполняется. Если изменение запрещено, оно, соответственно, не выполня- ется. Рисунок 10.2 служит иллюстрацией алгоритма изменения с возможностью запрета.
Главв 10. Компоненты, карквсы и линии продуктов Изменения в объекте Объект поддержки изменении с возможностью запрета Прослушивающий объект Отправить сообщение об изменении с возможностью запрета Отправить сообщение об изменении с возможностью запрета Рисунок 10.2. Диаграмма действий механизма изменений с возможностью запрета public class Gameboard extends JFrame { public Gameboard () public void acti vateMenu () public void deactivateMenu() protected void closeWindow() protected void loadGame() protected void saveGame() protected void quitGame() public void GameBoardMouseUp (MouseEvent evt, int x, int y) public void setMove( int pos ) protected void recalcPositions(int screenwidth, int screenBeight) public int getPlayerlD() public void setGameBoardPositions(int[] boardstate) protected void setPositionState(int pos, int newState) public void setGameStatus(int newStatus) public int getGameStatus () public void showStatus (Graphics g) public void paint (Graphics g) public void addPropertyChangeListener (PropertyChangeListener 1) public void removaPropertyChangeListener(PropertyChangeListener 1) public void addLoadPropertyChangeListener (PropertyChangeListener 1) public void removeLoadPropertyChangeListener (PropertyChangeListener 1) public void addSavePropertyChangeListener(PropertyChangeListener 1) public void removeSavePropertyChangeListener (PropertyChangeListener 1) public void addQuitPropertyChangeListener (PropertyChangeListener 1) public void removeQuitPropertyChangeListener (PropertyChangeListener 1) public void addVetoableChangeListener (VetoableChangeListener 1) public void removeVetoableChangeListener (VetoableChangeListener 1) ) Рисунок 10.3. Интерфейс Ьеап-компонента Game Board I3*
Глааа 10. Компоненты, каркасы и линии продуктов На рис. 10.3 представлен интерфейс для bean-компонента GameBoard. Он включает сигнатуру метода setMove(int). Этот метод вызывает механизм измене- ний с возможностью запрета. Тестовый шаблон для этого проектного шаблона описан на рис. 10.4 и реализован классами, показанными на рис. 10.5. Поток дей- ствий при использовании тестового шаблона показан на рис. 10.6. На рис. 10.7 приводится реализация тестового шаблона с механизмом изменений с возможно- стью запрета, каким он запрограммирован в методе setMoveTest(). На рис. 10.8 представлен план тестирования этого компонента. Если бы в Java, как и в C++, были реализованы шаблонные методы, можно было бы сгенерировать метод тестирования с возможностью многократного ис- пользования применительно к некоторому числу методов и к некоторому числу классов. Этот метод позволяет тестировщику тестировать каждую позицию на иг- ровом поле игры крестики-нолики. Поскольку такой метод тестирования написан с использованием API-интерфейса, ориентированного на Java, его можно вырезать и вставить в другие тестовые классы, выполнив лишь легкую модификацию. Про- ектный шаблон с механизмом изменений с возможностью запрета применяется в JavaBeans очень часто. По существу, он многократно используется в одном види- мом bean-компоненте. Тестовый шаблон, представленный на рис. 10.4, может многократно применяться в одном и том же тестовом классе. Структуры Большое количество проектов по разработке программного обеспечения, кото- рые применяют объектно-ориентированные технологии, в качестве базы некото- рой части их систем используют структуры (frameworks) приложений. Например, MFC (Microsoft Foundation Classes) представляет собой структуру для программ с оконным интерфейсом для PC-совместимых компьютеров. Эта структура состав- ляет основу для архитектуры интерактивных визуальных программ и библиотеки классов, которая поддерживает разработку приложения [Pross99J. В версии C++ игры «Кирпичики» мы использовали две таких структуры. Классы MFC были выбраны в качестве базовой реализации части, касающейся графического интер- фейса. Мы также воспользовались собственной игровой структурой как базовой для реализации игровых аспектов приложения «Кирпичики». В этом разделе рассматривается несколько вопросов. Окончательный вариант приложения, в основу которого положена некоторая структура, должен быть под- вергнут тестированию. Классы, содержащиеся в библиотеках структуры, должны подвергаться тестированию по мере их создания. Мы также проведем исследова- ние различий при тестировании приложений, построенных на базе структуры, приобретенной у стороннего производителя программного обеспечения, и постро- енных на базе структур собственного изготовления.
Глава 10. Компоненты, каркасы и линии продуктов Задача Разработчик принял решение реализовать изменение атрибута как измвнвнив с возмож- ностью запрета. В рамках этого проектного шаблона объект, присоединяющий атрибут, который может быть изменен в результате соответствующего пользовательского дей- ствия, поддерживает список прослушивающих объектов, которые затребовали право оотановить изменение, прежде чем оно будет сделано. Непосредственно перед тем, как атрибут, о котором идет речь, должен быть подвергнут изменению, объект, осуществля- ющий изменение, просматривает список прослушивающих объектов и отпрввляет сооб- щение fireVetoableChange() кеждому прослушивающему объекту и в большинстве слу- чаев отправляет прослушиввющвму объекту предлвгвемов новое знечение атрибута. Если какой-либо из прослушивающих объектов жвлевт запретить (т.е. “наложить вето" - отсюда и “vetoable1'] првдлагввмов изменение, он генерирует исключительную ситуецию PropertyVetoException и изменение не производится. В противном случае, измвнв- нив вступает в силу, после чего объект, его осуществляющий, выдает сообщение PropertyChenge (изменение свойства] каждому прослушивающему объекту, уведомляя их о том, что изменение имело место. Контекст Тестировщик рвзрвбвтывввт программное обеспечение, првднвзнечвннов для тестирова- ния приложения, в котором используется шаблон изменений с возможностью запрета. Такой шаблон предусматривевт примвнвнив, по меньшвй мера, четырех резличных клас- сов: класс, осуществляющий изменения, прослушивающие клвссы (прослушивающие объекты могут быть порождены из любого класса), класс VetoableChangeSupport и класс PropertyChangeListener. Этот шеблон преследует следующие цели: 1. Простота обслуживания множества заинтересованных субъектов. 2. Опрос только твх, кто требует внесения изменений (список прослушивеющих объек- тов просматривается последоветельно до тех пор, пока нв будет обнаружен объект, зепрещающий првдлагввмое изменение). 3. Дабы не усложнять прогремму приложения, для рвелизации запрета используется механизм генерации исключений. Процедуре изменения значения атрибута представляет собой алгоритм, состоящий из трех фаз: 1. Просматриваемый список для последовательного опроса кеждого зарегистрирован- ного в нам слушетеля. 2. Зарегистрированный в списка слушатель решает, соглашаться ли на изменение. 3. Зарегистрированный слушетель либо не предпринимает никаких действий, либо сбрасывает исключение, зепрещающее изменение. Влияющие факторы Количество заинтересованных сторон - Если существует только один субъект, заинте- ресованный в предлагаемом изменении, используется прямая связь, но не механизм изменений с возможностью запрете. Повторное использоввние тестового программного обеспечения - Шаблон изменений с возможностью запрета может применяться в одном компоненте многократно. Использо- венив тестового прогреммного обеспечения в каждом из этих экземпляров должно быть простым и удобным. Приспосабливаемость к различным реализациям - Как и в случев любого другого шеблона, возможны резличные реализации. Тестовый шаблон должен обледать максимальной способностью к адаптации. Использование методов отрежения позволяет во многих случеях сделеть тесты независимыми от конкретных имен тестов и даже от списков параметров. Рисунок 10.4. Тестовый шаблон с механизмом изменений с возможностью запрета
Глава 10. Компоненты, каркасы и линии лродуктоа Решение Решение определяет набор тестов, в основу которых положвнв структура тестового швблонв. Дивгрвммв действий этих швблонов, првдставлвннвя нв рис. 10.2, пред- лагает следующие пути для покрытия: 1. В ситуации, когдв нвт объектов, запрвщвющих измвнвнив, опрвдвлитв, что эти изменения выполнены успешно. Ситувция варьируется за счет изменения коли- честве зарегистрированных слушателей. 2. В ситуации, когда некоторый объект звпрвщавт измвнвнив, проверьте, было ли выполнено измвнвнив. В этом случвв возможны три вариации. Начните с ситуации, когда в список включены, ло мвньшвй мврв, три объекта: (А] измвнвнив запрвщввт лврвый объект в списке; (В) измвнвнив звпрвщввт квкой-то внутренний объект (ни первый, ни последний]; (С) измвнвнив запрещает последний объект. 3. Нв зврвгистрирован ни один из объектов, который должен быть уввдомлвн об изменении. Для поддержки соответствующих тестовых случввв РАСТ-класс регистрирует уведом- ления об изменениях. Его интерфейс содержит методы, необходимые для приема уведомления о грядущем изменении и уведомления о фактических изменениях. Пврввя строке программного коде, выделенного курсивом нв рис. 10.7, отображввт создание тестового прослушиввющвго объекте. Вторая выделенная курсивом строка программ- ного кода на рис. 10.7 служит иллюстрацией операции регистрации, осуществляемой тестовым прослушивающим объектом. Отдельные тестовые случаи будут либо при- нимать, либо отавргвть предложенные изменения в зависимости от нвзнвчвния конкретного твста. В каждом из двух первых путей соответствующий тестовый пример должен подтвердить, что изманвнив выполнено в полном объема (или вообще нв выполнено). Пример Шаблон изменения с возможностью звпрвта был задействован при рввлизации метода setmove(). Полный программный код твств можно выгрузить из Web-свйта. Нв рис. 10.7 показан фрагмент этого кода. Итоговые влияющие факторы После лримвнвния тестового шаблона изменений с возможностью запрете, влияющие факторы были уточнены и предстали а следующем виде: Количество звинтврвсоввнных субъектов - Был разработан стандартный нвбор те- стовых случаев, который изменяет количество заинтересованных субъектов. Этот фвктор успешно рвзрвшвн. Повторное использование тестового прогрвммного обеспечения - Был разработан класс PropertyChengeTeatListener, независимый от тестируемого объекта (OUT). Он можвт повторно использоваться в любом другом приложении этого тестового шаб- лона. Класс GemeBoardTeater специфичен, хотя он и применяет отражения, бла- годаря чему некоторые из вго действий нв зависят от тестируемого объекта. Этот фвктор разрешен частично. Приспосабливавмость к различным реализациям - Использование отражений и индивидуальных методов для каждого тестового случая упрощает адаптацию тесто- вого программного обеспечения при изменениях тестируемого объекте. Существует прямая зваисимость между изменениями классов и изменениями тестовых классов. Этот фвктор рвзрвшвн частично. РисуНОК 10.4. Тестовый шаблон с механизмом изменений с возможностью запрета (Продолжение)
Глава 10. Компоненты, каркасы и линии продуктов Рисунок 10.5, Диаграмма классов тестового шаблона с механизмом изменении с возможностью запрета Веап-компонент Класс архитектуры Рисунок 10.6. Диаграмма действий для тестового шаблона с механизмом изменений с возможностью запрета
Главе 10. Компоненты, квркасы и линии продуктов protected boolean setMoveTest(int pos) { boolean results; try { PropertyChangeTestListener proper tyChangeTest = new PropertychangeTestListener () ; ((GameBoard)OUT).addPropertyChangeListener( propertyChangeTest); Class parameterTypes[] - { Integer.TYPE }; Object parameters [] = { new Integer (pos) ); invokePrivateMethod( OUT, // объект, для которого вызывается метод "GameBoard", // тип объекта "setMove", // имя метода parameterTypes, parameters ) ; logTestResult( "setMove: invokePrivateMethod( setMove )", true ) ; boolean resultl - propertyChangeTest.isPropertyChangeReceived(); logTestResult( "setMove: isPropertyChangeReceived()", resultl ) ; if ( resultl ) { PropertyChangeEvent pee = propertyChangeTes t.getLastPropertyChangeEvent(); Move newMove = (Move)pee.getNewValue(); Systern.out.printin(newMove.newPosition) ; } results = resultl; ) catch ( Exception e ) { showFieldValues(OUT, "GameBoard"); e .printStackTrace () ; results = false; } Pl/ICyHOK 10.7. Иллюстративный фрагмент тестового случая на Java
Глава 10. Компоненты, каркасы и линии продуктов План тестирования компонентов | Имя компонента GameBoad Идентификационный номер TBS Разработчик (и): D.Sykes.J.McGregor Тестировщик(и): D.Sykes, J.McGregor Назначение данного компонента Предоставить класс, при помощи которого можно построить игровое пола для любой игры, требующей прямоугольной панвли. Требования целенаправленной проверки Поскольку этот компонент принадлежит к категории критических, 100% слу- чаев использования, которые затрагиаают этот компонант, будут задайствоааны в качестве твстов. Когда будет создано новое игровое поле, эти тестовые случаи будут задействоваться при испытаниях нового компонента в контакств случаев использования. Построение и сохранение тестовых наборов Тестовые наборы будут разрабатываться в соответствии со стандартами про- актирования. Класс GameBoardTestar должен содержать тестовый драйвер и тестовое меню. В этом класса должны быть предусмотрены операции, обес- печивающие выполнение функциональных, структурных, а также тестовых слу- чаев для проверки взаимодайстаий. Данные отчетов должны соответствовать проектным стандартам. Тестовые случен, ориентировенные на спецификацию Выполнить тестовые случаи для каждого постусловия каждого метода. Кроме того, проверить инварианты классов как часть каждого тестового случая. Тестовые случаи, ориентированные на реализацию Выполнить тестовые случаи, которые покрывают квждую строку программного кода в каждом методе. Критерии анализа риска, сопряженного с тестируемым компонентом: . Тестовые случаи, предназначенные для тестирования взаимодействия и функционирования компонентов Выполните тестовые случаи, основанные на квждом контракте между компо- нентами. Воспользуйтесь матрицами OATS для выбора тестовых случаев для прогона. Тестовые случаи, ориентированные не состояния Выполните тестовые случаи, покрывающие каждый переход в диаграмме со- стояний. РисуНОК 10.8. План тестирования компонента применительно к компоненту GameBoard
Глава 10. Компоненты, каркасы и линии продуктов Основные проблемы Структуры создаются таким образом, чтобы они были частично завершенными приложениями. Разработчики стремятся к тому, чтобы их структуры служили в качестве базового проекта и реализации для нескольких приложений — наборов программ, которые имеют нечто общее в смысле поведения. Подобный подход дает большую отдачу касательно инвестиций, нежели разовые системы (one-off systems). Идея структуры, по всей видимости, на текущий момент представляет собой наиболее удачный подход к решению проблемы многократного использова- ния, однако, как будет показано ниже, принцип линии продуктов расширяет воз- можности для многократного использования. Структура привносит три специфических составляющих: архитектуру про- граммного обеспечения, основную управляющую логику приложения и библиоте- ку готовых к использованию компонентов. Те места, в которых разработчик при- ложения может включить специфический для решаемой проблемы программный код, называются горячими точками (hot spots). Структуры функционируют по принципу «не вызывайте нас, мы сами вас вызовем». В заданной горячей точке разработчик приложения подключает программу, которая будет вызвана, когда программный код структуры посчитает это целесообразным. В структуре имеется спецификация, требования которой должны быть удовлетворены, прежде чем компоненты будут подключены в заданной горячей точке. Конкретная структура обычно должна быть связана со специфической пред- метной областью или специфической подсистемой, характерной для приложения. Классы MFC, как отмечалось ранее, связаны преимущественно с подсистемой пользовательского интерфейса приложения. Мы принимали участие в создании и использовании структур, ориентированных на специфику предметной области, для индустрии телекоммуникаций, и в построении систем наземной поддержки спутников, выведенных на околоземные орбиты. Процесс тестирования структур Проекты, в которых используются структуры, могут потребовать, ни много, ни мало, выполнения трех видов тестирования. В конечном итоге, программисты должны выполнять тестирование программного продукта, который они создают. Процесс тестирования приложения, разработанного на базе структур, во многом похож на тестирование любого объектно-ориентированного приложения, разра- ботка которого выполнялась в итеративном режиме с упором на повторное ис- пользование. Мы уже описывали процесс тестирования в главе 3, так что повто- ряться здесь не будем. Основное отличие процесса тестирования определяется степенью абстракции классов в структуре. Классы, составляющие структуру, тестируются по мере их разработки. Подход, предусматривающий использование архитектуры РАСТ и описанный в главе 7, может применяться для поддержки многократного использо- вания тестовых случаев; однако при этом существует предел, насколько могут быть специфичными тесты.
Глава 10. Компоненты, каркасы и пинии продуктов Для того чтобы провести тестирование структуры, которая была построена в вашей организации, одним из этапов процесса тестирования должно быть Постро- ение примера приложение с использованием структуры. В идеальном случае множе- ство примеров должно быть достаточно полным, чтобы можно было воспользо- ваться всеми горячими точками. Мы использовали различные версии структуры разрабатываемой нами игры для построения некоторого числа приложений в тече- ние нескольких лет. Тем не менее, следует отметить, что это довольно-таки доро- гостоящий процесс. Одно из средств, которым мы пользовались (а это надо делать на ранних стадиях жизненного цикла структуры) была целенаправленная провер- ка. В следующем разделе мы рассмотрим типы тестовых случаев, применяемых в рамках целенаправленной проверки, и некоторые критерии целенаправленной проверки, свойственные приложениям, построенным на базе структуры. Тот же подход можно задействовать при проведении приемочных испытаний структуры, которую конкретная организация намерена приобрести. Один из воп- росов, для выяснения которого можно использовать тестирование — обладает ли структура гибкостью, позволяющей внести изменения, необходимые для предло- женных (или намеченных) приложений. Второй вопрос подобного рода — облада- ют ли отдельные компоненты, образующие структуру, необходимым уровнем ка- чества. Эту задачу можно решить, воспользовавшись набором тестовых случаев, построенным с таким расчетом, чтобы покрыть точки, в которых требуется дости- жение особой гибкости. Проверка структуры Поскольку структура представляет собой незавершенное приложение, все вы- воды, к которым мы пришли ранее при обсуждении тестовых случаев целенаправ- ленной проверки, остаются в силе. К ним мы добавим новые критерии, характер- ные для структур. Полнота Спецификация структуры должна обеспечить полное описание области дей- ствия этой структуры. Иначе говоря, мы должны иметь возможность взять некото- рый набор случаев использования и определить, попадает ли приложение, описы- ваемое этими случаями использования, в область действия рассматриваемой структуры. В смысле полноты структура должна быть способной пройти те же виды про- верки, которые способно пройти любое приложение. Должна обеспечиваться воз- можность наблюдения за тем, как выполняется каждый случай использования приложения. Различие заключается в том, что в случае использования структуры, задачи конкретного случая использования могут быть решены функциональными сред- ствами, получаемыми через горячие точки.
Главе 10. Компоненты, каркасы и линии продуктов Непротиворечивость Спецификации горячих точек должны содержать достаточную информацию, дабы разработчик приложения знал, когда излишняя специализация одной горя- чей точки противоречит способу расширения другой горячей точки. То есть, бы- вают моменты, когда горячие точки, образующие некоторый набор, находятся между собой в определенных отношениях, и в документации на эти горячие точ- ки должны быть точно указаны любые ограничения, которым эти горячие точки подчиняются. Корректность Корректность структуры и приложение находятся на разных уровнях абстрак- ции. Корректность на этом уровне означает, что соответствующая информация передается из структуры программным кодам, отражающим специфику приложе- ния, и что соответствующие типы информации передаются в структуру из прило- жения. Специфичность Структура может оказаться не в меру специфичной. Другими словами, специ- фичность может ограничить использование структуры некоторым набором воз- можных приложений, который оказывается слишком узким. Она может также за- фиксировать значения некоторых атрибутов, которые должны оставаться переменными. В тестовый набор целенаправленной проверки должны быть вклю- чены следующие тестовые случаи: 1. При наличии набора случаев использования для каждого из запланирован- ных программных продуктов проведите исследование каждой горячей точки и определите, до какой степени приложение расширит структуру в плане достижения требуемого уровня функциональных возможностей. Отметьте места, в которых структура не обеспечивает необходимой гибкости и в ко- торых возникает потребность в модификации. 2. Обратитесь к независимым экспертам в предметной области (из числа тех, которые не брали участия в проектировании структуры) с просьбой провес- ти анализ изменчивости. Они должны составить список атрибутов, который меняется от одного приложения к другому. Группа, проводящая проверку, сравнивает эту информацию со списком горячих точек, чтобы определить, можно ли менять значение этих атрибутов. Структурирование тестовых случаев с целью поддержки структуры Обычно считается, что разработчик приложения будет использовать структуру путем наследования от образующих структуру классов. Подход, предусматриваю- щий использование архитектуры РАСТ (см. главу 7), служит дополнением этого со стороны тестирования. Коллектив разработчиков структуры выполняет постро- ение тестовых случаев, используя подход РАСТ. Разработчики приложения теперь
Глава 10. Компоненты, каркасы и линии продуктов получают возможность расширять эти классы тестовых случаев за счет построения собственных тестовых случаев. В результате получаем снижение трудозатрат на построение тестовых случаев для приложения или, за счет правильного использо- вания сэкономленных трудозатрат, лучшее тестовое покрытие. Линии продуктов В то время как компании ведут поиск методов, которые могли бы способство- вать увеличению производства программного обеспечения, подход, предусматри- вающий разработку лини программных продуктов, позволил некоторым произво- дителям программных продуктов добиться поразительного увеличения производительности [DonoOO]. Институт SEI (Software Engineering Institute — Ин- ститут технологии создания программного обеспечения) играет в этой области ве- дущую роль. Благодаря его усилиям было опубликовано множество трудов по этой теме [NoClOO]. Эти успехи были достигнуты благодаря тому, что работа ве- лась сразу в трех направлениях: в направлении организационной стратегии, так- тики административного управления и методам проектирования программного обеспечения. Далее мы рассмотрим каждое из этих направлений, однако основное внимание уделим уровню проектирования программного обеспечения. Прежде чем мы сделаем это, нам потребуется дать определения нескольких понятий. Линия программного продукта (software product line) представляет собой множество программных продуктов, которые обладают некоторым набором общих свойств благодаря тому, что они могут быть построены из наборов общих про- граммных фрагментов. Если вы полагаете, что это еще одна попытка решить про- блему многократного использования, то вы недалеки от истины. Тем не менее, это наиболее всесторонний подход, с которым нам приходилось сталкиваться до сих пор, и к тому же наиболее успешный. Он начинается на организационном уровне, на котором определяется набор программных продуктов. Во время определения программных продуктов выделяется набор точек вариации (variation points), кото- рые в первом приближении соответствуют горячим точкам структуры. Этими точ- ками являются области функциональных возможностей, которые изменяются от продукта к продукту вдоль всей линии продуктов. Подход, в основу которого положена линия продуктов, использует для постро- ения отдельных программных продуктов то, что известно как базовые свойства. Наиболее важным базовым свойством группы, образующих линию продуктов, яв- ляется архитектура линии продуктов. Такая архитектура представляет собой скелет, к которому прикрепляются компоненты, образуя в результате приложение. В опи- сание архитектуры указаны точки, в которых допускаются вариации. Эта архитек- тура описывает также набор атрибутов, которые продукту по идее удается достичь. В число таких атрибутов входят заданные уровни производительности, свойства системы безопасности и ожидаемая степень расширяемости. Компании, которые с максимальной продуктивностью могут использовать под- ход на базе линии продуктов, суть те организации, которые смогли подняться над Уровнем 1 (Level 1), определенным моделью СММ (Capability Maturity Model -
Глава 10. Компоненты, каркасы и линии продуктов модель развития функциональных возможностей) для программного обеспечения (SW-CMM)1 [SEI93J. Этот подход предусматривает координацию процессов, вы- полняемых некоторым числом групп разработчиков, и между уровнем линии про- дуктов и уровнем индивидуальных продуктов. Он также предусматривает слож- ный процесс планирования, который строит базу накопленных данных и обеспечивает принятие решений на основе имеющейся информации. Тестирование на уровне организационного управления Компания, которая принимает подход, основанный на линии продуктов, орга- низует свои ресурсы так, чтобы поддерживать перемещение информации и свойств между многочисленными процессами разработки программных продук- тов. Для наших целей свойства тестирования на этом уровне включают стратегию тестирования линии продуктов и генеральный план тестирования. В плане тести- рования определена организация групп, ответственных за различные виды тести- рования. Цель этой организации состоит в том, чтобы достичь той же степени многократного использования в тестировании, которой удалось добиться при раз- работке. Тестирование на уровне технического управления Организация разработки линии программных продуктов принимает тактичес- кие решения в отношении методов, посредством которых они будут осуществлять организационную стратегию. В число этих решений входит определение уровней тестового покрытия, необходимого для достижения желаемого уровня качества, и методы организации тестового программного обеспечения и тестовых случаев. В их число также входит определение процесса тестирования, который интегрирует- ся в программное обеспечение процесса разработки. Тестирование на уровне проектирования программного обеспечения Организация разработки линии программных продуктов реализует тактические решения в двух группах: в группе разработки линии продуктов, вырабатывающей базовые свойства, и в отдельных производственных группах, ответственных за базовые свойства. Группа линии продуктов осуществляет построение структуры программного продукта и набора компонентов, которые могут быть скомпонованы на этой структуре. Группы, разрабатывающие отдельные программные продукты, создают дополнительные компоненты, необходимые для конкретных программ- ных продуктов; однако, эти компоненты можно вводить только в заранее опреде- ленных точках вариации. Сопоставляя подходы, описанные в предыдущих главах, вы убедитесь в том, что обязанности в отношении тестирования назначаются так, как показано на рис. 10.9. 1 Модель развития функциональных возможностей является зарегистрированной торговой маркой ин- ститута SEI. Институт определил модель СММ как шкалу измерений, относительно которой изме- ряется уровень развития той или иной организации. Эта шкала определяет пять уровней развития, причем Уровень 1 характеризует наименее развитые организации.
Глава 10. Компоненты, каркасы и линии продуктов Целенаправленная проверка Группа линии продуктов Тестирование модулей Соамвстная ответстванность Комплексные испытания Группа программных продуктов Систамноа тестирование Группа программных продуктов РисуНОК 9.10. Обязанности при тестировании проектов по созданию линии продуктов Тестирование в рамках проекта линии продуктов Базовый процесс тестирования в рамках проекта линии продуктов во многом сходен с процессом, который был описан в данном разделе. Тестовые свойства зарождаются в группе разработки линии продуктов и проектируются таким обра- зом, что допускают многократное использование в рамках множества проектов создания программных продуктов. Существует организационная структура, цель которой состоит в том, чтобы упростить реализацию этого подхода путем ведения планирования, составления отчетов, а также поддержки линий связи и доставки программного обеспечения в рамках организации линии продуктов. Архитектура линии продуктов тщательно проверяется при помощи метода целенаправленной проверки. Эта архитектура проверяется на наличие свойств, выходящих за пределы стандартных свойств полноты, корректности и непротиво- речивости. В зависимости от предметной области, может потребоваться оценка таких качеств архитектуры, как производительности и безопасность. В главе 4 были даны описания методов проверки архитектур программных продуктов. Пу- тем использования таких исполняемых представлений, как Rapide, или таких ин- струментальных средств, как ObjectTime, создаются рабочие модели соответствую- щей архитектуры; эти модели подвергаются проверке, после чего они могут послужить основой для реализации. Архитектура линии продуктов будет исполь- зоваться для разработки нескольких продуктов, благодаря чему экономически це- лесообразно затратить определенные ресурсы на тщательную проверку упомяну- той архитектуры. Архитектура определяет (в частности, в точках вариации) интерфейсы, описы- вающие службы, которые ожидаются в таких точках. Группа линии продуктов получает возможность определять стандартные наборы тестовых случаев, основан- ных на таких интерфейсах. Группа линии продуктов создает базовые классы ар- хитектуры РАСТ для компонентов, построенных на этом уровне. Когда конкрет- ная группа программного продукта создает замещающий компонент для нового варианта, в ее обязанности входит создание новых классы архитектуры РАСТ на основе существующих базовых классов. Несмотря на то что аттестация программного продукта входит в обязанности конкретных групп программных продуктов, важный вклад в этот процесс вносит группа линии продуктов. Специальные требования к программному продукту, разрабатываемому в качестве составной части линии продуктов, произрастают из основных требований, сформулированных на уровне линии продуктов. Даже в
400 Глава 10. Компоненты, каркасы и линии продуктов процессе тестирования на уровне интерфейса GUI возможна иерархическая орга- низация проекта линии продукта. Языки написания сценариев некоторых средств тестирования на уровне GUI обеспечивают возможность установки отношений наследования между тестовыми сценариями. Мы построили иерархию специализации тестовых сценариев для выполнения средством тестирования, построенным на базе интерфейса GUI, с использованием сценарных языков. Более общие уровни иерархии могут совмес- тно использоваться многими группами программных продуктов, которые строят нижние уровни иерархии, свойственные их конкретным продуктам. Мы также воспользовались этим методом для установки параллельных отношений extends между случаями использования. Дальнейшие перспективы Подход, основанный на линии продуктов, является сравнительно новым ша- гом в эволюции производства программного обеспечения. Мы очень надеемся, что большое внимание, уделяемое в условиях этого похода процессу, приведет к тому, что на ранних стадиях разработки программных продуктов больше внима- ния будет уделяться таким оценкам, как целенаправленная проверка. В совокуп- ности с эффективными средствами сопровождения тестовых случаев, все это при- ведет к существенному повышения качества программных продуктов. Резюме Идеи, рассмотренные в данной главе, формируют мощный фундаментальный подход к проектированию программных продуктов, который стимулирует ускоре- ние разработки высококачественного программного обеспечения. Эти подходы были использованы в той или иной форме некоторыми из наших заказчиков в течение ряда лет. По мере того как «производственные умонастроения» будут крепнуть в области создания программного обеспечения, а компании будут под- ниматься вверх по шкале СММ, подход, основанный на линии продуктов, и чет- ко определенный процесс изготовления программных компонентов будет прини- маться все большим количеством компаний. Упражнения 10-1 Завершив создание игры «Кирпичики», мы решили построить другие игры, приняв за их основу ту же базовую архитектуру, использующую подход, основанный на линии продуктов. Еще раз прочтите материал, содержащийся в главах 5, 6 и 7 и касающийся тестирования классов. Опишите, как тесты, определенные в этих главах, могут быть приспособлены для работы в условиях линии продуктов. 10-2 Проанализируйте свой текущий проект. Если в нем используется структура, то как вы проводили тестирование, чтобы определить, что выбранная вами структура соответствует проекту в полном объеме? Насколько изменился ваш план тестиро- вания в связи с выбором структуры? 10-3 Опишите тесты, которые вам до сих пор не удалось выполнить, но которые вы хотели бы выполнить. Сколько из этих тестов вы смогли бы выполнить, если бы могли распределить свои усилия на некоторое количество продуктов?
Глава 11 Заключение Мы, наконец, добрались до конца книги. В ней было рассмотрено множество вопросов, а в некоторых случаях были представлены различные способы решения одних и тех же задач. В этой главе нам хотелось бы собрать воедино отдельные нити наших рассуждений и определить ряд всеобъемлющих перспек- тив. Это будет предложено в виде последовательности рекомендаций. Предложения Организация и процесс Создайте тестирующую организацию, состоящую из двух уровней. В обязан- ности первого уровня входит упрощение тестирования на нижнем уровне, т.е. в среде разработчиков. Эта группа выполняет построение для разработчиков высо- коуровневых классов Tester и других базовых тестовых средств многократного использования. Члены этой организации должны уметь программировать и, по всей видимости, в их обязанности должно входить распределение заданий меж- ду коллективом разработчиков и персоналом, выполняющим тестирование про- екта. Второй уровень поддерживает операции тестирования в масштабах целой си- стемы. Эта группа взаимодействует с группой разработчиков на всех стадиях процесса проектирования. Они пишут тестовые случаи на ранних стадиях про- екта с целью выявления требований, которые не поддаются тестированию. Они принимают участие в сеансах целенаправленной проверки и в конечном итоге проводят тестирование приложений как завершенных программных продуктов. Начните организационные преобразования с совместного тестирования базовых свойств, которые разрабатывались совместно с другими сотрудниками вашей орга- низации. При обсуждении принимаемых решений на совещаниях по проекту вы- ясните, как это повлияет на возможности коллектива проводить тестирование. Направьте свои усилия на повышение роли тестирования, облекая эту мысль в позитивную форму: «Посмотрите, от какого множества дефектов нам удалось избавить заказчиков». 401
Глава 11. Заключение Составьте подробное описание процесса для каждого из этих уровней. Вос- пользуйтесь для этой цели описанием, которое мы предложили в начале книги (См. раздел «Процесс тестирования»). Приспособьте их в соответствие с уров- нем развития вашей организации и с типом системы. Данные Осуществляйте сбор данных на протяжении времени жизни проекта и исполь- зуйте их как основу для принятия решений. Фразы наподобие «Я полагаю ...» или «Мне кажется ...» никак не могут служить основанием для принятия коммерчес- ких или технических решений. «Этот метод лучше, поскольку он увеличивает отношение дефект/час» — гораздо более предпочтительная стратегия. Даже ука- зать приблизительное число лучше, чем не указывать ничего. В таблице на рис. 11.1 представлен список некоторых показателей тестирования и упомянуты мес- та в тексте книги, в которых они рассматриваются. Включите в работу механизм регистрации в классе Tester, который способен упростить задачу числовой оценки обнаружения дефектов. Выберите стандартную терминологию и символы, употребляемые при регистрации. Помните, что тесто- вый случай, результатом прогона которого должно стать состояние ошибки и ге- нерация исключения, завершается успешно, если возникает ошибка, и неудачно в противном случае. Стандарты Применяйте стандарты в качестве отправной точки для любых программных продуктов, которые будут отправляться в архив для дальнейшего использования. Даже неофициальные стандарты прошли стадию совершенствования благодаря обсуждению в среде их сторонников и ранних приверженцев, и это обеспечива- ет более широкие перспективы, чем если бы над проектом трудилось всего лишь несколько разработчиков. Убедитесь в том, что стандарты, выбранные для тести- рования, совместимы со стандартами, выбранными для разработки. Определите набор шаблонов для тестирования рабочих программных продук- тов. Это позволяет уменьшить затраты усилий на разработку этих продуктов. Выберите эти шаблоны в качестве стандартов, действующих в масштабах всей организации. Планы тестирования, тестовые случаи и отчетность Используйте стандарты IEEE [IEEE87], как это делалось ранее в отношении плана тестирования, в качестве отправной точки для создания собственных планов тестирования или для форматирования тестовых случаев. Эти планы придется приспособить под свою предметную область и тип используемой системы, одна- ко проще удалить или модифицировать существующий модуль, недели создавать модуль с самого начала. Уточните применяемые форматы на базе опыта, полу- ченного при работе с ними. Чтобы сделать это:
Главе 11. Заключение 1. Соберите данные об отклонениях от планов. 2. Воспользуйтесь отчетами о тестировании для сбора данных о диапазонах ак- тивности дефектов, надежности и количестве дефектов на тысячу строк про- граммного кода. 1. Суммарное количество дефектов по типвм - Каждый вид деятельности, связанной с тастированивм, должен вести подсчет и клвссификвцию дефектов, которыа он обнвруживввт. Каждый вид деятельности должен рвгистрироввть стадию разработ- ки, на которой конкретный дефект был обнаружен. 2. Диапазон активности дефекте - Этот поквзатвпь позволяет выполнять дальнейший анализ данных о дефекте. Проводится изучение квждого дефекте, в аго появланив соотносится со стадией рвзрвботки. Диапазон активности дефекта — это набор стадий с момента, когде дефект зародился, до момента, когде он был обнаружен. 3. Время действия двфектов/врвмя тестирования в часах - Этот показвтвль эффек- тивности может использоваться в квчвствв критерия останове с целью определе- ния, когдв продолжение твстироввния становится нерентабельным. Нвчвльныв ита- рвции того или иного комплектующего модуля прекращают тестированиа при значениях этого показателя больших, нежели во время последующих итераций. Это можно объяснить твм фактом, что испрввлвнив одного дефекте влечет исправле- ние ряда других, в связи с чем на начальных итерациях поиск дефектов можно проводить на так усердно, как на последующих итарвциях. 4. Показатели покрытия - Мы обсудили различные способы изменения тестового покрытия. В каждом плана тестироввния должен обязательно указываться нама- чанный уровень (или уровни] покрытия. В акте об испытаниях приводится реально достигнутый уровень покрытия. Нижа перечисляются ссылки на некоторые разде- лы, посвященные методам измерения покрытий: • Какие объемы твстироввния следует считать вдвкввтными? • Система показателей твстироввния • Покрытие в моделях • Адекватность тестовых наборов, предназначенных для тестироввния класса • Измераниа тестового покрытия Рисунок 11.1. Показатели уровня тестирования Требования Создайте собственный стандарт шаблона случая использования путем модифи- кации нашего шаблона и шаблона, который можно получить из Web-сайта Алесте- ра Кокбурна (Alastair Cockburn) [СоскОО]. Запишите тестовые сценарии целе- направленной проверки на как можно более ранней стадии цикла разработки. Это позволяет уже на ранних стадиях выявить нечетко сформулированные тре- бования. Категории дефектов Используйте широко распространенные неофициальные стандарты, такие как, например, ODC (Orthogonal Defect Classification — ортогональная классификация
404 Глава 11. Заключение дефектов), в основе которой лежит большие массивы данных, накопленных корпо- рацией IBM. Такие классификации могут послужить базой для пересмотра раз- рабатываемых тестовых случаев и стратегий тестовых случаев. Выше были при- ведены списки некоторых из них вместе с методикой их использования (см. разделы «Ортогональная классификация дефектов как средство отбора тестовых случаев» и «Классификация ODC»). Инфраструктура программного обеспечения Не жалейте ресурсов на развитие инфраструктуры с целью совершенствования средств тестирования. На построение хорошо спроектированных классов Tester и на эффективное использование архитектуры PACT (Parallel Architecture for Class Testing - Параллельная архитектура тестирования классов) даже у квалифициро- ванных разработчиков уходит много времени. Мы уже рассматривали тестовые окружения для C++ и Java, которые поддерживают различные виды тестирова- ния низкого уровня. Версии многих из них можно получить на упомянутом выше Web-сайте. Хотя для каждой такой версий требуются определенные ресур- сы на модификацию применительно к существующей среде, тем не менее, каж- дая из них позволит сэкономить на тестировании многие и многие человеко- часы. Воспользуйтесь бесплатными или недорогими инструментальными средствами, подобными, например, JUnit [Веск99]. Они хорошо взаимодействуют с классами архитектуры РАСТ и обеспечивают для них среду выполнения. Включите их в свой проект и применяйте для автоматизации рутинных операций процесса тес- тирования, благодаря чему появится возможность сэкономить время на выборе дьявольски сложных тестовых случаев. Технологии Мы предложили вам некоторое количество технологий, которые допускают применение на различных этапах процесса разработки. Будем считать их набо- ром инструментальных средств тестировщика. Применяйте целенаправленную проверку, начиная с первых моделей и вплоть до моделей, которые не требуют дальнейшей корректировки. Этому должно пред- шествовать тестирование требований, которые служат источником тестов, и мо- делей. По мере того, как эти требования становятся все более устойчивыми, все реже звучат, а то и полностью исчезают, сомнения в корректности тестов. В этих условиях ускоряется и упрощается применение целенаправленной провер- ки и оценка ее результатов. Используйте средства временной логики при составлении спецификаций, пре- дусматривающих параллельное выполнение. Это позволит разрабатывать про- граммное обеспечение с большей точностью и проводить его тестирование с большей тщательностью. В случаях, в которых синхронизация играет важную роль, убедитесь в том, что спецификация учитывает требования синхронизации.
Глава 11. Заключение Используйте анализ SYN-путей в процессе построения тестовых случаев, пред- назначенных для покрытия возможных дефектов синхронизации. Выделите крити- ческие точки, в которых два потока осуществляют одновременный доступ к одной и той же информации о состоянии. Постройте тестовые случаи, отслежи- вающих путь вдоль каждого потока до других точек синхронизации в программ- ном коде. Применяйте HIT-тестирование (Hierarchical Increment Testing — иерархичес- кое инкрементальное тестирование) и матрицы OATS (Orthogonal Array Testing System — система тестирования с использованием ортогональной матрицы) в тех случаях, когда в распоряжении тестировщиков имеется больше тестовых случаев, чем ресурсов, необходимых для их прогона. Воспользуйтесь Н1Т-тестированием, чтобы определить, какие из тестовых случаев, унаследованных от родительских классов архитектуры РАСТ, могут применяться к порожденному классу. Если они полностью автоматизированы, а машинное время не представляет собой проблемы, выполните прогон всех этих тестовых случаев! Если необходимый объем ресурсов увеличивается с увеличением количества выполняемых тестов, воспользуйтесь HIT-тестированием, чтобы избавиться от тестов, для которых ве- роятность обнаружения новых дефектов меньше, чем у остальных. Воспользуйтесь матрицами OATS для выбора требуемых тестовых случаев из числа возможных, но еще не написанных тестов. Выясните, на каких этапах про- ектирования разрабатываемое программное обеспечение приобретает высокий уровень гибкости. Используйте тестовые шаблоны, соответствующие специализированным экспе- риментальным проектным шаблонам в процессе разработки тестовых случаев при поддержке классов архитектуры РАСТ. В ситуациях, когда проектная документа- ция ссылается на специализированные проектные шаблоны, определите, суще- ствуют ли соответствующие тестовые шаблоны. Если существуют, воспользуй- тесь ими для ускорения процесса проектирования тестовых случаев и программного обеспечения. Если такие тестовые шаблоны не существуют, напи- шите их и представьте на всеобщее ознакомление в стенах своей компании или на многочисленных совещаниях, посвященных обсуждению шаблонов. Риски С ролями тестирования в проекте связано некоторое количество рисков. Рас- смотрим некоторые из них и посмотрим, что можно сделать, дабы уменьшить их влияние: 1. Существует тенденция рассматривать тестирование как второстепенную про- блему по сравнению с разработкой, но не как равнозначную проблему. Этот риск должен быть уменьшен путем накапливания информации, доказываю- щей необходимость тестирования. 2. Тестировщики могут недооценить оптимальные объемы тестирования конк- ретного проекта, вследствие чего серьезные дефекты могут остаться необна-
Глава 11. Заключение руженными. Эта недооценка может обернуться большими проблемами как для руководящего персонала, так и для разработчиков. Продолжайте тести- рование, пока вам не скажут: «Все, хватит!». В то же время, накапливайте информацию с таким расчетом, чтобы вам были известны значения показа- теля затрат на дефект выполняемого тестирования. Применяйте технологии многократного использования и автоматизации, которые были описаны выше, с тем, чтобы эти затраты были минимально возможными. 3. Традиционные стратегии тестирования могут оказаться неэффективными при выявлении типов дефектов, которые возникают в распределенных объектных системах, допускающих перестройку собственной структуры. Этот тип риска уменьшается за счет внесения изменений в существующие стратегии с целью включения ряда других технологий, указанных в главе 8. Игра Мы использовали пример игры «Кирпичики» на протяжении большей части данной книги. Теперь мы хотели бы подвести итоги выполненному нами тести- рованию в виде перечня различных видов деятельности (см. рис. 11.2). Мы приводим список этих видов деятельности в виде последовательности, посколь- ку именно в такой форме на двухразмерном листе бумаги обеспечивается их наилучшее восприятие. Мы попытаемся также передать всю сложность взаимо- действий, которые возникают на множестве итераций со множеством комплек- тующих модулей. Виды деятельности при разработке Тестовая деятельность Ссылки Анализ требований Целенаправленная проверка, в рамках Глава 4. которой были использованы знания Раздел «Модель специалистов в предметной области и требований» заказчика (в данном случае это одно и то же лицо) с целью определить, является ли набор случаев использования полным, корректным и непротиворечивым. Анализ предметной области В рамках технологии целенаправленной Глава 4. проверки были использованы знания Раздел «Модель специалиста в предметной области и анализа предметной случаи использования высокого уровня области» (которые определяют область проекта) для определения, насколь правильно и полно специфицированы понятия. Помимо этого, такая технология позволила выполнить проверку того, что отношение между понятиями корректны, и ни одно из них не потеряно.
Глава 11. Заключение Анализ приложения Оценка модели анализа приложения давалась в сравнении с моделями предметной области и моделями требований. Сценарии, полученные на базе модели требований, направляли исследование классов, состояний и алгоритмов. Глава 4. Раздел «Модель анализа приложения» Проектирование архитектуры Архитектура была проверена на тех случаях использования, которые налагают ограничения на эту архитектуру. Мы провели тестирование нашей базовой архитектуры с целью убедиться в том, что она поддержит дальнейшее развитие игры с применением случаев изменения. Глава 4. Раздел ♦Архитектурная модель» Рабочий проект Эти два вида проектной деятельности тестируются совместно. Целенаправленная проверка этого материала концентрируется на потоке специфического алгоритма. Глава 4. Раздел «Подробная модель проекта класса» Реализация класса Этот вид тестирования проверяет, насколько реализация соответствует спецификации, а также проводит испытание безопасности путем тестирования, выходящего за пределы спецификации. В качестве иллюстрации доступного всестороннего тестирования был использован класс Velocity. Для поддержки тестирования класса Velocity был построен класс архитектуры РАСТ. Глава 5. Раздел ♦Тестирование классов» Взаимодействие объектов Классы архитектуры РАСТ были расширены, благодаря чему они обрели способность создавать многочисленные объекты и тестировать взаимодействия этих объектов друг с другом. В частности, основное внимание мы уделили динамическим эффектам полиморфизма. Матрицы OATS применялись для конфигурирования минимального набора тестовых случаев. Глава 6. Раздел «Тестирование взаимодействий объектов» Системное тестирование Были построены тестовые случаи. Началом этому процессу послужили сценарии целенаправленной проверки, затем были добавлены необходимые детали и специальные значения. Эти тесты выполнялись на программном продукте вручную. Глава 9. Раздел «Игра «Кирпичики» Рисунок 11.2. Краткая сводка поэтапного процесса тестирования
408 Главв 11. Заключение Резюме В этой книге мы предложили технологии тестирования для каждого этапа жизненного цикла разработки программного обеспечения. Мы использовали каждую из этих технологий во множестве реальных проектов. Мы не стремились уделять внимание каждой технологии и даже каждому виду тестирования, кото- рые достаточно детально описаны в литературе. Наш подход подчеркивает необходимость интенсивного тестирования на ран- них стадиях жизненного цикла. Этот подход объединяет процессы разработки и тестирования программного продукта с теми видами тестовой деятельности, ко- торые питают информацией как разработку, так и усилия по совершенствова- нию этих процессов. Накопленный нами опыт показал, что такой подход может принести солидный выигрыш как в достижении краткосрочных целей, так и в плане более широких возможностей в долгосрочной перспективе. Сообщите нам о собственном опыте обращения с описанными в книге техно- логиями. Дайте нам знать, что работает, а что нет. Мы хотим пожелать вам больших творческих успехов при тестировании программных продуктов!
Библиография [Веск89а] Kent Beck and Ward Cunningham. “A Laboratory for Teaching Object-Oriented Thinking.” SIGPLAN Notices. ACM 24: 10 (Oct. 1989). [Веск99] Kent Beck and Erich Gamma. “Test Infected: Programmers Love Writing Tests.” Available at http://members.pingnet.ch/ gamma/ Junit.htm (1 December 2000). [ Beiz90] Boris Beizer. Software Testing Techniques. New York: International Thomson Publishers, 1990. [ BetterStateOO] [Booch99] http://www.windriver.com/products/html/betterstate_ds.html Grady Booch, James Rumbaugh and Ivar Jacobson. The Unified Modeling Language User Guide. Boston, MA: Addison Wesley, 1999. [Brow77] P.J. Brown, ed. Software Portability. Cambridge, England: Cam- bridge University Press, 1977. [CaTa98] Richard H. Carver and Kuo-Chung Tai. “Use of Sequencing Constraints for Specification-based Testing of Concurrent Pro- grams.” IEEE Transactions on Software Engineering 24: 6 (June 1998). [Chill92] R. Chillarege, et al. “Orthogonal Defect Classification—-A Concept for In-Process Measurements.” IEEE Transactions on Software Engineering 18:(November 1992, pp. 943—956). [Chow87] Tsun Chow. “Testing Software Design Modeled by Finite-State Machines.” Transactions on Software Engineering SE-4 (1987). [CockOO] Alistair Cockbum. Use Case Template, http://members.aol.com/ acockbum/ (1 December 2000) [DonoOO] Donohoe, Patrick, ed. Software Product Lines: Experience and Research Directions. Norwell, MA: Kluwer Academic Publishers, 2000. [EcDe96] Earl F. Ecklund, Jr. and Lois M. L. Delcambre. “Change Cases: UseCases that Identify Future Requirements.” Proceedings of OOPSLA ’96. ACM (1996). 409
410 Библиография [EckeOO] [Faga86] [FoSc97] [GHJV94] [Gold89] [Hens96] [Hetz84] [IEEE87] [Java] [JCJO92] [Kazman94] [LiWi94] [Luckham95] [McGr96] [McGr97] Bruce Eckel. Thinking in C++ Volume 1: Introduction to Standard C++. Upper Saddle River, NJ: Prentice-Hall, 2000. M. Fagan. “Advances in Software Inspections.” IEEE Transactions on Software Engineering. 12: 7 (July 1986): 744—51. Martin Fowler, Kendall Scott (contributor), Grady Booch. UML Distilled: A Brief Guide to the Standard Object Modeling Language, second edition. Boston, MA: Addison-Wesley, 1999. Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Boston, MA: Addison-Wesley, 1994. Adele Goldberg. Smalltalk 80:The Language. Boston, MA: Addison- Wesley, 1989. Bill Hensler. Sex, Lies, and Video Games: How to Write a Macintosh Video Game. Boston, MA: Addison-Wesley, 1996. W.C. Hetzel. The Complete Guide to Software Testing. Wellesley, MA: QED Information Sciences, 1984. IEEE. IEEE Software Engineering Standards. Piscataway, NJ: IEEE Press, 1987. Sun Microsystems. Java Language Specification, http:// java.sun.com/j2se/L3 (1 December 2000) Ivar Jacobson, Magnus Christerson, Patrik Jonsson and Gunnar Hvergaard. Object-Oriented Software Engineering: A Use Case Driven Approach Boston, MA: Addison-Wesley, 1992. R. Kazman, L. Bass, G. Abowd and M. Webb. “SAAM: A Method for Analyzing the Properties Software Architectures.” Proceedings of the 16th International Conference on Software Engineering, pp. 81-90. Sorrento, Italy, May 1994. Barbara Liskov and Jeanette Wing. “A Behavioral Notion of Subtyping.” ACM Transactions on Programming Languages and Sys- tems 16: 6 (Nov. 1994): 1811-41. David C. Luckham, et al. Specification and Analysis of System Architecture Using Rapide. IEEE Transactions on Software Engineering, 21:4(April 1995). John D. McGregor, Jim Doble and Asha Keddy. “Let Architectural Reuse Guide Component Reuse.” Object Magazine (April 1996): 38—47. John D. McGregor. “The Parallel Architecture for Component Testing,” Journal of Object-Oriented Programming (May 1997).
Библиография [МеуеОО] [Меуе94] [MFC] [МоНаОО] [Niels94] [NoClOO] [OMG98] [Phadke89] [Pros99] [Redm97] [RJB98] [SEI93] [Selic94] [ShGa96] [Szyp98] [White97] [WK99] Bertrand Meyer. Eiffel: The Language. Upper Saddle River, NJ: Prentice-Hall, 2000. Bertrand Meyer. Object-Oriented Software Construction. Upper Saddle River, NJ: Prentice-Hall, 1994. Microsoft Corporation, ed. Mastering MFC Development Using Microsoft Visual C++. Seattle: Microsoft Press, 2000. Richard Monson-Haefel. Enterprise JavaBeans. Sebastopol, CA: O’Reilly & Associates, 2000. Jakob Nielsen. Usability Engineering. San Francisco: Morgan Kaufmann, Pub., 1994. Linda Northrop and Paul Clements. A Framework for Software Product Line Practice. Pittsburgh, PA: Software Engineering Institute, 2000. Object Management Group. The Common Object Request Broker: Architecture and Specification. Needham, MA: Object Management Group, 1998. M.S. Phadke. Quality Engineering Using Robust Design. Upper Saddle River, NJ: Prentice-Hall, 1989. Jeff Prosise. Programming Windows with MFC. Seattle: Microsoft Press, 1999. Frank E. Redmone III. DCOM: Microsoft Distributed Component Object Model. Foster City, CA: IDG Books Worldwide, 1997. James Rumbaugh, Ivar Jacobson, Grady Booch. The Unified Modeling Language Reference ManuaX. Reading, MA: Addison-Wes- ley, 1998. Software Engineering Institute. The Capability Maturity Model for Software, version 1.1, CMU/SEI-93-TR-024, Pittsburgh. Bran Selic, et al. Real-Time Object-Oriented Modeling. New York: Wiley & Sons, 1994. Mary Shaw and David Garlan. Software Architecture: Perspectives on an Emerging Discipline. Upper Saddle River, NJ: Prentice Hall, 1996. Clemens Szyperski. Component Software: Beyond Object-Oriented Programming. Boston, MA: Addison-Wesley, 1998. Barabara White. Using JavaBeans. Indianapolis: Que, 1997. Jos Warmer and Anneke Kleppe. The Object Constraint Language: Precise Modeling with UML. Boston, MA: Addison-Wesley. 1999.
Предметный указатель А Динамическое подключение (plug and play) 381 Анализ предметной области 85 приложения 85 рисков 95 Аналитическая модель 61 Архитектура РАСТ 322 линии продуктов 397 Директива include (включить) 41 Дружественный подход 116 Ж Жизненный цикл объекта 37, 365 3 Б Заглушка 317 Запросчик служб 316 Базовый класс 50 Браузер 335 Брокер объектных запросов (OBR) 312 И Иерархия наследования 50 В Инвариант класса 44,321 Взаимодействие объектов 239 Взаимодействия 383 Выборка 251 расслоенная 252 объекта 321 Индивидуальная проверка 145 Интерпретатор 79 Интерфейс 38 Г Инфраструктура DCOM 313 Исключения 38 Горячая замена компонентов 381 Исходные коды программ 79 д К Действие 69 Деструктор 42 Диаграмма действий 75 классов 65, 76 последовательностей сообщений 71, 79 состояний 68 Класс 39, 241 Grid 267 GridMain 267 SpriteTester 221 Tester 214 Timer 283 базовый 50
Предметный укезатель Класс диаграмма 65, 76 инвариант 44 корневой 50 наследник 51 непримитивный 241 предшественник 51 примитивный 241 производный 50 реализация 40,48 сотрудничающий 243 спецификация 40 Субкласс 50 Суперкласс 50 Классификация ODC 149, 345, 374 Клиент 372 тонкий 372 Код возврата 46 Коллекция 243 Коммуникации 383 Компилятор 79 Комплексное испытание системы 105 Компонент 264, 378 Bean 380 горячая замена 381 программный 378 распределенный 379 Конструктор 42 Контекст 367 Континуум 101 Конфигурация системы 347 Координатор 154 Корень 50 Корректность модели 142 М Менеджер тестирования ПО. См. также Руководитель испытаний Метод целенаправленной проверки 139 Модель СММ 397 анализа 163 архитектурная 166 вычислительная 300 клиент/сервер 311 параллельная 301 потока 309 проектная 76, 166 распределенная 301 распределения 311 сетевая 301 совмещенная 300 требований 157 Н Наследник 51 Наследование 50, 277 Недостижимый тестовый случай 352 Непротиворечивость модели 143 О Объект 35, 298 жизненный цикл 37 распределенный 298, 338 Ограничение 69 Оператор always(p) 323 Eventually(p) 322 until(p,q) 323 Л Операция 42 Линия программного продукта 397 Логика временных интервалов 319 Ортогональная матрица 255 Отправитель 37
414 Предметный указатель п Параллелизм 383 Переменные 48 План тестирования 193 Показатель 132 затраты человеко-часов разработчиков/ число неисправностей 132 число отказов/затраты человеко-часов разработчиков 132 Покрытие 107, 259 n-канального переключателя 370 взвешенное представительное 262 исчерпывающее 259 минимальное 259 jrгйвительное 262 произвольное 2э9 Полиморфизм 51 включения 52 папаметрическии 57 Полнота модели 143 Получатель 37 Последовательность сообщений 383 Поставщик служб 317 Постусловие 44 Поток 299, 304, 309 модель 309 синхронизация 299 Предусловие 44 Предшественник 51 Приращение 86 Проверка 133, 145 глубина 146 индивидуальная 145 область 146 целенаправленная 133 Программное обеспечение 19 встроенное 369 Проектная модель 76 Производный класс 50 Протокол обмена 385 Профиль 347 действующих субъектов 347 использования 345, 347 случая использования 149 Процесс 86 инкрементальный 86 итеративный 86 Р Разработчик 101 Реализация класса 40, 48 модуля 105 Регистратор 156 Риск 94 анализ уэ источники 95 технический 94 Роли в процессе тестирования 109 Руководитель испытаний НО С Самопроверка 361 Сервер 311 Сигнатура 318 Синхронизация 304, 383 потоков 299 Система 368, 382 OATS 382 встроенная 369 многоуровневая 372 распределенная 372 реагирующая 368 Система OATS 255 Скелет 317 Сообщение 37 Состояние 68
Предметный указатель Спецификация класса 40, 42 компонента 381 модуля 105 Структура 378, 388 Субкласс 50 Суперкласс 50 Сценарий 64,152 прогоном тестовых наборов 222 протоколов 269 систем различных типов 368 системное 340 системы безопасности 363 структур 394 характеристик производительности 366 Тестировщик 24, 34, 101, ПО классов 109 Т системный 110 целостности 109 Тестирование HIT 283 Web-страниц 335 абстрактных классов 293 базовое 220 в предельных режимах 364 в рамках проекта линии продуктов 399 взаимодействий 239, 326 взаимодействий окружения 361 взаимодействующих классов 248 жизненного цикла 310 иерархическое инкрементальное 281 измерение тестового покрытия 373 инкрементальных проектов 355 инфраструктуры 333 каждого предположения 328 классов 188, 325 коллекций 247 компонентов и объектов 381 механизма развертывания системы 360 множественных представлений 357 на протяжении жизненного цикла 365 на соответствие функциональным требованиям 358 на уровне организационного управления Тестовая модель временная 322 Тестовый случай 281 недостижимый 352 унаследованный 281 Тестовый шаблон 269 Тестовый драйвер 209 Технология 381 CORBA 312 plug and play 381 RMI 314 Точка вариации 397 Транслятор 79 У Унаследованные тестовые случаи 281 Уровень 255 Условия испытаний 325 ц Целенаправленная проверка 133 Ч 398 на уровне проектирования программного обеспечения 398 на уровне технического управления 398 повторное 183 после развертывания системы 361 Чертежник 156 Ш Шаблон документа 120
Предметный укезетель э Экземпляр 40 Эффект реостата 108 Я Язык описания интерфейсов 318 Языковая зависимость 332 Иностранные термины СММ (capability maturity model) 397 CORBA (Common Object Request Broker Architecture) 312 DCOM (Distributed Component Object Model) 313 IDL (Interface Definition Language) 318 IEEE (Institute of Electrical and Electronics Engineers 342 Internet 335 OATS (Orthogonal Array Testing System) 255,382 OBR (Object Request Broker) 312 ODC (Orthogonal Defect Classification) 345, 374 ODC (Orthogonal Defect Classification) 149 PACT (Parallel Architecture for Class Testing) 322 RMI (Remote Method Invocation) 314 Web-сервер 337
Джон Макгрегор, Девид Сайкс Тестирование объектно-ориентированного программного обеспече**^/ ПРАКТИЧЕСКОЕ Доктор Джон Макгрегор - стратегический партнер компании Korson-McGregor и профессор компьютерных наук в Клемсонском университете. Он является . оавтором книги Object-Oriented Software Development: Engineering Software for Reuse, изданной Van Nostrabd Reinhold. Доктор Макгрегор ведет постоянную колонку в журнале Journal of Object-Oriented Programming, посвященную тестированию и оценке качества объектно- ориентированного программного обеспечения, а также является членом редколлегии в журналах Journal for Software Testing Professional и International Journal of Computer and Information Sciences. Доктор Деаид Сайкс - ассистент профессора компьютерных наук в Фурманском университете. Он работает по совместительству в компании Korson-McGregor и параллельно ведет курсы по объектно-ориентированному анализу, проектированию и тестированию. Доктор Сайкс занимается разработкой и тестированием сложных систем и приложений, начиная с 1975 года Для кого предназначена эта книга? • Для программистов, которые уже работают с программным обеспечением тестирования • Для менеджеров, которые несут ответственность за разработку программного обеспечения и которые хотели бы знать как и на каких стадиях тестирование включается в план работ • Для разработчиков, которые отвечают за тестирование создаваемых ими программ и которые должны учитывать проблемы тестирования в процессе анализа, проектирования и написания кода Разработчикам Менеджерам проекте! Программистам 311.00 В книге основное внимание уделяется реальному планированию и эффективной реализации процесса тестирования объектно- ориентированного и компонентного программного обеспечения. Подробно рассматриваются концептуальные отличия технологий тестирования объектно-ориентированного программного обеспечения от таковых для традиционного процедурного программного обеспечения. Благодаря огромному опыту именитых авторов, книга может послужить эффективным практическим и учебным руководством для профессиональных разработчиков, предлагая готовые технологии построения надежного, предсказуемого и высокоэффективного программного обеспечения с тестированием на всех этапах - анализа, проектирования и реализации. Среди прочего, внимание уделяется таким вопросам, как: тестирование аналитических и проектных моделей, тестирование иерархии наследования, тестирование классов, тестирование взаимодействий между объектами, тестирование распределенных объектов, эффективный выбор тестовых наборов. Предлагаются уникальные методики подбора тестовых случаев, обеспечивающих максимальное покрытие и адекватность тестирования. Несмотря на то что многие до сего момента воспринимают сам процесс тестирования как неизбежное зло, после тщательного изучения этой книги их взгляды гарантированно изменятся на противоположные. Особенную пользу книга окажет разработчикам объектно-ориентированного программного обеспечения для западных компаний Cover design )