Draft: this page is under construction…
For Mail2Voice Next, we introduced unit tests. Units tests are useful to ensure that every classes/methods/functions behave the way we think they are supposed to. It helps the developers to design their code when writing the tests before coding the actual implementation. It helps the developers verifying that the code still works after modifications. Ultimately, it helps developers to detect regressions in the code. Unit tests are one the first tools to use in Quality Assurance.
The unit tests written for Mail2Voice are based on QTest suite.
Each class of Mail2Voice must have its dedicated unit tests to check every methods in relevant scenarios (to test edge cases, side effects, etc.).
Let's say we have a class named MyClass with the following header :
class MyClass { public: MyClass(); // Constructor initializing members void increment(); // Add 1 to m_count void setText(const QString& text); // Set m_text to text int count(); // Returns current m_count value QString text(); // Returns current m_text value private: int m_count; QString m_text; };
MyClass is a very simple class that does two things : incrementing an internal variable and setting an internal text. Nothing special here, isn't it? Why should we care about testing such a trivial code?
Wait, mistakes are common and even if you are sure of your code, are you sure that no one else won't introduce errors in your code? That, is where unit tests are useful: ensuring your code is correct and will stay correct over time. Saving you a lot of time of debugging in the future.
So, what do we have to test exactly? Basically every methods:
To write a unit test, you have to ask yourself what behavior is expected for each method. Let's do this exercise:
In the Mail2Voice Qt project, we have a subproject named unittests. For each class, there is a dedicated test class. The header of the test class will be like this:
#include "myClass.h" #include "propsTester.h" #include <QObject> class TestMyClass : public QObject, public PropsTester<MyClass> { Q_OBJECT // Because we derive from QObject and want Qt features public: explicit TestMyClass(QObject *parent = 0); // Below are the unit tests. Each slot will be called via the QTest suite. private Q_SLOTS: void default_constructor(); void increment(); void incrementMax(); void setText(); void setTextEmpty(); };
The PropsTester class is a convenience to check properties against their default values. While it is not mandatory, it is highly recommended to ease the test of class members integrity.
The implementation of TestMyClass will look like this:
#include <QTest> #include "test_myClass.h" TestMyClass::TestMyClass(QObject *parent) : QObject(parent) { // m_propsCheckList stores lambdas functions that check the default value of class members. // Each lambda function is associated with a name ("count" and "text" here) representing the tested class member. m_propsChecklist.append({"count", [](const MyClass& myClass) { QVERIFY2(myClass.count() == 0, "Count has not been initialized to 0"); }}); m_propsChecklist.append({"text", [](const MyClass& myClass) { QVERIFY2(myClass.text() == QString("Default text"), "Text is not set to default."); }}); } void TestMyClass::default_constructor() { MyClass myClass; // We just create a myClass object... checkDefaultProperties(myClass, {}); //... and use this method to check that the members values are set correctly. // Note that the second parameter is a list of members to NOT check. } void TestMyClass::increment() { MyClass myClass; for(int i = 1; i <= 10; ++i) { myClass.increment(); QVERIFY2(myClass.count() == i, "Count is incorrect."); checkDefaultProperties(myClass, {"count"}); // We check that all members are set to default values except m_count } } void TestMyClass::incrementMax() { MyClass myClass; for(int i = 0; i < 2147483647; ++i) { myClass.increment(); } QVERIFY2(myClass.count() == 2147483647, "Count is incorrect."); // Check we are at maximum myClass.increment(); // Increment again to see if m_count stays at 2147483647 QVERIFY2(myClass.count() == 2147483647, "Count is incorrect."); // Check again checkDefaultProperties(myClass, {"count"}); // Check that nothing else has changed due to side effects } void TestMyClass::setText() { MyClass myClass; myClass.setText("Hello world!"); QVERIFY2(myClass.text() == "Hello world!", "Text incorrect."); // Check the text has been set properly checkDefaultProperties(myClass, {"text"}); // Check that nothing else has changed due to side effects } void TestMyClass::setTextEmpty() { MyClass myClass; myClass.setText(""); QVERIFY2(myClass.text() == "Default text", "Text has been changed."); // Check that the text has not been changed at all. checkDefaultProperties(myClass, {"text"}); // Check that nothing else has changed due to side effects }
Then, in the main.cpp file of the unittests subproject, you have to instantiate a TestMyClass object and add it to the list of tests to run:
int main( int argc, char *argv[]) { int ret = 0; TestContact tstContact; TestEmail tstEmail; TestAccount tstAccount; TestAttachment tstAttachment; TestServerSettings tstServerSettings; TestMyClass tstMyClass; // <-- instantiation ret = executeTests(argc, argv, {&tstContact, &tstEmail, &tstAccount, &tstAttachment, &tstServerSettings, &tstMyClass}); // Add test to the suite return ret; }
Well, that is the simplest part, just select the unittests subproject in QtCreator, compile it and run it!