Development for Sailfish OS: Unit Testing Qt / C ++ for Sailfish OS

    Hello! This article is a continuation of a series of articles on testing Sailfish applications ( previous article ), and this time we will consider unit testing C ++ as part of projects for Sailfish OS.

    Test application


    So, we have an elementary example of an application for Sailfish OS, which is available in the repository of this project (how to create an application for Sailfish OS can be found in one of the previous articles ). The QML component contains a single welcome screen.

    import QtQuick 2.0
    import Sailfish.Silica 1.0
    ApplicationWindow 
    {
      Label  
      { 
        x: Theme.horizontalPageMargin 
        text: "Hello Sailors" 
        color: Theme.secondaryHighlightColor 
        font.pixelSize: Theme.fontSizeExtraLarge 
      }
    }
    

    In addition, there is one small class in C ++:
    class MyClass {
    public:
        MyClass();
        MyClass(int first, int second);
        int add() const;
        int multiply() const;
    private:
        int firstValue, secondValue;
    };
    

    Its meaning is simple - it stores 2 values ​​and finds their sum and product. This class we will test.

    Project creation


    Tests and the application itself must be placed as subdirectories of one project. To do this, when creating a new project, select "Project with subdirectories":


    Either in the already created * .pro file, specify TEMPLATE = subdirs . This template indicates that the project contains subdirectories. Now in the context menu of the main project it is possible to add subprojects:


    There are at least two such subprojects: one of them will be the application itself, the second - tests for it. When created using QtCreator, they are automatically added to the * .pro file as a subdirectory. If the application has already been created, its name can simply be added to the SUBDIRS variable in * .promanually. As a result, SUBDIRS will look something like this:
    SUBDIRS = \
    app \ 
    tests
    

    Let's move on to creating the tests themselves. For unit testing, Qt uses the QtTest framework, which was already mentioned in a previous article . To use it, you need to * .yaml project file, depending on assembly with the help of PkgConfig add Qt5Test.
    To create tests, in the * .pro subproject file containing the test suite, you must connect the testlib module :
    QT += testlib

    Also, you need to specify the files that will be tested. The easiest way to do this is to create a * .pri file in a subdirectory of the project, and specify the path to the tested classes in it:
    HEADERS += $$PWD/src/myclass.h 
    SOURCES += $$PWD/src/myclass.cpp 
    

    Next, you need to include it in * .pro application and project files with tests:
    INCLUDEPATH += ../app/
    include(../app/app.pri)
    

    The name of the subproject is indicated in TARGET . Later, a file with the same name will need to be run to run the tests.

    After that, the * .pro file of the test subproject will look something like this:
    TARGET = SailfishProjectTest
    CONFIG += sailfishapp qt c++11
    QT += testlib
    HEADERS += testmyclass.h
    SOURCES += testmyclass.cpp \
    nain.cpp
    INCLUDEPATH += ../app/
    include(../app/app.pri)
    

    Writing tests


    To write tests, a separate class is implemented that contains test scripts. It must be a descendant of the QObject class . The tests themselves are added as private slots of this class. Each of the slots will act as a test function.

    It should be noted that the QtTest library contains methods that allow you to configure data for tests before they are run, as well as tidy up after running the tests:
    • initTestCase () - is called before the first test function, if an error occurs in it, not a single test function will be executed.
    • cleanupTestCase () - called after all test functions have been executed.
    • init () - is called before each test function, if an error occurs in it, the subsequent test will not be performed.
    • cleanup () - called after each test function.

    Applying the information described above, you can get something like this class, which is responsible for testing our project:
    #include 
    #include "src/myclass.h"
    class TestMyClass : public QObject {
        Q_OBJECT
    private:
        MyClass myClass;
    private slots:
        void init();
        void testAdd();
        void testMultiply();
    };
    

    To compare the results of the function with the expected result, macro substitutions are used:
    • QVERIFY (condition) takes an expression as an argument and, if it is erroneous, displays a standard error message in the test log.
    • QVERIFY2 (condition, message) is similar to QVERIFY () , but displays the message specified in the arguments if the condition is false.
    • QTRY_VERIFY_WITH_TIMEOUT (condition, timeout) is similar to QVERIFY () , but repeats the comparison until the condition is true or until the time specified in the second argument expires.
    • QTRY_VERIFY2_WITH_TIMEOUT (condition, message, timeout) is similar to QVERIFY2 () , repeats the comparison in the same way as QTRY_VERIFY_WITH_TIMEOUT () .
    • QTRY_VERIFY (condition) , QTRY_VERIFY2 (condition, message) are similar to those described above, but with a timer of 5 seconds.
    • QCOMPARE (actual, expected) gives more detailed information about the failed test. The result of the function execution and the expected result are passed as arguments; if they do not match, then both of these values ​​are displayed in the testing log.
    • QTRY_COMPARE_WITH_TIMEOUT (actual, expected, timeout) is similar to QCOMPARE () , but repeats the comparison until the value is correct, or until the specified time in milliseconds is reached.
    • QTRY_COMPARE (actual, expected) is the same as QTRY_COMPARE_WITH_TIMEOUT () , but with a timer of 5 seconds.

    More information on macros can be found in the QTest documentation .

    We will use the information above to write our test functions:
    #include 
    #include "src/myclass.h"
    #include "testmyclass.h"
    void TestMyClass::init() {
        myClass =  MyClass(4, 2);
    }
    void TestMyClass::testAdd() {
        QCOMPARE(myClass.add(), 6);
    }
    void TestMyClass::testMultiply() {
        QCOMPARE(myClass.multiply(), 8);
    }
    

    Since in our project the testing class is divided into .h and .cpp files, at this step the process of writing unit tests ends. However, if the .h file is missing and the entire class is fully described in the .cpp file, then you need to include the automatically generated .moc file. Eg #include "testmyclass.moc".

    The last thing to do is organize an entry point to run the tests. To do this , one of three macros is used in the * .cpp class file with tests, or in a separate main.cpp : QTEST_MAIN () / QTEST_APPLESS_MAIN () / QTEST_GUILESS_MAIN (). As an argument, they pass the name of the test class. Each of the macros declares a main () function , so it can only be used once in a subproject. Different classes with unit tests should be placed in separate subprojects.

    Running tests


    So, the project is ready, we start it from the environment. After a successful launch on the device, a file with the name specified in TARGET will appear in the / usr / bin directory . Just execute this file.
    *********Start testing of TestMyClass *********
    Config: Using QtTest library 5.2.2 Qt 5.2.2
    PASS    : TestMyClass::initTestCase()
    PASS    : TestMyClass::testAdd()
    PASS    : TestMyClass::testMultiply()
    PASS    : TestMyClass::cleanupTestCase()
    Totals: 4 passed, 0 failed, 0 skipped
    ********Finish testing of TestMyClass *********
    

    Conclusion


    This article looked at a way to write unit tests to test applications for the Sailfish OS platform. As an example, we considered a simple application whose sources (along with tests) are available on GitHub .

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

    Author: Maxim Kosterin

    Also popular now: