Оценка тестового покрытия на проекте. Современные технологии тестирования Модель тестирования состоит из следующих элементов

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

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

Каскадная модель (Линейная последовательная модель жизненного цикла ПО)

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

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

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

Узнайте больше о каскадной модели из предыдущей статьи .

V-Model (Модель верификации и валидации)

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

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

Основные этапы этой методологии могут изменяться, однако обычно они включают следующие:

  • Этап определения требований . Приемочное тестирование относится к этому этапу. Его основная задача состоит в оценке готовности системы к финальному использованию
  • Этап, на котором происходит высокоуровневое проектирование, или High-Level Design (HDL) . Этот этап относится к системному тестированию и включает оценку соблюдения требований к интегрированным системам
  • Фаза детального дизайна (Detailed Design) параллельна фазе интеграционного тестирования, во время которой происходит проверка взаимодействий между различными компонентами системы
  • После этапа написания кода начинается другой важный шаг — юнит-тестирование. Очень важно убедиться в том, что поведение отдельных частей и компонентов ПО корректно и соответствует требованиям

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

Инкрементная модель

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

  1. дизайн и разработка
  2. тестирование
  3. реализация.

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

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

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

Спиральная модель

Спиральная модель это методология тестирования ПО, которая основана на инкрементном подходе и прототипировании. Она состоит из четырех этапов:

  1. Планирование
  2. Анализ рисков
  3. Разработка
  4. Оценка

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

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

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

Agile

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

Узнайте больше об Agile (прим. — статья на английском языке) .

Экстремальное программирование (XP, Extreme Programming)

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

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

Scrum

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

  • Участие в Scrum планировании
  • Поддержка в юнит-тестировании
  • Тестирование пользовательских историй
  • Сотрудничество с заказчиком и владельцем продукта для определения критериев приемлемости
  • Предоставление автоматического тестировании

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

В то же время принципы Agile методологии в Scrum к появлению специфических особенностей:

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

Узнайте больше о методологии Scrum из предыдущей статьи .

Заключение

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

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

Аннотация: Основные понятия тестирования. Фазы и этапы тестирования. Типы тестов. Разработка, управляемая тестами (Test Driven Development)

Введение

Тестирование является одним из наиболее устоявшихся способов обеспечения качества разработки программного обеспечения.

С технической точки зрения тестирование заключается в выполнении приложения на некотором множестве исходных данных и сверке получаемых результатов с заранее известными (эталонными) с целью установить соответствие различных свойств и характеристик приложения заказанным свойствам. Как одна из основных фаз процесса разработки программного продукта ( Дизайн приложения - Разработка кода - Тестирование), тестирование характеризуется достаточно большим вкладом в суммарную трудоемкость разработки продукта. Широко известна оценка распределения трудоемкости между фазами создания программного продукта: 40%-20%-40%.

С точки зрения математики тестирование можно рассматривать как интерпретацию некоторой формулы и проверки ее истинности на некоторых множествах. Действительно, программу можно представить в виде формулы f = f1* f2* f3*... * fn , где f1 , f 2 , ... fn - операторы языка программирования, а их суперпозиция - программа .

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

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

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

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

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

Классы критериев тестирования

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

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

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

Выделяют следующие частные виды функциональных критериев :

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

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

Мутационные критерии ориентированы на проверку свойств программного изделия на основе подхода Монте-Карло.

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

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

Фазы тестирования

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

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

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

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

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

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

Этапы тестирования

Каждая фаза тестирования включает в себя следующие этапы:

  1. Определение целей (требований к тестированию), включающее следующую конкретизацию: какие части системы будут тестироваться, какие аспекты их работы будут выбраны для проверки, каково желаемое качество и т. п.
  2. Планирование : создание графика (расписания) разработки тестов для каждой тестируемой подсистемы; оценка необходимых человеческих, программных и аппаратных ресурсов; разработка расписания тестовых циклов . Важно отметить, что расписание тестирования обязательно должно быть согласовано с расписанием разработки создаваемой системы.
  3. Разработка тестов (тестового кода для тестируемой системы).
  4. Выполнение тестов : реализация тестовых циклов .
  5. Анализ результатов .

Тестовый цикл - это цикл исполнения тестов, включающий фазы 4 и 5 тестового процесса. Тестовый цикл заключается в прогоне разработанных тестов на некотором однозначно определяемом срезе системы (состоянии кода разрабатываемой системы). Обычно такой срез системы называют build .

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

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

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

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

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

Типы тестов

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

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

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

Типы тестирования по способу выбора входных значений:

  1. Функциональное тестирование, при котором проверяется:
    • покрытие функциональных требований;
    • покрытие сценариев использования.
  2. Стрессовое тестирование, при котором проверяются экстремальные режимы использования продукта.
  3. Тестирование граничных значений.
  4. Тестирование производительности.
  5. Тестирование на соответствие стандартам.
  6. Тестирование совместимости с другими программно-аппаратными комплексами.
  7. Тестирование работы с окружением.
  8. Тестирование работы на конкретной платформе.

Test Driven Development

Рассмотрим подход к тестированию, несколько отличающийся от приведенного выше. Разработка через тестирование ( Test Driven Development - TDD) - процесс разработки программного обеспечения, который предусматривает написание и автоматизацию модульных тестов еще до момента написания соответствующих классов или модулей. Это гарантирует, что все обязанности любого элемента программного обеспечения определяются еще до того, как они будут закодированы.

TDD задает следующий порядок этапов программирования:

  • Красный - напишите небольшой тест, который не работает, а возможно, даже не компилируется.
  • Зеленый - заставьте тест работать как можно быстрее, при этом не думайте о правильности дизайна и чистоте кода. Напишите ровно столько кода, чтобы тест сработал.
  • Рефакторинг - удалите из написанного вами кода любое дублирование.
  • Освоив TDD, разработчики обнаруживают, что они пишут значительно больше тестов, чем раньше, и двигаются вперед маленькими шагами, которые раньше могли показаться бессмысленными.

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

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

Итоги

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

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

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

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

18.09.2003 Александр Петренко, Елена Бритвина, Сергей Грошев, Александр Монахов, Ольга Петренко

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

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

Подходы к улучшению качества программ

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

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

В простейшем варианте набор этапов жизненного цикла таков:

  • анализ требований;
  • проектирование (предварительное и детальное);
  • кодирование и отладка ("программирование");
  • тестирование;
  • эксплуатация и сопровождение.

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

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

В конце 80-х годов была предложена так называемая спиральная модель, был развит и проверен на практике метод итеративной и инкрементальной разработки (Iterative and Incremental Development, IID). В спиральной модели были учтены проблемы водопадной модели. Главный упор в спиральной модели делается на итеративности процесса. Описаны опыты использования IID с длиной итерации всего в полдня. Каждая итерация завершается выдачей новой версии программного обеспечения. На каждой версии уточняются (и, возможно, меняются) требования к целевой системе и принимаются меры к тому, чтобы удовлетворить и новые требования. В целом Rational Unified Process (RUP) также следует этой модели.

Позволило ли это решить проблему качества? Лишь в некоторой степени.

Проблема повышения качества программного обеспечения в целом и повышения качества тестирования привлекает все большее внимание; в университетах вводят специальные дисциплины по тестированию и обеспечению качества, готовят узких специалистов по тестированию и инженеров по обеспечению качества. Однако по-прежнему ошибки обходятся только в США от 20 до 60 млрд. долл. ежегодно. При этом примерно 60% убытков ложится на плечи конечных пользователей. Складывается ситуация, при которой потребители вынуждены покупать заведомо бракованный товар.

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

Каково же направление главного удара? Что предлагают «наилучшие практики»?

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

В последние годы в связи с появлением методов, которые принято обозначать эпитетом agile («шустрый», «проворный») предлагаются и внедряются новые конструктивные методы раннего обнаружения ошибок. Скажем, современные модели, такие как Microsoft Solutions Framework (MSF) и eXtreme Programming (XP), выделяют следующие рекомендации к разработке тестов:

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

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

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

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

Итак, различные работы в процессе производства программ должны быть хорошо интегрированы с работами по тестированию. Соответственно, инструменты тестирования должны быть хорошо интегрированы со многими другими инструментами разработки. Из крупных производителей инструментов разработки программ, первыми это поняли компании Telelogic (набор инструментов для проектирования, моделирования, реализации и тестирования телекоммуникационного ПО, базирующийся на нотациях SDL/MSC/TTCN) и Rational Software (аналогичный набор, преимущественно базирующийся на нотации UML). Следующий шаг сделала компания IBM, начав интеграцию возможностей инструментов от Rational в среду разработки программ Eclipse.

Тезис XP - «Пиши тест перед реализацией» - хорош как лозунг, но в реальности столь же неконструктивен. Для крупных программных комплексов приходится разрабатывать тесты различного назначения: тесты модулей, интеграционные или компонентные тесты, системные тесты.

Три составляющие тестирования - экскурс в теорию

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

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

Распространение компонентных технологий породило термин «компонентное тестирование» как частный случай интеграционного тестирования.

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

Инструменты тестирования - реальная практика

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

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

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

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

В данном виде тестирования широко применяются инструменты записи-воспроизведения (record/playback); из наиболее известных продуктов можно назвать Rational Robot (компания IBM/Rational), WinRunner (Mercury Interactive), QARun (Compuware). Наряду с этим существуют инструменты для текстовых терминальных интерфейсов, например, QAHiperstation компании Compuware.

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

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

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

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

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

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

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

Следующий класс инструментов - инструменты тестирования компонентов . Примером является Test Architect (IBM/Rational). Такие инструменты помогают организовать тестирование приложений, построенных по одной из компонентных технологий (например, EJB). Предусматривается набор шаблонов для создания различных компонентов тестовой программы, в частности, тестов для модулей, сценариев, заглушек.

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

Последний из рассматриваемых здесь классов инструментов - инструменты тестирования модулей . Примером может служить Test RealTime (IBM/Rational), предназначенный для тестирования модулей на C++. Важной составляющей этого инструмента является механизм проверочных «утверждений» (assertion). При помощи утверждений можно сформулировать требования к входным и выходным данным функций/методов классов в форме логических условий, в аналогичной форме можно задавать инвариантные требования к данным объектов. Это существенный шаг вперед по сравнению с Test Architect. Аппарат утверждений позволяет систематическим образом представлять функциональные требования и на базе этих требований строить критерии тестового покрытия (правда, Test RealTime автоматизированной поддержки анализа покрытия не предоставляет).

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

Решение перечисленных проблем предлагает новое поколение инструментов, которые следуют подходу тестирования на основе модели (model based testing) или на основе спецификаций (specification based testing).

Чем могут помочь модели

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

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

Новый всплеск интереса к формальным методам произошел в первой половине 90-х. Его вызвали первые результаты, полученные при использовании формальных моделей и формальных спецификаций в тестировании.

Преимущества тестирования на основе моделей виделись в том, что:

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

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

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

Имеется несколько ставших уже классическими нотаций формальных спецификаций: VDM, Z, B, CCS, LOTOS и др. Некоторые из них, например, VDM, используются преимущественно для быстрого прототипирования. Язык B удобен для анализа, в частности для аналитической верификации моделей. Все эти языки активно используются в рамках университетских программ. В реальной практике для описания архитектурных моделей используется UML, а для построения поведенческих моделей - языки SDL/MSC, исполнимые диаграммы UML и близкие к ним нотации.

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

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

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

Test Real Time - один из первых представителей этой группы. Более широкие возможности предоставляет Jtest компании Parasoft. Интересен инструментарий компании Comformiq. Семейство инструментов разработки тестов на основе моделей предлагает Институт системного программирования РАН в кооперации с компанией ATS. Поскольку семейство UniTesK авторам знакомо существенно ближе, мы изложим общую схему подхода тестирования на основе моделей на примерах из UniTesK.

Рис. 1. Фазы процесса разработки спецификаций и тестов

Общая схема процесса разработки спецификаций и тестов состоит из четырех фаз (рис. 1).

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

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

UniTesK - унифицированное решение

UniTesK предлагает использовать так называемые неявные спецификации или спецификации ограничений. Они задаются в виде пред- и постусловий процедур и инвариантных ограничений на типы данных. Этот механизм не позволяет описывать в модели алгоритмы вычисления ожидаемых значений функций, а только их свойства. Скажем, в случае системы управления памятью модель будет задана булевским выражением в постусловии типа «значение указателя принадлежит области свободной памяти». Простой пример постусловия для функции «корень квадратный» приведен на ; одна и та же спецификация представлена в трех разных нотациях: в стиле языков Cи, Java и C#. Использование спецификационных расширений обычных языков программирования вместо классических языков формальных спецификаций - шаг, на который идут почти все разработчики подобных инструментов. Их различает только выразительная мощность нотаций и возможности анализа и трансляции спецификаций.

Третья фаза - разработка тестового сценария. В простейшем случае сценарий можно написать вручную, но в данной группе инструментов - это плохой тон. Тест, т.е. последовательность вызовов операций целевой системы с соответствующими параметрами, можно сгенерировать, отталкиваясь от некоторого описания программы или структуры данных. Будем называть такое описание сценарием . Компания Conformiq предлагает описать конечный автомат. Различные состояния автомата соответствуют различным значениям переменных целевой системы, переходы - вызовам операций этой системы. Определить автомат - это значит для каждого состояния описать, в какое состояние мы перейдем из данного, если обратимся к любой наперед заданной операции с любыми наперед заданными параметрами. Если такое описание получить легко, больше ничего делать не понадобится: инструмент сгенерирует тест автоматически и представит результаты тестирования, например, в виде MSC-диаграмм. Но легко ли это, скажем, для программы с одной целочисленной переменной и двумя-тремя операциями? Скорее всего, да. Однако в общем случае сделать попросту невозможно.

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

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

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

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

Рис. 4. Архитектура тестовой программ

Создатели UniTesK, полагая, что не должно быть отдельной среды для разработки тестов, не только наделили его возможностью мимикрии под различные языки программирования, но обеспечили интеграцию составляющих его инструментов в популярные средства разработки программ. На рис. 5 представлен сеанс использования UniTesK в среде разработки Forte 4.0 компании Sun Microsystems.

Новое качество, которое обещают новые инструменты

Как отмечалось выше, создатели инструментов тестирования обычно сталкиваются со следующими проблемами:

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

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

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

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

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

Рассмотренные инструменты опробованы на реальных, масштабных проектах. Конечно, каждый проект несет в себе некоторую специфику, возможно, препятствующую исчерпывающему тестированию. Однако опыт использования данных инструментов показывает, что обычно удается достичь хороших результатов, лучших, чем результаты, полученные в аналогичных проектах при помощи ручного тестирования. Пользователи UniTesK, обычно, за приемлемый уровень качества принимают 70-80% покрытия кода целевой системы; при этом должен быть удовлетворен, как минимум, критерий покрытия всех логических ветвей в постусловиях. Для некоторых сложных программ (в том числе, для блока оптимизации компилятора GCC) был достигнут уровень покрытия 90-95%.

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

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

S - Сигнатура операции

A - Спецификация доступа

- Предусловие

B - Определение ветвей функциональности

> - Постусловие

Java:
Class SqrtSpecification { S Specification static double sqrt(double x) A reads x, epsilon { = 0; } post { > if(x == 0) { B branch «Zero argument»; > return sqrt == 0; > } else { B branch «Positive argument»; > return sqrt >= 0 && > Math.abs((sqrt*sqrt-x)/x) } } } }
Си:
S specification double SQRT(double x) A reads (double)x, epsilon { = 0.; } coverage ZP { if(x == 0) { B return(ZERO, «Zero argument»); } else { B return(POS, «Positive argument»); } } post { > if(coverage(ZP, ZERO)) { > return SQRT == 0.; > } else { > return SQRT >= 0. && > abs((SQRT*SQRT - x)/x) } } }
C#:
namespace Examples { specification class SqrtSpecification { S specification static double Sqrt(double x) A reads x, epsilon { = 0; } post { > if(x == 0) { B branch ZERO («Zero argument»); > return $this.Result == 0; > } else { B branch POS («Positive argument»); > return $this.Result >= 0 && > Math.Abs(($this.Result * $this.Result - x)/x) } > } > } } }

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

Цели процесса тестирования

Целью тестирования является оценка качества программного продукта путем

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

Особенности процесса тестирования в RUP

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

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

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

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

Артефакты

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

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

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

  • Контрольная задача – набор тестовых данных, условий выполнения тестов и ожидаемых результатов.
  • Методика испытаний – документ, содержащий указания по настройке и выполнению контрольных задач, а также по оценке получаемых результатов.
  • Сценарий тестирования – это упрощенное описание теста, включая исходные данные, условия и последовательности выполнения действий и ожидаемые результаты.
  • Тестовый скрипт является программой, выполняемой при автоматическом тестировании с помощью инструментальных средств тестирования.
  • Описание взаимодействия тестов представляет собой диаграмму последовательностей или коопераций, отражающую упорядоченный во времени поток сообщений между компонентами тестов и объектом тестирования.

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

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

Дефекты – это описания обнаруженных при проведении тестирования фактов несоответствия системы предъявляемым требованиям. Они представляют собой вид запросов на внесение изменений.

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

Фаза вхождения в проект. В этой фазе выполняется подготовка к тестированию. Она включает:

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

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

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

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

  • Создание плана тестирования для каждой итерации.
  • Уточнение и дополнение модели тестирования.
  • Выполнение тестов.
  • Описание обнаруженных дефектов.
  • Описание результатов тестирования.
  • Оценка результатов тестирования.

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

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

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

Инструментальная поддержка

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

  • Тестирование веб-сервисов
  • Самый лучший способ оценить, хорошо ли мы протестировали продукт – проанализировать пропущенные дефекты. Те, с которыми столкнулись наши пользователи, внедренцы, бизнес. По ним можно многое оценить: что мы проверили недостаточно тщательно, каким областям продукта стоит уделить больше внимания, какой вообще процент пропусков и какова динамика его изменений. С этой метрикой (пожалуй, самой распространённой в тестировании) всё хорошо, но… Когда мы выпустили продукт, и узнали о пропущенных ошибках, может быть уже слишком поздно: на “хабре” появилась про нас гневная статья, конкуренты стремительно распространяют критику, клиенты потеряли к нам доверие, руководство недовольно.

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

    Зачем оценивать?

    Любые метрики оценки – трата времени. В это время можно тестировать, заводить баги, готовить автотесты. Какую такую магическую пользу мы получаем благодаря метрикам тестового покрытия, чтобы пожертвовать временем на тестирование?
    1. Поиск своих слабых зон. Естественно, это нам нужно? не чтобы просто погоревать, а чтобы знать, где требуются улучшения. Какие функциональные области не покрыты тестами? Что мы не проверили? Где наибольшие риски пропуска ошибок?
    2. Редко по результатам оценки покрытия мы получаем 100%. Что улучшать? Куда идти? Какой сейчас процент? Как мы его повысим какой-либо задачей? Как быстро мы дойдём до 100? Все эти вопросы приносят прозрачности и понятности нашему процессу , а ответы на них даёт оценка покрытия.
    3. Фокус внимания. Допустим, в нашем продукте около 50 различных функциональных зон. Выходит новая версия, и мы начинаем тестировать 1-ю из них, и находим там опечатки, и съехавшие на пару пикселей кнопки, и прочую мелочь… И вот время на тестирование завершено, и эта функциональность проверена детально… А остальные 50? Оценка покрытия позволяет нам приоритезировать задачи исходя из текущих реалий и сроков.

    Как оценивать?

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

    Оцениваем покрытие требований тестами

    Допустим, у вас в команде есть аналитики, и они не зря тратят своё рабочее время. По результатам их работы созданы требования в RMS (Requirements Management System) – HP QC, MS TFS, IBM Doors, Jira (с доп. плагинами) и т.д. В эту систему они вносят требования, соответствующие требованиям к требованиям (простите за тавтологию). Эти требования атомарны, трассируемы, конкретны… В общем, идеальные условия для тестирования. Что мы можем сделать в таком случае? При использовании скриптового подхода – связывать требования и тесты. Ведём в той же системе тесты, делаем связку требование-тест, и в любой момент можем посмотреть отчёт, по каким требованиям тесты есть, по каким – нет, когда эти тесты были пройдены, и с каким результатом.
    Получаем карту покрытия, все непокрытые требования покрываем, все счастливы и довольны, ошибок не пропускаем…

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

    Проблема: требования не атомарны.

    Аналитики тоже иногда грешат винегретом в голове, и обычно это чревато проблемами со всем проектом. Например, вы разрабатываете текстовый редактор, и у вас могут быть в системе (в числе прочих) заведены два требования: «должно поддерживаться html-форматирование» и «при открытии файла неподдерживаемого формата, должно появляться всплывающее окно с вопросом». Сколько тестов требуется для базовой проверки 1-го требования? А для 2-го? Разница в ответах, скорее всего, примерно в сто раз!!! Мы не можем сказать, что при наличии хотя бы 1-го теста по 1-му требованию, этого достаточно – а вот про 2-е, скорее всего, вполне.

    Таким образом, наличие теста на требование нам вообще ничего не гарантирует! Что значит в таком случае наша статистика покрытия? Примерно ничего! Придётся решать!

    1. Автоматический расчёт покрытия требований тестами в таком случае можно убрать – он смысловой нагрузки всё равно не несёт.
    2. По каждому требованию, начиная с наиболее приоритетных, готовим тесты. При подготовке анализируем, какие тесты потребуются этому требованию, сколько будет достаточно? Проводим полноценный тест-анализ, а не отмахиваемся «один тест есть, ну и ладно».
    3. В зависимости от используемой системы, делаем экспорт/выгрузку тестов по требованию и… проводим тестирование этих тестов! Достаточно ли их? В идеале, конечно, такое тестирование нужно проводить с аналитиком и разработчиком этой функциональности. Распечатайте тесты, заприте коллег в переговорке, и не отпускайте, пока они не скажут «да, этих тестов достаточно» (такое бывает только при письменном согласовании, когда эти слова говорятся для отписки, даже без анализа тестов. При устном обсуждении ваши коллеги выльют ушат критики, пропущенных тестов, неправильно понятых требований и т.д. – это не всегда приятно, но для тестирования очень полезно!)
    4. После доработки тестов по требованию и согласования их полноты, в системе этому требованию можно проставить статус «покрыто тестами». Эта информация будет значить значительно больше, чем «тут есть хотя бы 1 тест».

    Конечно, такой процесс согласования требует немало ресурсов и времени, особенно поначалу, до наработки практики. Поэтому проводите по нему только высокоприоритетные требования, и новые доработки. Со временем и остальные требования подтянете, и все будут счастливы! Но… а если требований нет вообще?

    Проблема: требований нет вообще.

    Они на проекте отсутствуют, обсуждаются устно, каждый делает, что хочет/может и как он понимает. Тестируем так же. Как результат, получаем огромное количество проблем не только в тестировании и разработке, но и изначально некорректной реализации фич – хотели совсем другого! Здесь я могу посоветовать вариант «определите и задокументируйте требования сами», и даже пару раз в своей практике использовала эту стратегию, но в 99% случаев таких ресурсов в команде тестирования нет – так что пойдём значительно менее ресурсоёмким путём:
    1. Создаём фичелист (feature list). Сами! В виде google-таблички, в формате PBI в TFS – выбирайте любой, лишь бы не текстовый формат. Нам ещё статусы собирать надо будет! В этот список вносим все функциональные области продукта, и постарайтесь выбрать один общий уровень декомпозиции (вы можете выписать объекты ПО, или пользовательские сценарии, или модули, или веб-страницы, или методы API, или экранные формы…) – только не всё это сразу! ОДИН формат декомпозиции, который вам проще и нагляднее всего позволит не пропустить важное.
    2. Согласовываем ПОЛНОТУ этого списка с аналитиками, разработчиками, бизнесом, внутри своей команды… Постарайтесь сделать всё, чтобы не потерять важные части продукта! Насколько глубоко проводить анализ – решать вам. В моей практике всего несколько раз были продукты, на которые мы создали более 100 страниц в таблице, и это были продукты-гиганты. Чаще всего, 30-50 строк – достижимый результат для последующей тщательной обработки. В небольшой команде без выделенных тест-аналитиков большее число элементов фичелиста будет слишком сложным в поддержке.
    3. После этого, идём по приоритетам, и обрабатываем каждую строку фичелиста как в описанном выше разделе с требованиями. Пишем тесты, обсуждаем, согласовываем достаточность. Помечаем статусы, по какой фиче тестов хватает. Получаем и статус, и прогресс, и расширение тестов за счёт общения с командой. Все счастливы!

    Но… Что делать, если требования ведутся, но не в трассируемом формате?

    Проблема: требования не трассируемы.

    На проекте есть огромное количество документации, аналитики печатают со скоростью 400 знаков в минуту, у вас есть спецификации, ТЗ, инструкции, справки (чаще всего это происходит по просьбе заказчика), и всё это выступает в роли требований, и на проекте уже все давно запутались, где какую информацию искать?
    Повторяем предыдущий раздел, помогая всей команде навести порядок!
    1. Создаём фичелист (см. выше), но без детального описания требований.
    2. По каждой фиче собираем воедино ссылки на ТЗ, спецификации, инструкции, и прочие документы.
    3. Идём по приоритетам, готовим тесты, согласовываем их полноту. Всё то же самое, только благодаря объединению всех документов в одну табличку повышаем простоту доступа к ним, прозрачные статусы и согласованность тестов. В итоге, у нас всё супер, и все счастливы!

    Но… Ненадолго… Кажется, за прошлую неделю аналитики по обращениям заказчиков обновили 4 разные спецификации!!!

    Проблема: требования всё время меняются.

    Конечно, хорошо бы тестировать некую фиксированную систему, но наши продукты обычно живые. Что-то попросил заказчик, что-то изменилось во внешнем к нашему продукту законодательстве, а где-то аналитики нашли ошибку анализа позапрошлого года… Требования живут своей жизнью! Что же делать?
    1. Допустим, у вас уже собраны ссылки на ТЗ и спецификации в виде фичелиста-таблицы, PBI, требований, заметок в Wiki и т.д. Допустим, у вас уже есть тесты на эти требования. И вот, требование меняется! Это может означать изменение в RMS, или задачу в TMS (Task Management System), или письмо в почте. В любом случае, это ведёт к одному и тому же следствию: ваши тесты неактуальны! Или могут быть неактуальны. А значит, требуют обновления (покрытие тестами старой версии продукта как-то не очень считается, да?)
    2. В фичелисте, в RMS, в TMS (Test Management System – testrails, sitechco, etc) тесты должны быть обязательно и незамедлительно помечены как неактуальные! В HP QC или MS TFS это можно делать автоматически при обновлении требований, а в google-табличке или wiki придётся проставлять ручками. Но вы должны видеть сразу: тесты неактуальны! А значит, нас ждёт полный повторный путь: обновить, провести заново тест-анализ, переписать тесты, согласовать изменения, и только после этого пометить фичу/требование снова как «покрыто тестами».

    В этом случае мы получаем все бенефиты оценки тестового покрытия, да ещё и в динамике! Все счастливы!!! Но…
    Но вы так много внимания уделяли работе с требованиями, что теперь вам не хватает времени либо на тестирование, либо на документирование тестов. На мой взгляд (и тут есть место религиозному спору!) требования важнее тестов, и уж лучше так! Хотя бы они в порядке, и вся команда в курсе, и разработчики делают именно то, что нужно. НО НА ДОКУМЕНТИРОВАНИЕ ТЕСТОВ ВРЕМЕНИ НЕ ОСТАЁТСЯ!

    Проблема: не хватает времени документировать тесты.

    На самом деле, источником этой проблемы может быть не только нехватка времени, но и ваш вполне осознанный выбор их не документировать (не любим, избегаем эффекта пестицида, слишком часто меняется продукт и т.д.). Но как оценивать покрытие тестами в таком случае?
    1. Вам всё равно нужны требования, как полноценные требования или как фиче-лист, поэтому какой-то из вышеописанных разделов, в зависимости от работы аналитиков на проекте, будет всё равно необходим. Получили требования / фичелист?
    2. Описываем и устно согласовываем вкратце стратегию тестирования, без документирования конкретных тестов! Эта стратегия может быть указана в столбце таблицы, на странице вики или в требовании в RMS, и она должна быть опять же согласована. В рамках этой стратегии проверки будут проводиться по-разному, но вы будете знать: когда это последний раз тестировалось и по какой стратегии? А это уже, согласитесь, тоже неплохо! И все будут счастливы.

    Но… Какое ещё «но»? Какое???

    Говорите, все обойдём, и да пребудут с нами качественные продукты!