📜 ⬆️ ⬇️

Development for Sailfish OS: Testing QML code dependent on C ++ in Sailfish OS

Hello! This article is a continuation of a series of articles on the development and testing of the Sailfish OS mobile platform. One of the previous articles was devoted to testing QML-components of the application. However, developers often face the need to write their own components in C ++ to use functionality not available from QML or to improve performance. This has also been written . Testing such components is different from testing existing ones. In this article we will explain how to test your own QML components written in C ++.

Test application


As an application, we take the same thing that was taken in the previous article - the application counter (the source code of the application is available on GitHub ). In the current implementation, the counter value is stored in the page property. This value changes when you click the “add” and “reset” buttons and is used to display it in a view.

We will modify the application so that the value is stored and changed using code written in C ++. Add the class Counter , which has the following form:

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

This class is inherited from QObject and contains in its body the Q_OBJECT macro, which allows you to export it to QML. The class contains the only read property, count . To change the value of this property, use the incrementCount () and resetCount () methods. Methods are marked with the Q_INVOKABLE macro so that they can 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 main () function of the application. In this case, we can not do this. The fact is that qmltestrunner does not call the main function of the application when running tests.

QML plugin


To solve the problem described above, you can use the QML-plugin . The idea is to allocate C ++ code to the library and use it when running the application and tests. The registration of QML types in this case is transferred to the QML plugin.

In order to create a plugin, you need to divide the project into two parts. Under each of the parts we will create directories with subprojects containing a * .pro file with the name corresponding to the directory name. The first subproject is called core , it will contain all C ++ code, except for the * .cpp file with the main () function. The second is an app , it will contain QML files, a * .desktop file and files with translations. Here we put * .cpp file with the function main () .

The core.pro file looks like this:

 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 core.pro file, you must use TEMPLATE = lib so that the subproject is used as a library. The purpose of the library ( TARGET ) is its name, i.e. core . You must add a plugin to CONFIG to indicate that the library is connecting as a plugin. In DISTFILES, you must add the path to the qmldir file, which should be located in the core directory and contain the name of the module and the plugin. In our case there will be the following contents of this file:

 module counter.cpp.application.Core plugin core 

At the end of the core.pro file, you must specify the path to the library files and the qmldir file. The library is placed in the / usr / lib / directory, and the path to the plugin is set to counter / cpp / application / Core .

The app.pro file contains the configuration of the application itself. Here you can see the target application, paths to qml files, icons and translations. Here we also add a * .cpp file with the main function. In our example, the file is as follows:

 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 you need to change the * .pro project file so that it includes the two subprojects we created. To do this, add TEMPLATE = subdirs to this file and add our directories as subprojects. In this case, the subprojects located in these directories will be collected in turn. It is also necessary to leave the addition of the project files from the rpm directory, since they must always be in the root of the main project. For our application, it looks like this:

 TEMPLATE = subdirs OTHER_FILES += $$files(rpm/*) SUBDIRS += \ app \ core app.depends = core 

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

Now that the project structure has been prepared, you can start implementing the plugin. You need to create a C ++ class, which we will call CorePlugin . The class must be inherited from QQmlExtensionPlugin . It will register the types and initialize the engine.

In the QQmlExtensionPlugin class, there are two methods that you can override:


In our case, so far only the first method is sufficient. Use it to register the class Counter . In the body of the registerTypes () method we place the following:

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

The name of the class to be registered is indicated in angle brackets. The first parameter is the URI of the plugin. The second and third parameters are the major and minor version numbers. The fourth parameter is the name under which our class from QML will be available. Now you can use the Counter type in our application.

Using your own QML component


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

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

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

 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 instances of the application and view. Then we specify the path to our library, the path to the main QML file of the application and display the view on full screen. In the end, we set up the handler for closing the application and launch it. Now you can import the created library into QML files and use the Counter type.

The basis is the CounerPage.qml file from the previous article. Add our plugin as follows:

 import counter.cpp.application.Core 1.0 

It uses the URI previously specified in the qmldirs file, and version 1.0 specified during type registration. To use the type, add it inside the page:

 Counter { id: counter } 

Now, instead of changing the value of the count property, we will call the counter.increment () and counter.reset () methods on adding and resetting the counter, respectively.
The test code remains the same as in the previous article, since we did not change the visual components.

Running tests


The tests are run using qmltestrunner . Since part of the code is transferred to the 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 the tests. As a result, for the application in question, it will look like this:

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

The test code and the output of the results remain the same as in the previous article :

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

Conclusion


Testing your own QML components is different from testing existing ones in the QtQuick library. To realize the possibility of testing them, we had to select all C ++ code in the QML plugin and connect it as a library. As a result, the code for testing is no different from the one that would test standard QML components. Nevertheless, the project itself required significant changes. The application in this form can already be used when publishing in the store. Sample source code is available on GitHub .

Technical issues can also be discussed on the Sailfish OS Russian-speaking community channel in a Telegram or VKontakte group .

Author: Sergey Averkiev

Source: https://habr.com/ru/post/325936/


All Articles