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
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
the
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
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
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
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
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
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
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
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
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
Change the code of the function main() in order to specify the library path:
the
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
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
Instead of changing the value of count we will call the methods counter.increment() and counter.reset() when you add and reset, respectively.
the
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
The code for the tests and displaying the results remains the same as in previous article:
the
the
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
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
Комментарии
Отправить комментарий