When you write unit tests, you will have to compare the actual value and the expected value. A simplified example with QString
s 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 QCanBusFrame
s 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 QCanBusFrame
s
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