QT Testing 1 – Do you trust your code? Here is the mighty QTest

In first post of testing series I’ve mentioned about multiple targets as a starting point. Now it’s time to add a class to show testing utility. In main target let’s have a class called Calculations. Goal is to test this Calculations class methods.

Project source code can get from repository.

#ifndef CALCULATIONS_H
#define CALCULATIONS_H

class Calculations
{
public:
    Calculations() = default;
    int sum(const int &first, const int &second);
    int multiply(const int &first, const int &second);
    int difference(const int &first, const int &second);
    float divide(const int &first, const int &second);

};

#endif // CALCULATIONS_H
#include "calculations.h"
#include <QDebug>

int Calculations::sum(const int &first, const int &second)
{
    qDebug() << __PRETTY_FUNCTION__ << first + second;
    return first + second;
}

int Calculations::multiply(const int &first, const int &second)
{
    qDebug() << __PRETTY_FUNCTION__ << first * second;
    return first * second;
}

int Calculations::difference(const int &first, const int &second)
{
    qDebug() << __PRETTY_FUNCTION__ << first - second;
    return first - second;
}

float Calculations::divide(const int &first, const int &second)
{
    qDebug() << __PRETTY_FUNCTION__ << static_cast<float>(first) / second;
    return static_cast<float>(first) / second;
}

There is a class in Main_Target to test our code and cover it up. It’s time to have a testing class. Let’s call it TestMathCalculations. It will be defined in Test_Target and must be derived from QObject and testing methods are private slots. In this post I want to keep going on with just QCOMPARE macro to test output variables. For the rest of macros, you can check Qt’s official documentation.

#ifndef TESTMATHCALCULATIONS_H
#define TESTMATHCALCULATIONS_H

#include <QObject>

class TestMathCalculations : public QObject
{
    Q_OBJECT

private Q_SLOTS:
    void TestSum();
    void TestMultiply();
    void TestDifference();
    void TestDivide();
};

#endif // TESTMATHCALCULATIONS_H
#include "test_math_calculations.h"

#include <QTest>
#include "calculations.h"

void TestMathCalculations::TestSum()
{
    Calculations calculations{};
    auto result = calculations.sum(1,3);
    QCOMPARE(4,result);
}

void TestMathCalculations::TestMultiply()
{
    Calculations calculations{};
    auto result = calculations.multiply(1,3);
    QCOMPARE(2,result);
}

void TestMathCalculations::TestDifference()
{
    Calculations calculations{};
    auto result = calculations.difference(1,3);
    QCOMPARE(2,result);
}

void TestMathCalculations::TestDivide()
{
    Calculations calculations{};
    auto result = calculations.divide(1,2);
    QCOMPARE(0.5,result);
}

I gave QCOMPARE macros wrong output results too. It’s to show what happens when you get unexpected test results.

Now there is an issue, whenever Calculations class header file is included in secondary (Test_Target) project, it cannot access it. So it’s class must be packed and exported. In this chapter’s branch I’ve shredded the source app into two different sub targets too. My goal was to separate main.cpp from classes.

#----------------------
# Set Library (mkmath)
#----------------------
project(mkmath)

set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(AUTOMOC_MOC_OPTIONS -Muri=MkMath)

# Static library definitions for Qt
add_definitions(-DQT_PLUGIN)
add_definitions(-DQT_STATICPLUGIN=1)

# Add a library with the aboce sources
add_library(${PROJECT_NAME} STATIC
    "${CMAKE_CURRENT_LIST_DIR}/calculations.cpp"

    "${CMAKE_CURRENT_LIST_DIR}/calculations.h"
    )

#-------------------------
# Get Qt Libraries
#-------------------------
find_package(Qt5Core)
target_link_libraries(${PROJECT_NAME} Qt5::Core)

#----------------------------
# Link all libraries to target
#----------------------------
link_libraries(${PROJECT_NAME})
#----------------------------
# Include Path
#----------------------------
target_include_directories(${PROJECT_NAME} PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}
    )

#----------------------------
# Set Alias of Library to reach from parent executable
#----------------------------
add_library(sub::mkmath ALIAS ${PROJECT_NAME})

Trick is at the bottom actually. It exports project as a library with alias of sub::mkmath. This helps us to bind it with Test_Target to include Calculations class header file.

project(Test_Target LANGUAGES CXX)

set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Qt5Core)

set(SOURCE_FILES main.cpp
    test_math_calculations.cpp

    test_math_calculations.h
    )

add_executable(${PROJECT_NAME}
  ${SOURCE_FILES}
)

find_package(Qt5Test)

target_link_libraries(${PROJECT_NAME} sub::mkmath Qt5::Test)

Now we have a class and another class to test this class. Build pattern from CMake now it’s call the test over Test_Target’s main. I’m showing the most primitive way of test. It’s not a way to do in production actually. Not even close to Object Oriented logic & flow. It’s a tutorial. Do not apply like this in your projects.

#include <QCoreApplication>
#include <QTest>

#include "test_math_calculations.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    return QTest::qExec(new TestMathCalculations, argc, argv);
}

QTest::qExec I’m quoting from Qt’s documentation, because it’s so clear: “Executes tests declared in testObject. In addition, the private slots initTestCase()cleanupTestCase()init() and cleanup() are executed if they exist”

int QTest::qExec(QObject *testObject, int argc = 0, char **argv = nullptr)

Remember that testing class was derived from QObject class, because qExec need a QObject pointer and env arguments if you supply. I’m using it as a return value if I get errors it gives me with an error code.

You may also like...

Leave a Reply

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