Developing for Sailfish OS: Testing QML code that depends on C++ in Sailfish OS

Hello! This article is a continuation of the series of articles dedicated to developing for the mobile platform Sailfish OS and testing them. One of the previous articles was devoted to testing the QML-components of the application. However, developers often face the need of writing your own components in C++ to use functionality that is not accessible from QML, or to improve performance. This is also already written. Testing of such components is different from testing is already available. In this article we will tell you how to test your own QML components, written in C++.

the

Test application


As the application will take the same thing that took previous article — application counter (application source code available on GitHub). In the current implementation, the counter value is stored in a property page. This value can be changed by clicking on the button "add" and "reset" and is used to display on the form.

We change the application so that the value is stored and changed with code written in C++. Add the class Counter, having the following form:

the
// 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();
};


the
// counter.cpp
int Counter::count() {
return m_count;
}

void Counter::incrementCount() {
m_count++;
emit countChanged();
}

void Counter::resetCount() {
m_count = 0;
emit countChanged();
}

This class is inherited from the QObject and contains in its body the macro Q_OBJECT, which allows you to export it to QML. The class contains a single property count, read-only. To change the value of this property are used incrementCount() and resetCount(). Methods marked with the macro Q_INVOKABLE, in order to be called from QML.

To use classes written in C++ in QML, you must register the class as a QML type. This is usually done in the body of the function main() applications. In this case, we can't do this. The fact that qmltestrunner does not cause the main function of the application when the tests are run.

the

QML-plugin


To solve the problem described above by using QML-plugin. The idea is to highlight C++ code into a library and use it at application startup and tests. Register the QML types in the given case is transferred to QML-plugin.

In order to create a plugin, you need to divide the project into two parts. Under each part, create a directory subproject that contains the file *.pro with the name specified in the directory name. The first subproject will call core, it will contain all the C++ code, but the file *.cpp main(). Second app, it will contain QML files, the file *.desktop and files with translations. This will place *.cpp file with the function main().

The file core.pro as follows:

the
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

In the file core.pro you must use TEMPLATE = lib to a subproject was used as a library. The purpose of the library (TARGET) specify the name, i.e., core. In CONFIG will add plugin, to indicate that the library is included in plugin. In DISTFILES you need to add the path to the file qmldir, which must be located in the directory core to contain the name of the module and the plugin. In our case, the following will be the contents of this file:

the
module counter.cpp.application.Core
plugin core

At the end of the file core.pro, you must specify the path to the library files and to the file qmldir. The library is placed in the directory /usr/lib/, and the path to the specified plugin counter/cpp/application/Core.
The file app.pro contains the configuration of the application itself. Specifies the target application, the path to the qml files, icons, and translations. We should also add here *.cpp file with the main function. In our example, the following file:

the
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 

Now I want to change *.pro project file to include the two created subproject. This will add to the file TEMPLATE = subdirs and add our directory as a subproject. In this case, the queue will be collected by the subprojects, located in the data directories. You also have to keep adding the project files from the directory rpm, as they always should be in the root of the main project. For our application it looks like this:

the
TEMPLATE = subdirs

OTHER_FILES += $$files(rpm/*)

SUBDIRS += \
app \
core

app.depends = core

Here we indicated that the subproject app depends on the core.

Now that the structure of the draft prepared, you can start to implement the plugin. You need to create a C++ class, which we call CorePlugin. The class must be inherited from QQmlExtensionPlugin. It will be recorded types and engine initialization.

In the class QQmlExtensionPlugin there are two methods that can be overridden:

the

    registerTypes(const char *uri): we need to register our QML types. As the URI parameter method is passed to our plugin, which is specified in the file qmldirs.

    initializeEngine(QQmlEngine *engine, const char *uri): we need to initialize the engine of our application. The first parameter is the QML engine, which you can use to configure the application, the second URI of the plugin.


In our case, while only enough of the first method. Use it to register the class Counter. In the body of the method registerTypes() put the following:

the
qmlRegisterType<Counter>(uri, 1, 0, "Counter");

In angle brackets indicates the name of the registered class. The first parameter is passed the URI of the plugin. The second and third parameters are the major and minor version numbers. The fourth parameter is passed the name that will be available to our class from QML. You can now use the Counter type in our application.

the

Use your own QML component


Now we need to specify the library path in the main function of the application. To do this we need to manually initialize the application and look. In the usual case, the initialization is as follows:

the
int main(int argc, char *argv[]) {
return SailfishApp::main(argc, argv);
}

Change the code of the function main() in order to specify the library path:

the
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();
}

Here we create application instances and species. Then specify the path to our library, the path to the main QML file of the application and display the view on the screen. In the end, the custom handler close the application and run it. You can now import the created library in the QML file and use the type Counter.

As a basis we take the file CounerPage.qml from the previous article. Add our plugin as follows:

the
import-counter.cpp.application.Core 1.0

Here we use the URI specified in the file qmldirs, and version 1.0 specified when registering the type. To use type will add it inside the page:

the
Counter {
id: counter
}

Instead of changing the value of count we will call the methods counter.increment() and counter.reset() when you add and reset, respectively.

the

Running tests


The test run takes place using the qmltestrunner. As part of the code moved to QML-plugin, we need to manually specify the path to it. To do this, use the variable QML2_IMPORT_PATH, which is assigned the path to the library files before running tests. In the end, for the application it will look like this:

the
QML2_IMPORT_PATH=/usr/lib/counter-cpp-application/ /usr/lib/qt5/bin/qmltestrunner -input /usr/share/counter-cpp-application/tests/

The code for the tests and displaying the results remains the same as in previous article:

the
********* 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 *********

the

Conclusion


Testing your own QML components is different from testing is already available in the QtQuick library. For a test we had to select the whole C++ code to QML-plugin and connect it as a library. The code for testing does not differ from that which would have tested the standard QML components. However, the project required substantial changes. The application in this form can already be used when publishing in the store. Source code for the example available in GitHub.

Technical questions can be discussed on channel Russian-speaking community of Sailfish OS in Telegram or Facebook page.

Author: Sergey Averkiev
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Fresh hay from the cow, or 3000 icons submitted!

Knowledge base. Part 2. Freebase: make requests to the Google Knowledge Graph

Group edit the resources (documents) using MIGXDB