Unit Tests#

Basics#

This section covers the implementation of Qt Unit Tests along with CMake (and not QMake).
The advantage of Qt is to build UIs. The main goal here is to be able to launch tests without having the user to interact with it.

Preparation#

First, we create a folder along the source of the program, containing one or multiple .cpp files for all our tests:

project
├── bin
├── res
├── src
│   ├── main.cpp
│   └── myinclude.h
├── tests
│   ├── mytest1.cpp
│   └── mytest2.cpp
└── CMakeLists.txt

We then need to modify our CMakeLists.txt to include the test files (note that only one global CMakeLists is used - it would be better to have one CMakeLists per level for bigger projects).

 1cmake_minimum_required(VERSION 2.8.11 FATAL_ERROR)
 2#set(CMAKE_PREFIX_PATH "D:/Qt/5.15.1/msvc2019_64")
 3
 4project(MyProject)
 5
 6enable_testing() # Add this for tests activation
 7
 8set(CMAKE_AUTOMOC ON) # For meta object compiler
 9set(CMAKE_AUTORCC ON) # Resource files
10set(CMAKE_AUTOUIC ON) # UI files
11set(CMAKE_INCLUDE_CURRENT_DIR ON)
12
13# Find the QtWidgets library
14find_package(Qt5 REQUIRED Core Widgets)
15find_package(Qt5Test REQUIRED) # Add this for tests activation
16
17file(GLOB project_SOURCES
18        "src/*.h"
19        "src/*.hpp"
20        "res/*.qrc"
21        )
22
23#Main app
24add_executable(${PROJECT_NAME} "src/main.cpp" ${project_SOURCES})
25target_link_libraries(${PROJECT_NAME} Qt5::Widgets)
26#Unit tests
27add_executable(MyTest1 "tests/mytest1.cpp" ${project_SOURCES}) # needed sources
28target_link_libraries(MyTest1 Qt5::Widgets Qt5::Test) # needed includes for test, with Qt5::Test added

Once the project is set, we can dig in the implementation of our tests.

Tests implementation#

A typical test file will look like this, having all tests implemented as private slots :

 1#include <QTest> // Needed to test
 2// Include the files required to do tests
 3#include "src/myinclude.h"
 4
 5class MyTest1 : public QObject
 6{
 7    Q_OBJECT
 8private slots:
 9    void test1()
10    {
11        QVERIFY(1 > 0); // will pass
12    }
13    void test2()
14    {
15        QVERIFY(1 > 0); // pass
16        QVERIFY(0 > 1); // fail
17        // => finally, test2 did not pass
18    }
19};
20//Include those final files AFTER the class to create a stand-alone exec
21QTEST_MAIN(MyTest1)
22#include "mytest1.moc" // auto-generated by Qt
It is possible to test multiple conditions inside the same test. If any of those fails, the test will be considered as failed.
Running the MyTest1 project will provide informations in the output log if tests pass or fail. Taken from Qt website, a typical output is like :
1********* Start testing of TestQString *********
2Config: Using QtTest library %VERSION%, Qt %VERSION%
3PASS   : TestQString::initTestCase()
4PASS   : TestQString::toUpper()
5PASS   : TestQString::cleanupTestCase()
6Totals: 3 passed, 0 failed, 0 skipped
7********* Finished testing of TestQString *********

Multiple tests#

When it is required to do the same test multiples times, it can quickly become unreadable and repetitive to type everything by hand.
To automate this, you need to create a new slot with the same method name, adding _data() to it.
We will here create an array containing all our data, and that will be automatically fed to our test (that needs to be rewrote) :
 1void test1()
 2{
 3        QFETCH(int, mybigint);
 4        QFETCH(int, mysmallint);
 5    QVERIFY(mybigint > mysmallint);
 6}
 7void test1_data()
 8{
 9        // Create columns
10        QTest::addColumn<int>("mybigint");
11        QTest::addColumn<int>("mysmallint");
12        // Fill with rows
13        QTest::newRown("bigger") << 5 << 2;
14        QTest::newRown("bigger") << 5 << 5;
15        QTest::newRown("bigger") << 2 << 5;
16        // ...
17}

GUI Events#

It is possible to simulate GUI events while including the correct headers.
Similarly, a list of events can be passed to the tests.
Please check under here and here.

Benchmark#

It is possible to add a benchmark for parts of code that need measurement.
You simply need to use the QBENCHMARK{my code here} macro.

Skipping a test#

If you need to do the tests in specific conditions only, you can use the QSKIP("Error message") macro.
Once encountered, the test will be skipped (e.g. if you need to have a specific revision of a lib to do the tests …).
If the macro is put into the _data() function, it will skip all tests if reached.

Misc. Macros#

QBENCHMARK{my code} : to benchmark a part of code
QBENCHMARK_ONCE{my code} : to run the benchmark with only one pass
QCOMPARE(actual, expected) : compare two variables; print both values to see the error when any occurs
QEXPECT_FAIL(dataIndex, comment, mode) : tells the next test will fail (due to a revision to be done, an error still persisting …); dataIndex is for which test index (when using _data() function), else set as empty string / comment is what is output when encoutered / mode is Continue or Abort, depending if the rest of the code must be run or not
QFAIL(message) : force a failure
QFETCH(type, name) : to fetch values from a _data() function
QFETCH_GLOBAL(type, name) : to fetch values from a _data() function that does not correspond to current test
QFINDTESTDATE(filename) : look for given filepath and return a string with full path (empty if none found)
QSKIP(description) : skip the test
QTEST(actual, testElement) : same as QCOMPARE
QVERIFY(condition) : check the condition is true
QVERIFY(condition, message) : as QVERIFY, but outputs the message when condition is false
QVERIFY_EXCEPTION_THROWN(expression, exceptiontype) : execute the expression, and tries to catch the exceptiontype - if corresponds (or is subclass of), the test continues
QWARN(message) : appends a warning message to the output

QT C++ Cmake Unittest