Разработка для Sailfish OS: Тестирование QML-кода, зависящего от C++ в Sailfish OS

в 15:51, , рубрики: QML, qt, sailfish os, testing, разработка мобильных приложений, Тестирование мобильных приложений

Здравствуйте! Данная статья является продолжением цикла статей, посвященных разработке для мобильной платформы Sailfish OS и их тестированию. Одна из предыдущих статей была посвящена тестированию QML-компонентов приложения. Однако, часто разработчики сталкиваются с потребностью написания собственных компонентов на языке C++ для использования функционала, недоступного из QML, или для улучшения производительности. Об этом также уже было написано. Тестирование таких компонентов отличается от тестирования уже существующих. В данной статье мы расскажем, как тестировать собственные QML-компоненты, написанные на языке C++.

Тестируемое приложение

В качестве приложение возьмём то же самое, что брали в предыдущей статье — приложение-счётчик (исходный код приложения доступен на 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 или группе ВКонтакте.

Автор: Сергей Аверкиев

Автор: FRUCT

Источник

Поделиться

* - обязательные к заполнению поля