| Writing Unittests for Qt4 and KDE4 with QtTestLib | ||
|---|---|---|
| <<< Previous | Next >>> | |
An important part of Qt programming is the use of signals and slots. This section covers the support for testing of these features.
![]() | If you are not familiar with Qt signals and slots, you probably should review the introduction to this feature provided in your Qt documentation. It is also available at http://doc.trolltech.com/latest/signalsandslots.html. You also might like to review Tutorial 5 in your Qt documentation. This tutorial is also available at http://doc.trolltech.com/4.1/tutorial-t5.html. |
Testing slots is very easy, because a slot is just a specially annotated method. You can call slots just like any other method you'd like to test, as shown below:
Example 25. QLabel test code,
showing testing of a couple of slots
1 #include <QtTest>
2 #include <QtCore>
3 #include <QtGui>
4 class testLabel: public QObject
5 {
6 Q_OBJECT
7 private slots:
8 void testChanges();
9 };
10 void testLabel::testChanges()
11 {
12 QLabel label;
13 // setNum() is a QLabel slot, but we can just call it like any
14 // other method.
15 label.setNum( 3 );
16 QCOMPARE( label.text(), QString("3") );
17 // clear() is also a slot.
18 label.clear();
19 QVERIFY( label.text().isEmpty() );
20 }
21 QTEST_MAIN(testLabel)
22 #include "tutorial5.moc"
|
Testing of signals is a little more difficult than testing of
slots, however Qt offers a very useful class called
QSignalSpy that helps a lot.
QSignalSpy is a class provided with Qt that allows you to record the signals that have been emitted from a particular QObject subclass object. YOu can then check that the right number of signals have been emitted, and that the right kind of signals were emitted. You can find more information on the QSignalSpy class in your Qt documentation, or at http://doc.trolltech.com/latest/qsignalspy.html.
An example of how you can use QSignalSpy to test a class that has signals is shown below.
Example 26. QCheckBox test code,
showing testing of signals
1 #include <QtTest>
2 #include <QtCore>
3 #include <QtGui>
4 class testCheckBox: public QObject
5 {
6 Q_OBJECT
7 private slots:
8 void testSignals();
9 };
10 void testCheckBox::testSignals()
11 {
12 // You don't need to use an object created with "new" for
13 // QSignalSpy, I just needed in in this case to test the emission
14 // of a destroyed() signal.
15 QCheckBox *xbox = new QCheckBox;
16 // We are going to have two signal monitoring classes in use for
17 // this test.
18 // The first monitors the stateChanged() signal.
19 // Also note that QSignalSpy takes a pointer to the object.
20 QSignalSpy stateSpy( xbox, SIGNAL( stateChanged(int) ) );
21
22 // Not strictly necessary, but I like to check that I have set up
23 // my QSignalSpy correctly.
24 QVERIFY( stateSpy.isValid() );
25 // Now we check to make sure we don't have any signals already
26 QCOMPARE( stateSpy.count(), 0 );
27 // Here is a second monitoring class - this one for the
28 // destroyed() signal.
29 QSignalSpy destroyedSpy( xbox, SIGNAL( destroyed() ) );
30 QVERIFY( destroyedSpy.isValid() );
31 // A sanity check to verify the initial state
32 // This also shows that you can mix normal method checks with
33 // signal checks.
34 QCOMPARE( xbox->checkState(), Qt::Unchecked );
35 // Shouldn't already have any signals
36 QCOMPARE( destroyedSpy.count(), 0 );
37 // If we change the state, we should get a signal.
38 xbox->setCheckState( Qt::Checked );
39 QCOMPARE( stateSpy.count(), 1 );
40 xbox->setCheckState( Qt::Unchecked );
41 QCOMPARE( stateSpy.count(), 2 );
42 xbox->setCheckState( Qt::PartiallyChecked );
43 QCOMPARE( stateSpy.count(), 3 );
44 // If we destroy the object, the signal should be emitted.
45 delete xbox;
46 // So the count of objects should increase.
47 QCOMPARE( destroyedSpy.count(), 1 );
48 // We can also review the signals that we collected
49 // QSignalSpy is really a QList of QLists, so we take the first
50 // list, which corresponds to the arguments for the first signal
51 // we caught.
52 QList<QVariant> firstSignalArgs = stateSpy.takeFirst();
53 // stateChanged() only has one argument - an enumerated type (int)
54 // So we take that argument from the list, and turn it into an integer.
55 int firstSignalState = firstSignalArgs.at(0).toInt();
56 // We can then check we got the right kind of signal.
57 QCOMPARE( firstSignalState, static_cast<int>(Qt::Checked) );
58 // check the next signal - note that takeFirst() removes from the list
59 QList<QVariant> nextSignalArgs = stateSpy.takeFirst();
60 // this shows another way of fudging the argument types
61 Qt::CheckState nextSignalState = (Qt::CheckState)nextSignalArgs.at(0).toInt();
62 QCOMPARE( nextSignalState, Qt::Unchecked );
63 // and again for the third signal
64 nextSignalArgs = stateSpy.takeFirst();
65 nextSignalState = (Qt::CheckState)nextSignalArgs.at(0).toInt();
66 QCOMPARE( nextSignalState, Qt::PartiallyChecked );
67 }
68 QTEST_MAIN(testCheckBox)
69 #include "tutorial5a.moc"
|
The first 11 lines are essentially unchanged from previous examples that we've seen. Line 15 creates the object that will be tested - as noted in the comments in lines 12-14, the only reason that I'm creating it with new is because I need to delete it in line 45 to cause the destroyed() signal to be emitted.
Line 20 sets up the first of our two
QSignalSpy instances. The one in line
20 monitors the stateChanged(int) signal,
and the one in line 29 monitors the
destroyed() signal. If you get the name
or signature of the signal wrong (for example, if you use
stateChanged() instead of
stateChanged(int)), then this will not be
caught at compile time, but will result in a runtime
failure. You can test if things were set up correctly using
the isValid(), as shown in lines 24 and
30.
As shown in line 34, there is no reason why you cannot test normal methods, signals and slots in the same test.
Line 38 changes the state of the object under test, which is supposed to result in a stateChanged(int) signal being emitted. Line 39 checks that the number of signals increases from zero to one. Lines 40 and 41 repeat the process, and again in lines 42 and 43.
Line 45 deletes the object under test, and line 47 tests that the destroyed() signal has been emitted.
For signals that have arguments (such as our stateChanged(int) signal), you may also wish to check that the arguments were correct. You can do this by looking at the list of signal arguments. Exactly how you do this is fairly flexible, however for simple tests like the one in the example, you can manually work through the list using takeFirst() and check that each argument is correct. This is shown in line 52, 55 and 57 for the first signal. The same approach is shown in lines 59, 61 and 62 for the second signal, and the in lines 64 to 66 for the third signal. For a more complex set of tests, you may wish to apply some data driven techniques.
![]() | You should be aware that, for some class implementations, you may need to return control to the event loop to have signals emitted. If you need this, try using the QTest::qWait(). |
| <<< Previous | Home | Next >>> |
| Tutorial 4 - Testing for failure and avoiding tests | Tutorial 6 - Integrating with GNU autoconf |