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

, ВМиК МГУ им. М.В. Ломоносова,

, МГТУ ГА

, Luxsoft International

, «Модуль»

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

Введение. Интегрированная среда среды управления каналообразующим оборудованием бортовых сетей [1] представляет собой многокомпонентное и многоуровневое приложение с графическим интерфейсом пользователя (ГИП), предназначенное для работы на платформах MS Windows и Linux. В таком проекте возникают задачи, требующие автоматизации, в том числе задачи:

-  по собственно разработке продукта,

-  по управлению версиями,

-  по разработке, исполнению, оценке полноты тестового комплекта,

-  по регистрации ошибок и требований и контролю исполнения,

-  другие задачи по управлению проектом.

Для автоматизации этих задач при разработке продукта [1] мы использовали значительное число инструментальных средств, некоторые из которых разработаны специально для этого проекта. Ниже мы попытаемся отразить наш опыт разработки и применения этих инструментальных средств.

Компиляторы и библиотеки. На платформе MS Windows использовался компилятор и среда MS Visual Studio (версии 2003 и 2005), на платформе Linux – компилятор GNU C++ (версии 3.2.2. и 4.1.1). Для реализации графического интерфейса пользователя (ГИП), с учетом необходимости работы продукта на двух платформах, использовалась библиотека Qt Library [2] (версии 4.3.4), которая поставляется с рядом сопутствующих инструментальных средств: редактор форм ГИП (designer), компилятор форм ГИП (uic), компилятор (препроцессор) метаобъектов (moc), программа управления сборкой (qmake). Для платформно-независимого управления сборкой продукта использовался генератор проектов cmake (версия 2.6) [3], для автоматизации исполнения тестового комплекта – поставляемая с ним программа ctest.

НЕ нашли? Не то? Что вы ищете?

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

-  редактирование при помощи ГИП,

-  ввод-вывод.

Рассмотрим эти операци подробнее.

Редактирование при помощи ГИП требует реализации функций-методов доступа к данным («сеттеров» и «геттеров», от англ. set и get), а также событий по изменению данных оператором, позволяющих программному обеспечению реагировать на действия оператора («сигналов» в терминологии Qt Library [2]).

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

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

Язык описания данных (DDL) представляет собой расширение С++. Описание простой структуры данных с применением DDL выглядит так:

struct ComConf: public ISerializableImpl<ComConf> {

DDL_CLASS

ddl_property bool check2usecInterval ddl_visual ddl_serializable ddl_tagname("CHECK-2usec-INTERVAL");

ddl_property RamLocation ramLocation ddl_visual ddl_serializable;

ddl_property int ramOnCrystalSize ddl_visual ddl_serializable ddl_format("%04X");

};

Здесь:

- DDL_CLASS – маркер (начала) класса или структуры, содержащей описания на DDL,

- ddl_property – маркер (начала) описания элемента данных на DDL,

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

- ddl_serializable – указание, что для этого элемента данных должен генерироваться код метаданных для сериализации,

- ddl_tagname(…) – имя элемента (тега) для XML,

- ddl_format(…) – формат ввода-вывода для чисел, в смысле функций sprintf()/sscanf().

Такой текст все еще является текстом на C++; для компиляции С++ директивы DDL определены как макросы с пустым расширением и игнорируются.

Препроцессор DDL анализирует такое описание и генерирует файлы, содержащие программый код (на С++) двух дополнительных классов: «Контроллера» и «Метаобъекта».

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

class ComConfController : public QObject {

Q_OBJECT

signals:

void changed();

void need_resync();

void check2usecInterval_changed(bool);

void ramLocation_changed(RamLocation);

void ramOnCrystalSize_changed(int);

public slots:

void force_resync() {

emit need_resync();

}

void set_check2usecInterval(bool newValOfcheck2usecInterval) {

model_->check2usecInterval = newValOfcheck2usecInterval;

emit check2usecInterval_changed(newValOfcheck2usecInterval);

emit changed();

}

void set_ramLocation(RamLocation newValOframLocation) {

model_->ramLocation = newValOframLocation;

emit ramLocation_changed(newValOframLocation);

emit changed();

}

void set_ramOnCrystalSize(int newValOframOnCrystalSize) {

model_->ramOnCrystalSize = newValOframOnCrystalSize;

emit ramOnCrystalSize_changed(newValOframOnCrystalSize);

emit changed();

}

public:

explicit ComConfController(ComConf *model = NULL): model_(model) {

if (model_ == NULL) model_ = &nullModel_;

}

void set_model(ComConf *model) {

if (model == NULL)

model_ = &nullModel_;

else

model_ = model;

emit need_resync();

}

bool get_check2usecInterval() const {

return model_->check2usecInterval;

}

RamLocation get_ramLocation() const {

return model_->ramLocation;

}

int get_ramOnCrystalSize() const {

return model_->ramOnCrystalSize;

}

private:

ComConf *model_;

ComConf nullModel_;

};

Это описание класса – также на расширении С++ и требует обработки как компилятором С++, так программой moc из библиотеки Qt Library [2]. Секция «signals» содержит функции-«сигналы», секция «public slots» - функции-«сеттеры», секция «public» - функции-«геттеры». Функции-«сеттеры» в простых случаях можно непосредственно связать с функциями-«сигналами» элемента ГИП («виджета»), предназначенного для ввода данных оператором. (Описание событийной модели «сигнал-слот» – см. [2].) Приватное поле model предназначено для ссылки на экземпляр исходного класса, доступ к которому реализуется. Классы-«Контроллеры» является частью паттерна программирования «Model-View-Controller» [5], широко используемого в данном проекте.

«Метаобъект» представляет собой объект, содержащий метаданные (описания элементов данных) исходного класса, и выглядит так:

struct ComConf_metaobject : public IMetaobjectImpl< ComConf >

{

Metafield< ComConf, bool > check2usecInterval;

Metafield< ComConf, RamLocation > ramLocation;

Metafield< ComConf, int > ramOnCrystalSize;

ComConf_metaobject():

check2usecInterval(& ComConf :: check2usecInterval,

"CHECK-2usec-INTERVAL"),

ramLocation(& ComConf :: ramLocation, "ramLocation"),

ramOnCrystalSize(& ComConf :: ramOnCrystalSize, "ramOnCrystalSize", "%04X"),

{

tagname = "ComConf";

fields. push_back(&check2usecInterval);

fields. push_back(&ramLocation);

fields. push_back(&ramOnCrystalSize);

}

};

Элементы данных этого класса являются подстановками шаблона Metafield<> и содержат

-  описатель типа поля,

-  ссылку (типизированное смещение) на элемент данных в исходном классе.

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

- ISerializable – этот интерфейс должен реализовываться каждым классом/структурой, допускающей ввод-вывод. Основная задача – привязка к соответствующему интерфейсу доступа к метаданным (есть также несколько дополнительных задач). В рассмотренном примере это обеспечивается путем наследования от класса-шаблона ISerializableImpl<>.

- IMetaobject – интерфейс доступа к метаданным. Все генерируемые «Метаобъекты» реализуют этот интерфейс путем наследования от класса-шаблона IMetaobjectImpl<>.

- IField – интерфейс доступа к описателю элемента данных. В примере не показано, но элементы Metafield<> реализуют этот интерфейс. Через него можно получить доступ к интерфейсу описателя типа.

- IType – интерфейс описателя типа. Имеет подтипы для разных типов данных:

- IAtomType – описатель атомарного типа данных,

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

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

- IPointerType – описатель типа данных - указателя на элемент другого типа данных.

Интерфейс IAtomType включает методы доступа к элементам данных в виде строк, а интерфейсы IClassType, IContainerType, IPointerType – методы навигации по структурам данных, необходимые для реализации ввода-вывода.

Таким образом, данные и алгоритмы ввода-вывода полностью независмы друг от друга. Подсистема ввода-вывода ориентируется только на информацию из «Метаобъектов», которые генерируются автоматически по DDL-описаниям. Обобщенный алгоритм ввод-вывода реализует только четыре сценария ввода-вывода, соответствующих описателям типов IAtomType, IClassType, IContainerType, IPointerType. Образно говоря, ввод-вывод конкретных данных в этом проекте никто не программирует.

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

-  атомарные типы char, int, long, float, double, перечисления, std::string, bool,

-  классы и структуры (такие классы должны, подобно объемлющему классу, реализовывать интерфейс ISerializable),

-  одно - и двух-мерные массивы фиксированной длины из разрешенных типов данных,

-  стандартные контейнеры std::list<> и std::vector<> из разрешенных типов данных,

-  указатели на разрешенные типы данных.

Реализован ввод-вывод классов с учетом наследования.

Имеется возможность задавать строковые эквиваленты для перечислимых типов и bool.

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

Системы, обеспечивающие такую возможность, в общем-то есть. В частности, полностью удовлетворяет приведенным требованиям подсистема ввода-вывода, реализованная в библиотеке boost::serialization [6], и эта библиотека использовалась в ранних версиях данного продукта [1]. Позднее она была отвергнута по следующим причинам:

1)  Сложность управления форматом файлов. Формат файлов XML (названия XML-элементов, порядок вложенности) определяется рекурсивным аглоритмом boost::serialization и почти не поддается изменению.

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

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

Идея использовать для этой цели именно расширение языка С++ и препроцессор заимствована из библиотеки Qt Library [2].

При реализации метаданных необходимо создавать качественно разные описатели типов (разные наследники IType) для разных типов данных, для чего эти типы необходимо различать. Остро стоял вопрос, в какой компоненте программного обеспечения должно быть реализовано это различение. Назначение этой задачи препроцессору требовало бы почти полного разбора синтаксиса С++ в нем, что вело к неприемлемому усложнению препроцессора. Назначение этой задачи подсистеме ввода-вывода усложняло бы ее, к тому же, требовалось бы извлечение из исходного описания класса данных на С++ и включение в метаданные необходимых для этого дополнительных сведений, искусственных и избыточных, расширение интерфейса взаимодействия. Обе альтернативы достаточно плохи. Найденное решение находится «посередине»: для детектирования типов элементов данных, выбора типа и создания элементов метаданных соответствующего типа, разработан специальный набор шаблонов С++. Подходяшие элементы из этого набора шаблонов автоматически инстанциируются между описателями полей в «Метаобъектах» и интерфейсами описателей полей и их типов IField и IType. Схема взаимодействия шаблонов с интерфейсами и «Метаобъектами» приведена на Рисунке 1.

Идею использовать шаблоны для определения типов данных и создания (подстановки) кода, зависящего от типа данных, в процессе компиляции, мы заимствовали из библиотеки boost::serialization [6]. Реализация – собственная, менее элегантная, но лучше ориентированная на решаемые задачи.

Предварительная реализация препроцессора DDL представляла собой не слишком сложный скрипт на языке Perl. Заключительная реализация построена путем модернизации программы moc (Meta Object Compiler) из библиотеки Qt Library [2].

Автоматизированная генерация кода методов доступа, генерации или обработки событий уже встречалась, например, в продуктах MS Visual Studio (для языка Basic), Borland Delphi (для языка Pascal), Borland C++ Builder (для языка С++).

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

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

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

Был проведен анализ доступных продуктов подобного рода, в частности, рассматривались: Stupid repeater, QuickTest PRO, Frologic Squish, AutoIt, TestComplete, UOPilot [13-18], и некоторые другие. Общий вывод анализа: такие средства автоматизации тестирования либо дороги, либо обладают ограниченными возможностями и разного рода неудобствами. По результатам анализа было принято решение о разработке собственного имитатора действий пользователя. Одним из аргументов в пользу такго решения была возможность полностью контролировать это инструментальное средство, подгонять под нужды проекта. Стоит отметить, что во время завершения работы над проектом, компания Nokia, чьей собственностью на данный момент является библиотека Qt, в рамках проекта Qt Extended опубликовала исходный код аналога «Обезьяны», который обладает рядом преимуществ по сравнению с указанными выше программными продуктами [19].

«Обезьяна» состоит из двух компонентов:

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

-  программа «front-end», имеющая интерактивный режим с ГИП и режим командной строки и управляющая агентом.

Для связи этих двух компонентов испольуются сокеты TCP/IP и специальный протокол связи.

Сценарий действий пользователя представляет собой скрипт на языке JavaScript (используется интерпретатор JavaScript, поставляемый в составе Qt Library [2]).

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

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

«Обезьяна» ориентирована на работу с элементами ГИП Qt Library [2] и не предназначена для тестирования приложений на основе других библиотек ГИП.

Проблемы автоматизации тестирования ГИП и их решения в имитаторе действия пользователя. Кратко остановимся на следующих проблемах:

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

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

Первая проблема была решена комплексно, объект ГИП, на который оказывает воздействие пользователь идентифицировался с помощью

-  свойства «имя», которым обладает любой объект из библиотеки Qt,

-  текстовых надписей на нем,

-  типа (кнопка, надпись, выпадающий список),

-  номера (если объект элемент списка, или дерева)

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

Вторая проблема, к сожалению, не была решена до конца, т. к. оптимальное решение требует поддержки со стороны ГИП библиотеки (как например в [19]).

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

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

-  построчное и посимвольное сравнение файлов,

-  построчное сравнение с применением регулярных выражений (регулярные выражения допускаются в эталоне),

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

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

Для реализации скриптов автоматизации тестирования использован язык Perl.

Для автоматизации исполнения тестового комплекта в целом использованы возможности программ cmake и ctest [3] и управляющие скрипты на входных языках этих программ.

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

В качестве архива исходных текстов использовалась система Subversion [7].

Для регистрации обнаруженных ошибок, а в ряде случаев – даже для краткосрочного планирования работ и контроля исполнения, использовалась система Bugzilla [8].

Для автоматической сборки проекта и запуска тестового комплекта в фоновом режиме, без участия оператора, использовался пакет Buildbot [9].

Для автоматизированной генерации справочников по программным элементам для разработчиков использовалась программа Doxygen [10].

Для оценки полноты тестового комплекта использовались возможности компилятора GNU C++ версии 4.1.1 и программа gcov [11], а для визуализации этих данных – программа lcov [12].

Заключение. Рассмотрен комплект инструментальных средств для обеспечения разработки сложного программного продукта с графическим пользовательским интерфейсом, предназначенного для использования на двух платформах – MS Windows и Linux. Этот комплект позволяет решать задачи по автоматизации разработки программного кода, автоматизации тестирования продукта, контроля качества продукта, управления проектом. Некоторые из инструментальных средств разработаны специально для этого проекта, и эти средства рассмотрены более подробно. В одном из таких средств – препроцессоре DDL – впервые реализована автоматическая генерация программного кода одновременно для метаданных, методов доступа, функций генерации и обработки событий, по описанию класса на языке, являющемся расширением С++.

Литература

1.  , , . Разработка интегрированной среды управления каналообразующими устройствами бортовых сетей / Современные информационные технологии и ИТ-образование. Под. ред. . М.: Макс-Пресс, 2006.

2.  Qt Library. http:///products

3.  CMake. http://www. cmake. org

4.  , , . Разработка устройств сопряжения мультиплексного канала обмена (MIL-STD-1553) / Современные информационные технологии и ИТ-образование. Под. ред. . М.: Макс-Пресс, 2005.

5.  Martin Fowler. GUI Architechtures. http://www. /eaaDev/uiArchs. html

6.  boost C++ libraries. http://www. boost. org/doc/

7.  Subversion. http://subversion. tigris. org/

8.  Bugzilla. http://www. bugzilla. org/

9.  Buildbot. http:///

10.  Doxygen. http://www. stack. nl/~dimitri/doxygen/

11.  gcov—a Test Coverage Program. http://gcc. gnu. org/onlinedocs/gcc/Gcov. html

12.  LCOV - the LTP GCOV extension. http://ltp. /coverage/lcov. php

13.  Stupid repeater. http://www. /download/development/software_developer/soft_442.html

14.  QuickTest PRO.
https:///cda/hpms/display/main/hpms_content. jsp? zn=bto&cp=^1352_4000_100__

15.  Frologic Squish. http://www. /pg? id=Home

16.  AutoIt. http://www. /

17.  TestComplete http://www. /products/testcomplete/

18.  UOPilot http://www. /uopilot. r.html

19.  QtUiTest.
http:///products/device-creation/qt-extended/files/pdf/qt-uitest-whitepaper