Skip to content

QCOMPARE with Custom Qt Types

When you write unit tests, you will have to compare the actual value and the expected value. A simplified example with QStrings would look like this.

void MyTest::testQCompare() {
    auto actualStr = QString{"abba"};
    auto expectedStr = QString{"juhu"};
    QCOMPARE(actualStr, expectedStr);
}

When you run this unit test, the QTest framework will print the actual value "abba" and the expected value "juhu". This is often enough to know what went wrong.

FAIL!  : MyTest::testQCompare() Compared values are not the same
   Actual   (actualStr)  : "abba"
   Expected (expectedStr): "juhu"

If two values of a custom type differ, the QTest framework will only print the first line: Compared values are not the same. This is not very helpful. How can you make QCOMPARE print the actual and expected for custom types as well?

The class QCanBusFrame is a good example of a custom type. It lacks an equality operator and doesn’t produce a meaningful output when QCOMPARE fails. The next unit tests exhibits exactly this behaviour.

void MyTest::testQCompare()
{
    auto frame1 = QCanBusFrame{0x18ef0201U, QByteArray::fromHex("018A")};
    auto frame2 = QCanBusFrame{0x18ef0301U, QByteArray::fromHex("0153")};
    QCOMPARE(frame1, frame2);
}

The compilation of the unit tests fails with an error caused by QCOMPARE located at mytest.cpp:74:5.

qtestcase.h: In instantiation of ‘bool QTest::qCompare(const T&,
    const T&, const char*, const char*, const char*, int) 
    [with T = QCanBusFrame]’:
mytest.cpp:74:5:   required from here
qtestcase.h:359:34: error: no match for ‘operator==’ (operand types
    are ‘const QCanBusFrame’ and ‘const QCanBusFrame’)
    return compare_helper(t1 == t2, "Compared values are not the
                                     same",

The third error message says that QCanBusFrame has no equality operator, which is needed at location qtestcase.h:359:34. The function compare_helper is passed the result of the comparison t1 == t2, whether the QCanBusFrames t1 and t2 are equal.

Two CAN frames are equal, if their frame IDs and payloads are equal. The equality operator has global scope.

bool operator==(const QCanBusFrame &frame1, const QCanBusFrame &frame2)
{
    return frame1.frameId() == frame2.frameId() && 
        frame1.payload() == frame2.payload();
}

The unit test compiles fine now, but produces the not so useful output that the two CAN frames are not the same.

FAIL!  : MyTest::testQCompare() Compared values are not the same

The complete call of compare_helper gives you a clue how to make the output more instructive.

return compare_helper(t1 == t2, "Compared values are not the same",
                      toString(t1), toString(t2), actual, expected,
                      file, line);

The function calls toString(t1) and toString(t2) try to convert the CAN frames t1 and t2 to C strings. As there is no specific overload for QCanBusFrames

char *toString(const QCanBusFrame &frame);

the compiler chooses the general overload

template<> char *toString(const T &t)

This general overload returns nullptr when T equals QCanBusFrame. The function compare_helper calls the function QTestResult::compare passing nullptr to the arguments val1 and val2. If the comparison returns false and if one of the values is nullptr, QTestResult::compare won’t print the actual and expected value but only “Compared values are not the same”.

The overload toString(const char *str) in qtestcase.cpp is a good blueprint for your own overload.

char *toString(const QCanBusFrame &frame)
{
    auto src = QByteArray::number(frame.frameId(), 16) + "#" +
        frame.payload().toHex();
    char *dst = new char[src.size() + 1];
    return qstrcpy(dst, src.data());
}

The variable src contains the CAN frame in the format id#payload – as described in my previous post Printing Custom Qt Types with qDebug(). The variable dst is a newly created array of char. The function qstrcpy copies the contents of src into dst.

Returning a pointer to a char array created by new smells of a memory leak. It is not, because QTestResult::compare deletes the char arrays created by toString. This solution is certainly not foolproof, but works if used with caution.

Note that your overload must be declared globally. Putting it in the QTest namespace doesn’t work.

When you run the unit test, it will print the instructive message you worked so hard for.

FAIL!  : MyTest::testQCompare() Compared values are not the same
   Actual   (frame1): 18ef0201#018a010000000000
   Expected (frame2): 18ef0301#0153020000000000

Leave a Reply

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