| Writing Unittests for Qt4 and KDE4 with QtTestLib | ||
|---|---|---|
| <<< Previous | Next >>> | |
In the previous two tutorials, we've tested a date management class. This is an pretty typical use of unit testing. However Qt and KDE applications will make use graphical classes that take user input (typically from a keyboard and mouse). QtTestLib offers support for testing these classes, which we'll see in this tutorial.
Again, we'll use an existing class as our test environment, and
again it will be date related - the standard Qt
QDateEdit class (see http://doc.trolltech.com/4.0/qdateedit.html.
For those not familiar
with this class, it is a simple date entry widget (although with
some powerful back end capabilities). A picture of the widget is
shown below.
The way QtTestLib provides GUI testing is by injecting
QInputEvent (see http://doc.trolltech.com/4.0/qinputevent.html)
events. To the application, these input events appear the same
as normal key press/release and mouse clicks/drags. However the
mouse and keyboard are unaffected, so that you can continue to
use the machine normally while tests are being run.
An example of how you can use the GUI functionality of QtTestLib is shown below.
Example 13. QDateEdit test code
1 #include <QtTest>
2 #include <QtCore>
3 #include <QtGui>
4 Q_DECLARE_METATYPE(QDate)
5 class testDateEdit: public QObject
6 {
7 Q_OBJECT
8 private slots:
9 void testChanges();
10 void testValidator_data();
11 void testValidator();
12 };
13 void testDateEdit::testChanges()
14 {
15 // 11 March 1967
16 QDate date( 1967, 3, 11 );
17 QDateEdit dateEdit( date );
18 // up-arrow should increase day by one
19 QTest::keyClick( &dateEdit, Qt::Key_Up );
20 QCOMPARE( dateEdit.date(), date.addDays(1) );
21 // we click twice on the "reduce" arrow at the bottom RH corner
22 // first we need the widget size to know where to click
23 QSize editWidgetSize = dateEdit.size();
24 QPoint clickPoint(editWidgetSize.rwidth()-2, editWidgetSize.rheight()-2);
25 // issue two clicks
26 QTest::mouseClick( &dateEdit, Qt::LeftButton, 0, clickPoint);
27 QTest::mouseClick( &dateEdit, Qt::LeftButton, 0, clickPoint);
28 // and we should have decreased day by two (one less than original)
29 QCOMPARE( dateEdit.date(), date.addDays(-1) );
30 QTest::keyClicks( &dateEdit, "25122005" );
31 QCOMPARE( dateEdit.date(), QDate( 2005, 12, 25 ) );
32 QTest::keyClick( &dateEdit, Qt::Key_Tab, Qt::ShiftModifier );
33 QTest::keyClicks( &dateEdit, "08" );
34 QCOMPARE( dateEdit.date(), QDate( 2005, 8, 25 ) );
35 }
36 void testDateEdit::testValidator_data()
37 {
38 qRegisterMetaType<QDate>("QDate");
39 QTest::addColumn<QDate>( "initialDate" );
40 QTest::addColumn<QString>( "keyclicks" );
41 QTest::addColumn<QDate>( "finalDate" );
42 QTest::newRow( "1968/4/12" ) << QDate( 1967, 3, 11 )
43 << QString( "12041968" )
44 << QDate( 1968, 4, 12 );
45 QTest::newRow( "1967/3/14" ) << QDate( 1967, 3, 11 )
46 << QString( "140abcdef[" )
47 << QDate( 1967, 3, 14 );
48 // more rows can go in here
49 }
50 void testDateEdit::testValidator()
51 {
52 QFETCH( QDate, initialDate );
53 QFETCH( QString, keyclicks );
54 QFETCH( QDate, finalDate );
55 QDateEdit dateEdit( initialDate );
56 // this next line is just to start editing
57 QTest::keyClick( &dateEdit, Qt::Key_Enter );
58 QTest::keyClicks( &dateEdit, keyclicks );
59 QCOMPARE( dateEdit.date(), finalDate );
60 }
61 QTEST_MAIN(testDateEdit)
62 #include "tutorial3.moc"
|
Much of this code is common with previous examples, so I'll focus on the new elements and the more important changes as we work through the code line-by-line.
Lines 1 to 3 import the various Qt declarations, as before.
Line 4 is a macro that is required for the data-driven part of this test, which I'll come to soon.
Lines 5 to 12 declare the test class - while the names have changed, it is pretty similar to the previous example. Note the testValidator and testValidator_data functions - we will be using data driven testing again in this example.
Our first real test starts in line 13. Line 16 creates a
QDate, and line 17 uses that date as the
initial value for a QDateEdit widget.
Lines 19 and 20 show how we can test what happens when we press the up-arrow key. The QTest::keyClick function takes a pointer to a widget, and a symbolic key name (a char or a Qt::Key). At line 20, we check that the effect of that event was to increment the date by a day. The QTest:keyClick function also takes an optional keyboard modifier (such as Qt::ShiftModifier for the shift key) and an optional delay value (in milliseconds). As an alternative to using QTest::keyClick, you can use QTest::keyPress and QTest::keyRelease to construct more complex keyboard sequences.
Lines 23 to 29 show a similar test to the previous one, but in this case we are simulating a mouse click. We need to click in the lower right hand part of the widget (to hit the decrement arrow - see Figure 1), and that requires knowing how large the widget is. So lines 23 and 24 calculate the correct point based off the size of the widget. Line 26 (and the identical line 27) simulates clicking with the left-hand mouse button at the calculated point. The arguments to Qt::mouseClick are:
a pointer to the widget that the click event should be sent to.
the mouse button that is being clicked.
an optional keyboard modifier (such a Qt::ShiftModifier), or 0 for no modifiers.
an optional click point - this defaults to the middle of the widget if not specified.
an optional mouse delay.
In addition to QTest::mouseClick, there is also QTest::mousePress, QTest::mouseRelease, QTest::mouseDClick (providing double-click) and QTest::mouseMove. The first three are used in the same way as QTest::mouseClick. The last takes a point to move the mouse to. You can use these functions in combination to simulate dragging with the mouse.
Lines 30 and 31 show another approach to keyboard entry, using
the QTest::keyClicks. Where
QTest::keyClick sends a single key press,
QTest::keyClicks takes a
QString (or something equivalent, in line
30 a character array) that represents a sequence of key clicks to
send. The other arguments are the same.
Lines 32 to 34 show how you may need to use a combination of functions. After we've entered a new date in line 30, the cursor is at the end of the widget. At line 32, we use a Shift-Tab combination to move the cursor back to the month value. Then at line 33 we enter a new month value. Of course we could have used individual calls to QTest::keyClick, however that wouldn't have been as clear, and would also have required more code.
Lines 50 to 60 show a data-driven test - in this case we are
checking that the validator on
QDateEdit is performing as
expected. This is a case where data-driven testing can really
help to ensure that things are working the way they should.
At lines 52 to 54, we fetch in an initial value, a series of
key-clicks, and an expected result. These are the columns that
are set up in lines 39 to 41. However note that we are now
pulling in a QDate, where in previous
examples we used three integers and then build the
QDate from those. However
QDate isn't a registered type for
QMetaType, and so we need to register
it before we can use it in our data-driven testing. This is
done using the Q_DECLARE_METATYPE macro in
line 4 and the qRegisterMetaType function
in line 38.
Lines 42 to 47 add in a couple of sample rows. Lines 42 to 44 represent a case where the input is valid, and lines 45 to 47 are a case where the input is only partly valid (the day part). A real test will obviously contain far more combinations than this.
Those test rows are actually tested in lines 55 to 59. We
construct the QDateEdit widget in line
55, using the initial value. We then send an
Enter key click in line 57, which is required
to get the widget into edit mode. At line 58 we simulate the
data entry, and at line 59 we check whether the results are
what was expected.
Lines 61 and 62 are the same as we've seen in previous examples.
If you are re-using a set of events a number of times, then it may be an advantage to build a list of events, and then just replay them. This can improve maintainability and clarity of a set of tests, especially for mouse movements.
The key class for building a list of test events is
imaginatively known as
QTestEventList. It is a
QList of
QTestEvents. The normal approach is to
create the list, and then use various member functions to add
key and mouse events. The normal functions that you'll need
are addKeyClick and
addMouseClick, which are very similar to
the QTest::keyClick and
QTest::mouseClick functions we used
earlier in this tutorial. For finer grained operations, you
can also use addKeyPress,
addKeyRelease,
addKeyEvent,
addMousePress,
addMouseRelease,
addMouseDClick and
addMouseMove to build up more complex
event lists. You can also use addDelay to
add a specified delay between events. When the list has been
built up, you just call simulate on each
widget.
You can see how this works in the example below, which is the
QDateEdit example (from above)
converted to use QTestEventList.
Example 14. QDateEdit test code,
using QTestEventList
1 #include <QtTest>
2 #include <QtCore>
3 #include <QtGui>
4 Q_DECLARE_METATYPE(QDate)
5 class testDateEdit: public QObject
6 {
7 Q_OBJECT
8 private slots:
9 void testChanges();
10 void testValidator_data();
11 void testValidator();
12 };
13 void testDateEdit::testChanges()
14 {
15 // 11 March 1967
16 QDate date( 1967, 3, 11 );
17 QDateEdit dateEdit( date );
18 // up-arrow should increase day by one
19 QTest::keyClick( &dateEdit, Qt::Key_Up );
20 QCOMPARE( dateEdit.date(), date.addDays(1) );
21 // we click twice on the "reduce" arrow at the bottom RH corner
22 // first we need the widget size to know where to click
23 QSize editWidgetSize = dateEdit.size();
24 QPoint clickPoint(editWidgetSize.rwidth()-2, editWidgetSize.rheight()-2);
25 // build a list that contains two clicks
26 QTestEventList list1;
27 list1.addMouseClick( Qt::LeftButton, 0, clickPoint);
28 list1.addMouseClick( Qt::LeftButton, 0, clickPoint);
29 // call that list on the widget
30 list1.simulate( &dateEdit );
31 // and we should have decreased day by two (one less than original)
32 QCOMPARE( dateEdit.date(), date.addDays(-1) );
33 QTest::keyClicks( &dateEdit, "25122005" );
34 QCOMPARE( dateEdit.date(), QDate( 2005, 12, 25 ) );
35 QTestEventList list2;
36 list2.addKeyClick( Qt::Key_Tab, Qt::ShiftModifier );
37 list2.addKeyClicks( "08" );
38 list2.simulate( &dateEdit );
39 QCOMPARE( dateEdit.date(), QDate( 2005, 8, 25 ) );
40 }
41 void testDateEdit::testValidator_data()
42 {
43 qRegisterMetaType<QDate>("QDate");
44 QTest::addColumn<QDate>( "initialDate" );
45 QTest::addColumn<QTestEventList>( "events" );
46 QTest::addColumn<QDate>( "finalDate" );
47 QTestEventList eventsList1;
48 // this next line is just to start editing
49 eventsList1.addKeyClick( Qt::Key_Enter );
50 eventsList1.addKeyClicks( "12041968" );
51 QTest::newRow( "1968/4/12" ) << QDate( 1967, 3, 11 )
52 << eventsList1
53 << QDate( 1968, 4, 12 );
54 QTestEventList eventsList2;
55 eventsList2.addKeyClick( Qt::Key_Enter );
56 eventsList2.addKeyClicks( "140abcdef[" );
57 QTest::newRow( "1967/3/14" ) << QDate( 1967, 3, 11 )
58 << eventsList2
59 << QDate( 1967, 3, 14 );
60 // more rows can go in here
61 }
62 void testDateEdit::testValidator()
63 {
64 QFETCH( QDate, initialDate );
65 QFETCH( QTestEventList, events );
66 QFETCH( QDate, finalDate );
67 QDateEdit dateEdit( initialDate );
68 events.simulate( &dateEdit);
69 QCOMPARE( dateEdit.date(), finalDate );
70 }
71 QTEST_MAIN(testDateEdit)
72 #include "tutorial3a.moc"
|
This example is pretty much the same as the previous version,
up to line 25. In line 26, we create a
QTestEventList. We add events to the
list in lines 27 and 28 - note that we don't specify the
widget we are calling them on at this stage. In line 30, we
simulate each event on the widget. If we
had multiple widgets, we could call
simulate using the same set of events.
Lines 31 to 34 are as per the previous example.
We create another list in lines 35 to 37, although this time we are using addKeyClick and addKeyClicks instead of adding mouse events. Note that an event list can contain combinations of mouse and keyboard events - it just didn't make sense in this test to have such a combination. We use the second list at line 38, and check the results in line 39.
You can also build lists of events in data driven testing as
well, as shown in lines 41 to 70. The key difference is that
instead of fetching a QString in each
row, we are fetching a
QTestEventList. This requires that we
add a column of QTestEventList, rather
than QString (see line 45). At lines 47
to 50, we create a list of events. At line 52 we add those
events to the applicable row. We create a second list at lines
54 to 56, and add that second list to the applicable row in
line 58.
We fetch the events in line 65, and use them in line 68. If we had multiple widgets, then we could use the same event list several times.
| <<< Previous | Home | Next >>> |
| Tutorial 2 - Data driven testing of a date class | Tutorial 4 - Testing for failure and avoiding tests |