3.5 Тестирование объектно-ориентированных ПС

Особенности тестирования объектно-ориентированных программных систем 

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

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

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

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

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

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

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

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

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

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

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

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

Тестирование полиморфизма. Каждый возможный вариант полиморфного вызова метода должен быть протестирован. Примером такого случая является класс А, содержащий метод МА, вызывающий в свою очередь полиморфный метод МАV. Тогда для любого производного класса В, переопределяющего метод MAV, должен быть выполнен тест по вызову метода МА. 

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

Тестирование без учета внутренней структуры. Так называемое тестирование методом "черного ящика" ("Black-box testing"). Набор тестов составляется опираясь на спецификацию и требования к классу. Структура класса при этом не анализируется. Надо учитывать, что любая спецификация не отражает (и не должна отражать) всех деталей реализации, поэтому анализ программного кода все же потребуется. Поэтому, различия между "white-box" и "black-box" тестированием уменьшаются. 

Тестирование, основанное на состояниях объекта. При тестировании на основе состояний, набор тестов определяется на основе моделирования класса как конечного автомата. Результаты выполнения методов касса рассматриваются как переходы между состояниями класса. Автоматная модель класса определяет последовательность смены состояний, которую и следует проверить в ходе тестирования. Основой для составления автоматной модели класса могут служить диаграммы использования, также допустимо определение допустимых состояний методом "white box" (т.е. на основе анализа программного кода). Подобное тестирование не лишено сложностей. Класс может быть ориентирован на любую последовательность вызова методов и в этом случае тестирование состояний будет неэффективно. Управление сменой состояний объекта может быть рассредоточено по всему приложению. Такое "совместное" управление делает изолированное тестирование классов очень сложным. Выходом может является составление глобальной автоматной модели приложения, с целью определения в ней взаимодействия классов. 

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

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

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

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

Перед тем как приступить к описанию графовой модели объектно-ориентированной программы, остановимся отдельно на одном существенном аспекте разработки программного обеспечения на языке объектно-ориентированного программирования (ООП), например, C++ или С#. Разработка программного обеспечения высокого качества для MS Windows или любой другой операционной системы, использующей стандарт "look and feel", с применением только вновь созданных классов практически невозможна. Программист должен будет затратить массу времени на решение стандартных задач по созданию пользовательского интерфейса. Чтобы избежать работы над давно решенными вопросами, во всех современных компиляторах предусмотрены специальные библиотеки классов. Такие библиотеки включают в себя практически весь программный интерфейс операционной системы и позволяют задействовать при программировании средства более высокого уровня, чем просто вызовы функций. Базовые конструкции и классы могут быть переиспользованы при разработке нового программного проекта. За счет этого значительно сокращается время разработки приложений. В качестве примера подобной системы можно привести библиотеку Microsoft Foundation Class для компилятора MS Visual C++ ".

Работа по тестированию приложения не должна включать в себя проверку работоспособности элементов библиотек, ставших фактически промышленным стандартом для разработки программного обеспечения, а только проверку кода, написанного непосредственно разработчиком программного проекта. Тестирование объектно-ориентированной программы должно включать те же уровни, что и тестирование процедурной программы - модульное, интеграционное и системное. Внутри класса отдельно взятые методы имеют императивный характер исполнения. Все языки ООП возвращают контроль вызывающему объекту, когда сообщение обработано. Поэтому каждый метод (функция – член класса) должен пройти традиционное модульное тестирование по выбранному критерию C (как правило,С1). В соответствии с введенными выше обозначениями, назовем метод Modi, а сложность тестирования – V(Modi,C). Все результаты, полученные в лекции 5 для тестирования модулей, безусловно, подходят для тестирования методов классов. Каждый класс должен быть рассмотрен и как субъект интеграционного тестирования. Интеграция для всех методов класса проводится с использованием инкрементальной стратегии снизу вверх. При этом мы можем переиспользовать тесты для классов-родителей тестируемого класса, что следует из принципа наследования – от базовых классов, не имеющих родителей, к самым верхним уровням классов.
 
В объектно-ориентированном программировании — часто используется Mock-объект (от англ. mock object, буквально: «объект-пародия», «объект-имитация», а также «подставка») - тип объектов, реализующих заданные аспекты моделируемого программного окружения. например эммуляцию интернет соединения или работу с базами данных.

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

Использование
Mock-объекты активно используются в разработке через тестирование.

Обычно Mock-и создаются для симулирования работы с:
  • хранилищами данных (СУБД)
  • внешними API
  • другими классами приложения, реализующими бизнес-логику, но не тестируемыми в данном конкретном тесте.
Источник: