unit_tests
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionLast revisionBoth sides next revision | ||
unit_tests [2016/09/08 06:17] – [Example] mhatz | unit_tests [2016/09/09 07:38] – mhatz | ||
---|---|---|---|
Line 3: | Line 3: | ||
====== Implementing Unit Tests ====== | ====== Implementing Unit Tests ====== | ||
- | For Mail2Voice Next, we introduced unit tests. | + | For Mail2Voice Next, we introduced unit tests. |
+ | The unit tests written for Mail2Voice are based on QTest suite. | ||
- | Each class of Mail2Voice must have its dedicated | + | ===== HOWTO writing a unit test for a class ===== |
- | ===== Example | + | Each class of Mail2Voice must have its dedicated unit tests to check every methods in relevant scenarios (to test edge cases, side effects, etc.). |
+ | |||
+ | ==== Example ==== | ||
Line 17: | Line 20: | ||
{ | { | ||
public: | public: | ||
- | MyClass(); // Constructor initializing | + | MyClass(); // Constructor initializing |
| | ||
void increment(); | void increment(); | ||
Line 23: | Line 26: | ||
| | ||
int count(); // Returns current m_count value | int count(); // Returns current m_count value | ||
+ | QString text(); // Returns current m_text value | ||
| | ||
private: | private: | ||
Line 29: | Line 33: | ||
}; | }; | ||
</ | </ | ||
+ | |||
+ | 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. | ||
+ | |||
+ | ==== What to test? ==== | ||
+ | |||
+ | |||
+ | So, what do we have to test exactly? | ||
+ | Basically every methods: | ||
+ | * first, the constructor because it is in charge of initializing an object of type MyClass, | ||
+ | * the count() and text() methods to make sure they return correct values, | ||
+ | * the increment() and setText(const QString& | ||
+ | |||
+ | To write a unit test, you have to ask yourself what behavior is expected for each method. Let's do this exercise: | ||
+ | |||
+ | * MyClass(): the constructor must initialize the object, so it must initialize the members m_count and m_text. But, to which values? This have to be specified somewhere otherwise the objects could be inconsistent. | ||
+ | * For our example, we will pretend that m_count must be initialized to 0 and m_text to " | ||
+ | |||
+ | * increment(): | ||
+ | * For our example, we choose the second option: never go to negative value. | ||
+ | |||
+ | * setText(const QString& | ||
+ | |||
+ | * count() and text() methods must always return the current value. | ||
+ | |||
+ | ==== Coding the unit tests ==== | ||
+ | |||
+ | 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: | ||
+ | |||
+ | |||
+ | <code cpp> | ||
+ | #include " | ||
+ | #include " | ||
+ | |||
+ | #include < | ||
+ | |||
+ | class TestMyClass : public QObject, public PropsTester< | ||
+ | { | ||
+ | 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: | ||
+ | |||
+ | <code cpp> | ||
+ | #include < | ||
+ | |||
+ | #include " | ||
+ | |||
+ | TestMyClass:: | ||
+ | { | ||
+ | // m_propsCheckList stores lambdas functions that check the default value of class members. | ||
+ | // Each lambda function is associated with a name (" | ||
+ | m_propsChecklist.append({" | ||
+ | m_propsChecklist.append({" | ||
+ | } | ||
+ | |||
+ | void TestMyClass:: | ||
+ | { | ||
+ | MyClass myClass; // We just create a myClass object... | ||
+ | checkDefaultProperties(myClass, | ||
+ | // Note that the second parameter is a list of members to NOT check. | ||
+ | } | ||
+ | |||
+ | |||
+ | void TestMyClass:: | ||
+ | { | ||
+ | MyClass myClass; | ||
+ | | ||
+ | for(int i = 1; i <= 10; ++i) | ||
+ | { | ||
+ | myClass.increment(); | ||
+ | QVERIFY2(myClass.count() == i, "Count is incorrect." | ||
+ | checkDefaultProperties(myClass, | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void TestMyClass:: | ||
+ | { | ||
+ | MyClass myClass; | ||
+ | | ||
+ | for(int i = 0; i < 2147483647; ++i) | ||
+ | { | ||
+ | myClass.increment(); | ||
+ | } | ||
+ | | ||
+ | QVERIFY2(myClass.count() == 2147483647, "Count is incorrect." | ||
+ | | ||
+ | myClass.increment(); | ||
+ | QVERIFY2(myClass.count() == 2147483647, "Count is incorrect." | ||
+ | | ||
+ | checkDefaultProperties(myClass, | ||
+ | } | ||
+ | |||
+ | void TestMyClass:: | ||
+ | { | ||
+ | MyClass myClass; | ||
+ | myClass.setText(" | ||
+ | QVERIFY2(myClass.text() == "Hello world!", | ||
+ | checkDefaultProperties(myClass, | ||
+ | } | ||
+ | |||
+ | void TestMyClass:: | ||
+ | { | ||
+ | MyClass myClass; | ||
+ | myClass.setText("" | ||
+ | QVERIFY2(myClass.text() == " | ||
+ | checkDefaultProperties(myClass, | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | |||
+ | 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: | ||
+ | |||
+ | <code cpp> | ||
+ | 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, | ||
+ | {& | ||
+ | & | ||
+ | & | ||
+ | & | ||
+ | & | ||
+ | & | ||
+ | |||
+ | return ret; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Running unit tests ==== | ||
+ | |||
+ | Well, that is the simplest part, just select the unittests subproject in QtCreator, compile it and run it! |
unit_tests.txt · Last modified: 2023/04/25 16:52 by 127.0.0.1