📜 ⬆️ ⬇️

Unit testing in Qt


gollum noticed that there is an error in the text of the picture

Salute, Habr! How are you?

I wanted to learn a little something. I was looking for a hub in the Qt Software hub at least some post about unit testing in Qt. Not found. Here I will tell you the basic things about unit testing on Qt (do not expect a powerful shamanism). In fact, unit testing in Qt is pretty simple. To learn how to do this, I invite you to read further.

I will try to break everything apart. I'm spoiling, so to speak. Let's start.
')

Theory


If you know the theory of unit testing, you can skip this point.

We have a code. As if we did not want it there are bugs. Bugs are bad. To avoid bugs, you need to write very high-quality code (this is not in this article) and most importantly, test it. But we write code, we supplement, we refactor ... And every time it is unpleasant to give each version of the project to the same test suite. And here one very wise programmer thought of making such a program that could test your program with this, the cherished test suite. And this model of testing is called unit testing!

Unit testing in qt


And now more specifically. In Qt, the QTestLib module (testlib) is responsible for unit testing. It provides us with a set of macros for testing. But more about that later. There are several test methods:


I use the first method more often, the second one is ugly. But today I will show you on the example of the second method, and I will sign the first method now.

Qt uses a cool model: one project - one test. Therefore, tests are implemented by creating the tests project in the child directory of the tests of the main project. In tests is the class that implements the test of the main class. You will learn the principle of its operation later, and the main difference of this approach lies in the way the test is run. This approach requires the absence of main.cpp and the presence of the Q_TEST_MAIN (Test_ClassName) macro at the end of test_classname.cpp.

Task


I suggest, for example, to implement a class Smart, which will work with the comparison of integers. What will he specifically do? We implement the int min method (int, int) , which will return a smaller number and int max (int, int) , which will return a larger number.

Well, let's go already!


So. Go to Qt Creator. Create a Qt console application. Add the testlib and gui module (necessary for testing the GUI) to the .pro file. Now you can start. It is customary to begin with the writing of tests, and then the class itself, but perhaps I deviate from the traditions. Let's write class Smart. You are lucky, I'll write it. You just need to understand how it works. Here is this handsome:

smart.h
#ifndef SMART_H #define SMART_H #include <QObject> #include <QStringList> class Smart : public QObject { Q_OBJECT public: explicit Smart(QObject *parent, const QStringList& list); public slots: int max(int a, int b); int min(int a, int b); }; #endif // SMART_H 


smart.cpp
 #include "smart.h" Smart::Smart(QObject *parent, const QStringList& list) : QObject(parent) { } int Smart::max(int a, int b) { if(a > b) return a; return b; } int Smart::min(int a, int b) { if(a < b) return a; return b; } 


QObject * Class Testing


The class is ready. It's time to check how it works! To do this, we write a class that will test our "smart" class. It will be called Test_Smart.

test_smart.h
 #ifndef TEST_SMART_H #define TEST_SMART_H #include <QObject> class Test_Smart : public QObject { Q_OBJECT public: explicit Test_Smart(QObject *parent = 0); private slots: //    void max(); // int max(int, int) }; #endif // TEST_SMART_H 


test_smart.cpp
 #include <QTest> #include "test_smart.h" #include "smart.h" Test_Smart::Test_Smart(QObject *parent) : QObject(parent) { } void Test_Smart::max() { Smart a; QCOMPARE(a.max(1, 0), 1); QCOMPARE(a.max(-1, 1), 1); QCOMPARE(a.max(4, 8), 8); QCOMPARE(a.max(0, 0), 0); QCOMPARE(a.max(1, 1), 1); QCOMPARE(a.max(-10,-5), -5); } 


We didn’t write a bit, but it's not scary. We still have time. Now we need to learn how to run our tests.

main.cpp
 #include <QApplication> #include <QTest> #include <iostream> #include <cstdlib> #include <cstdio> #include "test_smart.h" using namespace std; int main(int argc, char *argv[]) { freopen("testing.log", "w", stdout); QApplication a(argc, argv); QTest::qExec(new Test_Smart, argc, argv); return 0; } 


Compile ...

testing.log
 ********* Start testing of Test_Smart ********* Config: Using QTest library 4.8.1, Qt 4.8.1 PASS : Test_Smart::initTestCase() PASS : Test_Smart::max() PASS : Test_Smart::cleanupTestCase() Totals: 3 passed, 0 failed, 0 skipped ********* Finished testing of Test_Smart ********* 


Believe me, this is the best test outcome!

But we have not tested one method yet. I left it, because I want to show it on one test method. I call it simply - " plate ". The essence of this method is not to repeat the code. Remember our test method void max () ? There we repeated the same code many times (except with different parameters). To avoid this, the “sign” method is implemented in Qt. How does it work? We create a method method_data (), in it we carry out a couple of simple operations, and then we load all this with the QFETCH () macro. Now is the time to see it all in practice!

Now it's time to add to the test_smart.cpp implementation of our " label ":

 void Test_Smart::min_data() { QTest::addColumn<int>("first"); QTest::addColumn<int>("second"); QTest::addColumn<int>("result"); QTest::newRow("min_data_1") << 1 << 0 << 0; QTest::newRow("min_data_2") << -1 << 1 << -1; QTest::newRow("min_data_3") << 4 << 8 << 4; QTest::newRow("min_data_4") << 0 << 0 << 0; QTest::newRow("min_data_5") << 1 << 1 << 1; QTest::newRow("min_data_6") << -10 << -5 << -10; } void Test_Smart::min() { Smart a; QFETCH(int, first); QFETCH(int, second); QFETCH(int, result); QCOMPARE(a.min(first, second), result); } 


Now we compile again. We get a conclusion.

testing.log
 ********* Start testing of Test_Smart ********* Config: Using QTest library 4.8.1, Qt 4.8.1 PASS : Test_Smart::initTestCase() PASS : Test_Smart::max() PASS : Test_Smart::min() PASS : Test_Smart::cleanupTestCase() Totals: 4 passed, 0 failed, 0 skipped ********* Finished testing of Test_Smart ********* 


Now somewhere we do something wrong. For example, change to Smart :: min (..) change <to>.
testing.log
 ********* Start testing of Test_Smart ********* Config: Using QTest library 4.8.1, Qt 4.8.1 PASS : Test_Smart::initTestCase() PASS : Test_Smart::max() FAIL! : Test_Smart::min(data_1) Compared values are not the same Actual (a.min(first, second)): 1 Expected (result): 0 Loc: [test_smart.cpp(41)] FAIL! : Test_Smart::min(data_1) Compared values are not the same Actual (a.min(first, second)): 1 Expected (result): -1 Loc: [test_smart.cpp(41)] FAIL! : Test_Smart::min(data_1) Compared values are not the same Actual (a.min(first, second)): 8 Expected (result): 4 Loc: [test_smart.cpp(41)] FAIL! : Test_Smart::min(data_1) Compared values are not the same Actual (a.min(first, second)): -5 Expected (result): -10 Loc: [test_smart.cpp(41)] PASS : Test_Smart::cleanupTestCase() Totals: 3 passed, 4 failed, 0 skipped ********* Finished testing of Test_Smart ********* 


It means everything is OK).

Testing GUI


Sometimes, and sometimes very often, we have to test the GUI. In QTestLib, this is also implemented. Let's test QLineEdit.

This is what our test_qlineedit.h looks like:
 #ifndef TEST_QLINEEDIT_H #define TEST_QLINEEDIT_H #include <QObject> class Test_QLineEdit : public QObject { Q_OBJECT private slots: //    void edit(); }; #endif // TEST_QLINEEDIT_H 


And this is what our test_qlineedit.cpp looks like:
 #include <QtTest> #include <QtGui> #include "test_qlineedit.h" void Test_QLineEdit::edit() { QLineEdit a; QTest::keyClicks(&a, "abCDEf123-"); QCOMPARE(a.text(), QString("abCDEf123-")); QVERIFY(a.isModified()); } 


It's time to fix main.cpp :
 #include <QApplication> #include <QTest> #include <iostream> #include <cstdlib> #include <cstdio> #include "test_smart.h" #include "test_qlineedit.h" using namespace std; int main(int argc, char *argv[]) { freopen("testing.log", "w", stdout); QApplication a(argc, argv); QTest::qExec(new Test_Smart, argc, argv); cout << endl; QTest::qExec(new Test_QLineEdit, argc, argv); return 0; } 


Now we start testing:
 ********* Start testing of Test_Smart ********* Config: Using QTest library 4.8.1, Qt 4.8.1 PASS : Test_Smart::initTestCase() PASS : Test_Smart::max() PASS : Test_Smart::min() PASS : Test_Smart::cleanupTestCase() Totals: 4 passed, 0 failed, 0 skipped ********* Finished testing of Test_Smart ********* ********* Start testing of Test_QLineEdit ********* Config: Using QTest library 4.8.1, Qt 4.8.1 PASS : Test_QLineEdit::initTestCase() PASS : Test_QLineEdit::edit() PASS : Test_QLineEdit::cleanupTestCase() Totals: 3 passed, 0 failed, 0 skipped ********* Finished testing of Test_QLineEdit ********* 


So we learned to test the GUI. The test showed that QLineEdit works correctly)).

Testing arguments


OptionExplanation
-o filenameDisplays test results in filename
-silentLimit messages to warnings and errors only.
-v1Display information about the input and output of test methods
-v2Complements the -v1 option with messages for the QCOMPARE and QVERIFY macros
-vsDisplay every sent signal and called slot
-xmlOutput all information in XML format
-eventdelay msMake the test stop and wait for ms milliseconds. This option is useful for finding errors in GUI elements.

All that I told you today is just enough to start testing your Qt-applications right now. What can I tell you? All the tips and suggestions for improving the article please write in the comments - for me it is important, since this, I hope, is not my last article.

Good luck and good code to you;)

Source: https://habr.com/ru/post/146449/


All Articles