C++ unit testing with Qt Test – part 1 – introduction

This tutorial is an introduction to C++ unit testing with Qt Test. A working example is discussed and analysed in detail. Full Qt Creator project and source code are provided.

C++ unit testing with Qt Test

Qt Test is a framework for C++ unit testing. It is part of Qt, which means it includes features to test Qt GUIs and other Qt elements like signals, but it can also be used to test plain (no Qt) C++ code.

In this tutorial I am going to explain how to use Qt Test for testing a C++ class. That will involve creating a project, defining the unit test and using the different macros available to test code.

This is the first post of a series of three dedicated to Qt Test. The posts of this series are:

Setting up the project

The idea behind Qt Test is that each test case needs to be an independent executable and needs its own project.

The quickest way to create a project is using the “Auto Test Project” template, which is listed in the “Other Project” group of the “New Project” dialog.

Creating an Auto Test Project in Qt Creator for C++ unit testing with Qt Test

The wizard will guide you through the setup of the project. In particular the Details section will let you specify several options.

Qt Creator template autotest project details

The “Test case name” will be the name of the class representing the unit test.

If you don’t want to use the project wizard, you need to add testlib to the QT variable in your qmake project file:

QT += testlib

If your unit test does not involve any GUI elements, it is possible to disable the gui module:

QT -= gui

Writing a unit test

To write a C++ unit test with Qt  you need to create a class which inherits from QObject and implements at least one private slot. Each private slot is an independent test.

The simplest unit test class is something like this:

#include <QtTest>

class TestMinimal : public QObject
{
    Q_OBJECT

private slots:
    void testFoo();
};

You can also implement 4 special private slots which are used for initialization and cleanup operations:

// called before the first test function
void initTestCase();
// called before every test function
void init();
// called after every test function
void cleanup();
// called after the last test function
void cleanupTestCase();

Furthermore, you can have all the data and functions you want in a test class as it is just a normal C++ class.

An example unit test class

For this tutorial I wrote a simple example class to test, called Calculator. This class performs basic mathematical operations on 2 integers. Nothing fancy, it’s just an example after all.

One thing to keep in mind is that usually you won’t be including code to test directly in the unit test project, but you will be testing some library or code from another project. As this is an example, we can keep things simple for now.

The unit test class is:

#include "Calculator.h"

#include <QtTest/QtTest>

class TestCalculator: public QObject
{
    Q_OBJECT

private slots:
    // -- setup/cleanup --
    void init();

    // -- tests --
    void testConstructor();
    void testSum();

private:
    const int A0 = 0;
    const int B0 = 0;

private:
    Calculator mCalc;
};

The different functions of TestCalculator will be explained in the next sections.

Verify values

Qt Test provides different macros to check that everything is as expected during a test.

The simplest check you can perform is verifying if a statement is true. To do that you can use the QVERIFY macro:

void TestCalculator::testConstructor()
{
    // default values
    Calculator c1;

    QVERIFY(c1.getA() == 0);
    QVERIFY(c1.getB() == 0);

In this code everything is fine as long the functions getA() and getB() return 0. If that’s the case you will see this message when running the test:

PASS : TestCalculator::testConstructor()

In case of failure the message will notify you that something went wrong:

FAIL! : TestCalculator::testConstructor() 'c1.getA() == 0' returned FALSE. ()
   Loc: [../QtTestIntroduction/TestCalculator.cpp(16)]

The error message doesn’t provide much information about what went wrong. It only tells you what expression is false.

In case you need to add a little context to a check, you can use a second version of the macro, QVERIFY2, which allows to add an error message:

    // full constructor
    const int A = 10;
    const int B = 2;
    Calculator c2(A, B);

    QVERIFY2(c2.getA() == A, "first operand doesn't match");
    QVERIFY2(c2.getB() == B, "second operand doesn't match");
}

In case of failure, the debug message will add the extra information passed to the macro:

FAIL! : TestCalculator::testConstructor() 'c2.getB() == B' returned FALSE. (second operand doesn't match)
 Loc: [../QtTestIntroduction/TestCalculator.cpp(25)]

The error message might seem like extra work, but it will help people not familiar with your code. You should always prefer QVERIFY2 (over QVERIFY) to include extra information in your tests.

Qt Test also provide other versions of QVERIFY which provide some extra functionalities. For example QTRY_VERIFY_WITH_TIMEOUT or QVERIFY_EXCEPTION_THROWN. The former let you test a condition multiple times before considering it false. The latter checks if a particular exception is thrown. For further information check the reference at the end of this tutorial.

Compare values

If you need more information when a test fails and if you want to compare any kind of value, what you need is QCOMPARE:

void TestCalculator::testSum()
{
    // sum default
    QCOMPARE(mCalc.Sum(), A0 + B0);

This macro will compare the 2 parameters using the most appropriate testing operator. For example, for comparing floats it uses the Qt function qFuzzyCompare().

When QCOMPARE fails it provides more detailed information:

FAIL! : TestCalculator::testSum() Compared values are not the same
Actual (mCalc.Sum()): 10
Expected (A0 + B0) : 12
 Loc: [../QtTestIntroduction/TestCalculator.cpp(39)]

In this case you will also find out what the returned and the expected values are. Something very useful in many cases.

Also QCOMPARE has different versions to offer extra functionalities, like QTRY_COMPARE_WITH_TIMEOUT, which tests a condition multiple times before considering it false. For further information check the reference at the end of this tutorial.

Application main

As mentioned before, each unit test is supposed to be an independent executable. That means that after creating a unit test you need a main to run it.

Qt Test provides 3 macros to generate a main according to your needs:

// full Qt application
QTEST_MAIN(TestName)

// core Qt application: no GUI, but event loop is available
QTEST_GUILESS_MAIN(TestName) 

// no Qt application: no GUI and no events
QTEST_APPLESS_MAIN(TestName)

You can add one of those macros at the end of the cpp file defining the unit test. For this example I used:

QTEST_APPLESS_MAIN(TestCalculator)

as I am testing plain C++ code (no Qt).

One thing to remember is that if you declare the unit test class directly in the .cpp file (no .h file), you will need to add an extra include at the end:

QTEST_APPLESS_MAIN(TestCalculator)
#include "TestCalculator.moc"

This is required by Qt to work properly.

Source code

The full source code of this tutorial is available on GitHub and released under the Unlicense license.

References

To know more about Qt Test you can check out the latest documentation of the QTest namespace.

If you want to learn more about Qt have a look at the other Qt tutorials I posted.

Conclusion

Qt Test is a simple and easy to use framework to write C++ unit tests. It doesn’t offer all the features of other similar frameworks, but it can be enough in many cases.

If your project does not involve Qt then you should probably go for other options. If you are working with Qt, then Qt Test can be your best choice, especially if you want to test GUI code, which is something I am going to discuss in my next post.

Stay connected

Don’t forget to subscribe to the blog newsletter to get notified of future posts.

You can also get updates following me on Google+LinkedIn and Twitter.

 

1 Comment

  1. Mli Mber

    I have an extensive test suite in Qt Test. If I had to do it over again, I would use Gtest or Catch or similar for the superior testing infrastructure at use QTest for things like mouseButtonClick().

    Reply

Leave a Comment

Your email address will not be published. Required fields are marked *