Опубликован: 05.03.2005 | Уровень: специалист | Доступ: платный
Практическая работа 2:

Модульное тестирование на примере классов

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

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

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

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

Кто, что, когда, как и в каком объеме?

Рассмотрим эти вопросы в контексте тестирования классов.

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

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

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

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

Какие объемы тестирования следует считать адекватными? Адекватность может быть измерена полнотой охвата тестами спецификации или реализации. Будем использовать оба способа.

Что тестировать?

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

  • примитивные классы;
  • непримитивные классы.

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

Таблица 2.1. Типы Классов
Класс Тип
TBearingParam Примитивный
TAxleParam Примитивный
TCommand Примитивный
TLog Примитивный
TCommandQueue Непримитивный
TStore Непримитивный
TTerminalBearing Непримитивный
TTerminalAxle Непримитивный
TModel Непримитивный
MainForm Непримитивный

В большинстве объектно-ориентированных языков члены класса имеют один из трех уровней доступа:

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

Private. Члены с доступом private доступны только внутри самого класса, то есть из его методов. Они являются частью внутренней реализации класса и недоступны стороннему разработчику.

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

Таким образом, необходимость тестирования функциональности класса зависит от того, предоставляется ли им возможность наследования. Если класс является законченным ( final ) и не предполагает наследования, необходимо тестирование его public части (впрочем, классы final не содержат protected членов). Если же класс рассчитан на расширение за счет наследования, необходимо тестирование также его protected части.

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

Как тестировать?

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

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

Тестовый драйвер реализуется в виде класса, наследуемого от тестируемого. В отличие от предыдущего способа, такому тестовому драйверу доступна не только public, но и protected часть.

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

В дальнейшем мы будем использовать первый способ при реализации драйверов.

При разработке спецификации класса можно задействовать один из следующих подходов:

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

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

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

Федор Антонов
Федор Антонов

Здравствуйте!

Записался на ваш курс, но не понимаю как произвести оплату.

Надо ли писать заявление и, если да, то куда отправлять?

как я получу диплом о профессиональной переподготовке?

Сергей Чурбанов
Сергей Чурбанов
Юрий Коробков
Юрий Коробков
Россия, Москва, МЭИ, 1998