Здравствуйте! Данная статья является продолжением цикла статей, посвященных разработке для мобильной платформы Sailfish OS и их тестированию. Одна из предыдущих статей была посвящена тестированию QML-компонентов приложения. Однако, часто разработчики сталкиваются с потребностью написания собственных компонентов на языке C++ для использования функционала, недоступного из QML, или для улучшения производительности. Об этом также уже было написано. Тестирование таких компонентов отличается от тестирования уже существующих. В данной статье мы расскажем, как тестировать собственные QML-компоненты, написанные на языке C++.
В качестве приложение возьмём то же самое, что брали в предыдущей статье — приложение-счётчик (исходный код приложения доступен на GitHub). В текущей реализации значение счётчика хранится в свойстве страницы. Это значение изменяется при нажатии на кнопки «add» и «reset» и используется для отображения на виде.
Мы изменим приложение таким образом, чтобы значение хранилось и изменялось с помощью кода, написанного на C++. Добавим класс Counter, имеющий следующий вид:
Данный класс унаследован от QObject и содержит в своём теле макрос Q_OBJECT, что позволяет экспортировать его в QML. Класс содержит единственное свойство count, доступное только для чтения. Для изменения значения этого свойства используются методы incrementCount() и resetCount(). Методы помечены макросом Q_INVOKABLE, для того, чтобы их можно было вызывать из QML.
Для использования классов, написанных на C++ в QML, необходимо зарегистрировать класс как QML-тип. Обычно это делается в теле функции main() приложения. В данном случае мы этого сделать не можем. Дело в том, что qmltestrunner не вызывает главную функцию приложения при запуске тестов.
Решить проблему, описанную выше, можно с помощью QML-плагина. Идея состоит в том, чтобы выделить C++ код в библиотеку и использовать её при запуске приложения и тестов. Регистрация QML-типов в данном случае переносится в QML-плагин.
Для того, чтобы создать плагин, необходимо разделить проект на две части. Под каждую из частей создадим директории с подпроектами, содержащими файл *.pro с названием, соответствующим названию директории. Первый подпроект назовём core, он будет содержать весь C++ код, кроме файла *.cpp с функцией main(). Второй — app, он будет содержать QML-файлы, файл *.desktop и файлы с переводами. Сюда же поместим *.cpp файл с функцией main().
Файл core.pro выглядит следующим образом:
В файле core.pro необходимо использовать TEMPLATE = lib, чтобы подпроект использовался как библиотека. Целью библиотеки (TARGET) указываем её название, т.е. core. В CONFIG обязательно добавляем plugin, чтобы указать, что библиотека подключается в виде плагина. В DISTFILES необходимо добавить путь к файлу qmldir, который должен быть расположен в директории core и содержать название модуля и плагина. В нашем случае будет следующее содержимое этого файла:
В конце файла core.pro необходимо указать путь к файлам библиотеки и к файлу qmldir. Библиотеку помещаем в директорию /usr/lib/, а путь к плагину указываем counter/cpp/application/Core.
Файл app.pro содержит конфигурацию самого приложения. Здесь указывается целевое приложение, пути к файлам qml, иконок и переводов. Сюда же добавляем *.cpp файл с главной функцией. В нашем примере файл имеет следующий вид:
Теперь необходимо изменить *.pro файл проекта, чтобы он включал в себя два созданных нами подпроекта. Для этого добавим в данный файл TEMPLATE = subdirs и добавим наши директории как подпроекты. В таком случае по очереди будут собраны подпроекты, находящиеся в данных директориях. Здесь же необходимо оставить добавление файлов проекта из директории rpm, так как они всегда должны находиться в корне основного проекта. Для нашего приложения это выглядит так:
Здесь же мы указали, что подпроект app зависит от core.
Теперь, когда структура проекта подготовлена, можно приступать к реализации плагина. Необходимо создать C++ класс, который мы назовём CorePlugin. Класс должен быть унаследован от QQmlExtensionPlugin. В нём будет производиться регистрация типов и инициализация движка.
В классе QQmlExtensionPlugin есть два метода, которые можно переопределить:
В нашем случае пока достаточно только первого метода. Используем его для регистрации класса Counter. В тело метода registerTypes() поместим следующее:
В угловых скобках указывается имя регистрируемого класса. Первым параметром передаётся URI плагина. Вторым и третьим параметрами передаются старший и младший номера версий. Четвёртым параметром передаётся имя, под которым будет доступен наш класс из QML. Теперь можно использовать тип Counter в нашем приложении.
Теперь нам необходимо указать путь к библиотеке в главной функции приложения. Для этого нам необходимо вручную инициализировать приложение и вид. В обычном случае инициализация производится следующим образом:
Изменим код функции main() для того, чтобы указать путь к библиотеке:
Здесь мы создаём экземпляры приложения и вида. Затем указываем путь к нашей библиотеке, путь к главному QML-файлу приложения и отображаем вид на весь экран. В конце концов настраиваем обработчик закрытия приложения и запускаем его. Теперь можно импортировать созданную библиотеку в QML-файлах и использовать тип Counter.
За основу возьмём файл CounerPage.qml из предыдущей статьи. Добавим наш плагин следующим образом:
Здесь используется URI, ранее указанный в файле qmldirs, и версии 1.0 указанные при регистрации типа. Для использования типа добавим его внутри страницы:
Теперь вместо изменения значения свойства count будем вызывать методы counter.increment() и counter.reset() при добавлении и сбросе счётчика соответственно.
Код тестов при этом остаётся тем же самым, что и в предыдущей статье, так как мы не меняли визуальные компоненты.
Запуск тестов происходит с помощью qmltestrunner. Так как часть кода перешла в QML-плагин, то нам необходимо вручную указывать путь к нему. Для этого используется переменная QML2_IMPORT_PATH, которой присваивается путь к файлам библиотеки перед запуском тестов. В итоге для рассматриваемого приложение это будет выглядеть так:
Код тестов и вывод результатов остаётся тем же самым, что и в предыдущей статье:
Тестирование собственных QML-компонентов отличается от тестирования уже имеющихся в библиотеке QtQuick. Для реализации возможности их тестирования нам пришлось выделить весь C++ код в QML-плагин и подключать его как библиотеку. В результате код для тестирования не отличается от того, который тестировал бы стандартные QML-компоненты. Тем не менее сам проект потребовал значительных изменений. Приложение в таком виде уже можно использовать при публикации в магазине. Исходный код примера доступен на GitHub.
Технические вопросы можно также обсудить на канале русскоязычного сообщества Sailfish OS в Telegram или группе ВКонтакте.
Автор: Сергей Аверкиев
Тестируемое приложение
В качестве приложение возьмём то же самое, что брали в предыдущей статье — приложение-счётчик (исходный код приложения доступен на GitHub). В текущей реализации значение счётчика хранится в свойстве страницы. Это значение изменяется при нажатии на кнопки «add» и «reset» и используется для отображения на виде.
Мы изменим приложение таким образом, чтобы значение хранилось и изменялось с помощью кода, написанного на C++. Добавим класс Counter, имеющий следующий вид:
// counter.h
#include <QObject>
class Counter : public QObject {
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
private:
int m_count = 0;
public:
int count();
Q_INVOKABLE void incrementCount();
Q_INVOKABLE void resetCount();
signals:
void countChanged();
};
// counter.cpp
int Counter::count() {
return m_count;
}
void Counter::incrementCount() {
m_count++;
emit countChanged();
}
void Counter::resetCount() {
m_count = 0;
emit countChanged();
}
Данный класс унаследован от QObject и содержит в своём теле макрос Q_OBJECT, что позволяет экспортировать его в QML. Класс содержит единственное свойство count, доступное только для чтения. Для изменения значения этого свойства используются методы incrementCount() и resetCount(). Методы помечены макросом Q_INVOKABLE, для того, чтобы их можно было вызывать из QML.
Для использования классов, написанных на C++ в QML, необходимо зарегистрировать класс как QML-тип. Обычно это делается в теле функции main() приложения. В данном случае мы этого сделать не можем. Дело в том, что qmltestrunner не вызывает главную функцию приложения при запуске тестов.
QML-плагин
Решить проблему, описанную выше, можно с помощью QML-плагина. Идея состоит в том, чтобы выделить C++ код в библиотеку и использовать её при запуске приложения и тестов. Регистрация QML-типов в данном случае переносится в QML-плагин.
Для того, чтобы создать плагин, необходимо разделить проект на две части. Под каждую из частей создадим директории с подпроектами, содержащими файл *.pro с названием, соответствующим названию директории. Первый подпроект назовём core, он будет содержать весь C++ код, кроме файла *.cpp с функцией main(). Второй — app, он будет содержать QML-файлы, файл *.desktop и файлы с переводами. Сюда же поместим *.cpp файл с функцией main().
Файл core.pro выглядит следующим образом:
TEMPLATE = lib
TARGET = core
CONFIG += qt plugin c++11
QT += qml quick
HEADERS += counter.h
SOURCES += counter.cpp
DISTFILES += qmldir
uri = counter.cpp.application.Core
qmldir.files = qmldir
installPath = /usr/lib/counter-cpp-application/$$replace(uri, \\., /)
qmldir.path = $$installPath
target.path = $$installPath
INSTALLS += target qmldir
В файле core.pro необходимо использовать TEMPLATE = lib, чтобы подпроект использовался как библиотека. Целью библиотеки (TARGET) указываем её название, т.е. core. В CONFIG обязательно добавляем plugin, чтобы указать, что библиотека подключается в виде плагина. В DISTFILES необходимо добавить путь к файлу qmldir, который должен быть расположен в директории core и содержать название модуля и плагина. В нашем случае будет следующее содержимое этого файла:
module counter.cpp.application.Core
plugin core
В конце файла core.pro необходимо указать путь к файлам библиотеки и к файлу qmldir. Библиотеку помещаем в директорию /usr/lib/, а путь к плагину указываем counter/cpp/application/Core.
Файл app.pro содержит конфигурацию самого приложения. Здесь указывается целевое приложение, пути к файлам qml, иконок и переводов. Сюда же добавляем *.cpp файл с главной функцией. В нашем примере файл имеет следующий вид:
TARGET = counter-cpp-application
CONFIG += sailfishapp sailfishapp_i18n c++11
SOURCES += src/counter-cpp-application.cpp
OTHER_FILES += qml/counter-cpp-application.qml qml/cover/CoverPage.qml translations/*.ts counter-cpp-application.desktop
TRANSLATIONS += translations/counter-cpp-application-de.ts
SAILFISHAPP_ICONS = 86x86 108x108 128x128 256x256
DISTFILES += qml/CounterCppApplication.qml qml/pages/CounterPage.qml
Теперь необходимо изменить *.pro файл проекта, чтобы он включал в себя два созданных нами подпроекта. Для этого добавим в данный файл TEMPLATE = subdirs и добавим наши директории как подпроекты. В таком случае по очереди будут собраны подпроекты, находящиеся в данных директориях. Здесь же необходимо оставить добавление файлов проекта из директории rpm, так как они всегда должны находиться в корне основного проекта. Для нашего приложения это выглядит так:
TEMPLATE = subdirs
OTHER_FILES += $$files(rpm/*)
SUBDIRS += app core
app.depends = core
Здесь же мы указали, что подпроект app зависит от core.
Теперь, когда структура проекта подготовлена, можно приступать к реализации плагина. Необходимо создать C++ класс, который мы назовём CorePlugin. Класс должен быть унаследован от QQmlExtensionPlugin. В нём будет производиться регистрация типов и инициализация движка.
В классе QQmlExtensionPlugin есть два метода, которые можно переопределить:
- registerTypes(const char *uri): нужен для регистрации наших QML-типов. В качестве параметре методу передаётся URI нашего плагина, который указывается в файле qmldirs.
- initializeEngine(QQmlEngine *engine, const char *uri): нужен для инициализации движка нашего приложения. Первый параметр является QML-движком, с помощью которого можно настроить приложение, второй — URI плагина.
В нашем случае пока достаточно только первого метода. Используем его для регистрации класса Counter. В тело метода registerTypes() поместим следующее:
qmlRegisterType<Counter>(uri, 1, 0, "Counter");
В угловых скобках указывается имя регистрируемого класса. Первым параметром передаётся URI плагина. Вторым и третьим параметрами передаются старший и младший номера версий. Четвёртым параметром передаётся имя, под которым будет доступен наш класс из QML. Теперь можно использовать тип Counter в нашем приложении.
Использование собственного QML-компонента
Теперь нам необходимо указать путь к библиотеке в главной функции приложения. Для этого нам необходимо вручную инициализировать приложение и вид. В обычном случае инициализация производится следующим образом:
int main(int argc, char *argv[]) {
return SailfishApp::main(argc, argv);
}
Изменим код функции main() для того, чтобы указать путь к библиотеке:
int main(int argc, char *argv[]) {
QGuiApplication* app = SailfishApp::application(argc, argv);
QQuickView* view = SailfishApp::createView();
view->engine()->addImportPath("/usr/lib/counter-cpp-application/");
view->setSource(SailfishApp::pathTo("qml/counter-cpp-application.qml"));
view->showFullScreen();
QObject::connect(view->engine(), &QQmlEngine::quit, app, &QGuiApplication::quit);
return app->exec();
}
Здесь мы создаём экземпляры приложения и вида. Затем указываем путь к нашей библиотеке, путь к главному QML-файлу приложения и отображаем вид на весь экран. В конце концов настраиваем обработчик закрытия приложения и запускаем его. Теперь можно импортировать созданную библиотеку в QML-файлах и использовать тип Counter.
За основу возьмём файл CounerPage.qml из предыдущей статьи. Добавим наш плагин следующим образом:
import counter.cpp.application.Core 1.0
Здесь используется URI, ранее указанный в файле qmldirs, и версии 1.0 указанные при регистрации типа. Для использования типа добавим его внутри страницы:
Counter {
id: counter
}
Теперь вместо изменения значения свойства count будем вызывать методы counter.increment() и counter.reset() при добавлении и сбросе счётчика соответственно.
Код тестов при этом остаётся тем же самым, что и в предыдущей статье, так как мы не меняли визуальные компоненты.
Запуск тестов
Запуск тестов происходит с помощью qmltestrunner. Так как часть кода перешла в QML-плагин, то нам необходимо вручную указывать путь к нему. Для этого используется переменная QML2_IMPORT_PATH, которой присваивается путь к файлам библиотеки перед запуском тестов. В итоге для рассматриваемого приложение это будет выглядеть так:
QML2_IMPORT_PATH=/usr/lib/counter-cpp-application/ /usr/lib/qt5/bin/qmltestrunner -input /usr/share/counter-cpp-application/tests/
Код тестов и вывод результатов остаётся тем же самым, что и в предыдущей статье:
********* Start testing of qmltestrunner *********
Config: Using QtTest library 5.2.2, Qt 5.2.2
PASS : qmltestrunner::Counter tests::initTestCase()
PASS : qmltestrunner::Counter tests::test_counterAdd()
PASS : qmltestrunner::Counter tests::test_counterReset()
PASS : qmltestrunner::Counter tests::cleanupTestCase()
Totals: 4 passed, 0 failed, 0 skipped
********* Finished testing of qmltestrunner *********
Заключение
Тестирование собственных QML-компонентов отличается от тестирования уже имеющихся в библиотеке QtQuick. Для реализации возможности их тестирования нам пришлось выделить весь C++ код в QML-плагин и подключать его как библиотеку. В результате код для тестирования не отличается от того, который тестировал бы стандартные QML-компоненты. Тем не менее сам проект потребовал значительных изменений. Приложение в таком виде уже можно использовать при публикации в магазине. Исходный код примера доступен на GitHub.
Технические вопросы можно также обсудить на канале русскоязычного сообщества Sailfish OS в Telegram или группе ВКонтакте.
Автор: Сергей Аверкиев
Поделиться с друзьями