GUI unit testing with Qt Test – part 1 – introduction

This tutorial is an introduction to GUI unit testing with Qt Test. A working example is discussed and analysed in detail. Full qmake project and C++ source code are provided.

GUI unit testing with Qt Test

In this tutorial I will introduce GUI unit testing with Qt Test, the Qt framework for C++ unit testing. In particular I will discuss how to write a basic unit test for a widget class, how to simulate mouse and keyboard events and how to write data driven tests.

This is the third post of a series dedicated to Qt Test. The posts of this series are:

I recommend to read the previous posts of the series to fully understand new concepts I will introduce here.

Testing a QWidget

This tutorial will discuss the testing of a QWidget class called PanelConcat which represents the central widget (content) of the following window:

example Qt application for GUI unit testing

The widget contains 2 input fields and 2 buttons. Pushing the button “CONCAT” merges the strings of the 2 input fields and prints the result to a label. Pushing the CANCEL button clears all the visible data. Nothing fancy or particularly complex, but this is just an example after all.

Testing GUI code normally requires a different approach to normal unit testing. That’s because usually testing public functions of a widget is not enough. To properly test a widget, it’s usually better if the tester class can access all its internal data and functions. The easiest way to achieve that is making the tester class friend of the tested class.

For this example PanelConcat needs to be modified like this to give full access to TestPanelConcat:

class WIDGETSLIBSHARED_EXPORT PanelConcat : public QWidget
{
  Q_OBJECT

  friend class TestPanelConcat;

public:

When testing complex widget structures it could be necessary to provide full class access to more than one tester class. In that case a possible solution is to create a friend visitor class which is used by all the testers.

Basic GUI testing

The simplest test that can be performed on a widget is checking if all its elements have been created properly.

void TestPanelConcat::TestConstruction()
{
	QVERIFY2(panel.mInputA, "Input field A not created");
	QVERIFY2(panel.mInputB, "Input field B not created");

	QVERIFY2(panel.mLabelRes, "Result label not created");
}

In this case the test is fairly straightforward as the code is just checking if the different internal widgets are not NULL. In a real application things can be more complex as some elements are not always created, so it’s important to cover all the cases when unit testing.

Another basic GUI testing is checking that all the important properties of a widget and of its elements are properly set.

void TestPanelConcat::TestSize()
{
	QVERIFY2(panel.minimumWidth() == PanelConcat::MIN_W, "Minimum width not set.");
	QVERIFY2(panel.minimumHeight() == PanelConcat::MIN_H, "Minimum height not set");
}

In this example TestSize is checking if the minimum size of the panel has been set.

Note that I didn’t use QCOMPARE in this case because it doesn’t work with a static const defined in another class. A possible fix is assigning the external static const to a local const and pass that to QCOMPARE, but in this case I decided to keep things simple and I used QVERIFY.

Testing GUI usage

When doing GUI unit testing with Qt Test, what you usually want to test is normal GUI usage. That means testing how your widgets react to mouse and keyboard events.

Qt Test offers several functions to programmatically send keyboard or mouse events to QWidgets.

The code in this paragraph will simulate the following actions:

  1. writing in the 2 input fields
  2. pushing the CONCAT button to get a result
  3. pushing the CANCEL button to clear all the data

The expected result of this test is to see empty input fields and result label at the end of it.

The first step is performed using the function QTest::keyClicks which simulates a sequence of key clicks on a widget:

void TestPanelConcat::TestClear()
{
	// write into input fields
	QTest::keyClicks(panel.mInputA, STR1);
	QTest::keyClicks(panel.mInputB, STR2);

The sequence of keys is encoded in a QString. For example, the string “www” means that the widget will receive 3 “w” key clicks.

The second step is performed using the function QTest::mouseClick which simulates a mouse click on a widget:

	// click button CONCAT
	QTest::mouseClick(panel.mButtonConcat, Qt::LeftButton);
	// click button CANCEL
	QTest::mouseClick(panel.mButtonCancel, Qt::LeftButton);

By default QTest::mouseClick simulates the click in the middle of the widget, which is fine for a button, but it is also possible to specify a position.

The final step is just verifying that all the text in the widget is empty.

	// check all fields are empty
	QVERIFY2(panel.mInputA->text().isEmpty(), "Input A not empty");
	QVERIFY2(panel.mInputB->text().isEmpty(), "Input B not empty");
	QVERIFY2(panel.mLabelRes->text().isEmpty(), "Label result not empty");
}

Data driven testing

A more advanced way of doing GUI unit testing with Qt Test is data driven testing. The idea is to separate tests and data to avoid to have a long list of similar QVERIFY or QCOMPARE macros and to replicate all the code needed to initialise a test.

To provide data to a test function you have to create another private slot with the same name of the function and add the “_data” suffix. For example the data function for TestConcat() is TestConcat_data().

Implementing a data function is a bit like inserting data into a database. First you define your data like if you were defining a table:

void TestPanelConcat::TestConcat_data()
{
	QTest::addColumn<QTestEventList>("inputA");
	QTest::addColumn<QTestEventList>("inputB");
	QTest::addColumn<QString>("result");

The type of the first 2 “columns” is QTestEventList, which is a list of keyboard or mouse events.

In this case I am storing 2 strings containing key clicks in both lists

	QTestEventList listA;
	QTestEventList listB;

	// -- Normal A + B --
	listA.addKeyClicks(STR1);
	listB.addKeyClicks(STR2);

Finally a row of data is added into the dataset:

	QTest::newRow("Normal A + B") << listA << listB << STR_RES;

	// ... more data ...
}

Each row contains a name and a list of values. You can imagine that the previous code is converted to something like the following table:

INDEX NAME inputA inputB result
0 “Normal A + B” listA listB STR_RES

Once we have defined the data function we can write the test function which is divided in 3 parts.

The fist part retrieves the data or a row using the macro QFETCH:

void TestPanelConcat::TestConcat()
{
	QFETCH(QTestEventList, inputA);
	QFETCH(QTestEventList, inputB);
	QFETCH(QString, result);

Then that data is used to simulate some interaction with the widget. In particular the function QTestEventList::simulate simulates the events from the event list one by one on the destination widget:

	// write into input fields
	inputA.simulate(panel.mInputA);
	inputB.simulate(panel.mInputB);

	// click button CONCAT
	QTest::mouseClick(panel.mButtonConcat, Qt::LeftButton);

Finally the last part checks what happened:

	// compare result
	QCOMPARE(panel.mLabelRes->text(), result);
}

For example this code tests if the result label contains the right result string.

Without a data driven approach we should have repeated the 3 steps many times in code.

Source code

The full source code of this tutorial is available on GitHub and released under the Unlicense license.

The full project structure includes 3 sub-projects:

  • WidgetsLib – a dynamic library containing the widget class.
  • ExampleApp – an example application using the PanelConcat widget.
  • TestPanelConcat – the unit test of PanelConcat.

To try the example load the top subdirs project called GuiUnitTestingIntro in Qt Creator.

Keep in mind that by default running the project will launch the example application. To run the unit tests you can change the active run configuration, use the Tests panel or use the menu

Tools > Tests > Run All Tests

References

To know more about the concepts introduced in this tutorial you can check out the latest documentation of the QTest namespace.

If you want to learn more about Qt have a look at the other Qt tutorials I posted.

Conclusion

As shown in this tutorial GUI unit testing with Qt Test is a concrete possibility and something Qt developers should take into consideration when working on Qt code. Testing every possible interaction with a user interface is not trivial for sure, but covering most of it is definitely possible and Qt Test offers enough support for doing it.

In the next and final post of this series I will talk about more advanced GUI unit testing with Qt Test. In particular I will discuss how to simulate more complex widget interaction involving focus and signals.

In case you need help to handle your GUI unit tests with Qt Test feel free to contact me.

Stay connected

Don’t forget to subscribe to the blog newsletter to get notified of future posts.

You can also get updates following me on GithubGoogle+LinkedIn and Twitter.

Leave a Comment

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