Qt event loop and unit testing?

11,116

Solution 1

I realize this is an old thread but as I hit it and as others will, there is no answer and the answer by peter and other comments still miss the point of using QSignalSpy.

To answer you original question about "where the QCoreApplication exec function is needed", basically the answer is, it isn't. QTest and QSignalSpy already has that built in.

What you really need to do in your test case is "run" the existing event loop.

Assuming you are using Qt 5: http://doc.qt.io/qt-5/qsignalspy.html#wait

So to modify your example to use the wait function:

void CommunicationProtocolTest::testConnectToCammera()
{
    QSignalSpy spy(communicationProtocol->m_socket, SIGNAL(connected()));
    communicationProtocol->connectToCamera();

    // wait returns true if 1 or more signals was emitted
    QCOMPARE(spy.wait(250), true);

    // You can be pedantic here and double check if you want
    QCOMPARE(spy.count(), 1);
}

That should give you the desired behaviour without having to create another event loop.

Solution 2

Good question. Main issues I've hit are (1) needing to let app do app.exec() yet still close-at-end to not block automated builds and (2) needing to ensure pending events get processed before relying on the result of signal/slot calls.

For (1), you could try commenting out the app.exec() in main(). BUT then if someone has FooWidget.exec() in their class that you're testing, it's going to block/hang. Something like this is handy to force qApp to exit:

int main(int argc, char *argv[]) {
    QApplication a( argc, argv );   

    //prevent hanging if QMenu.exec() got called
    smersh().KillAppAfterTimeout(300);

    ::testing::InitGoogleTest(&argc, argv);
    int iReturn = RUN_ALL_TESTS(); 
    qDebug()<<"rcode:"<<iReturn;

    smersh().KillAppAfterTimeout(1);
    return a.exec();
   }

struct smersh {
  bool KillAppAfterTimeout(int secs=10) const;
};

bool smersh::KillAppAfterTimeout(int secs) const {
  QScopedPointer<QTimer> timer(new QTimer);
  timer->setSingleShot(true);
  bool ok = timer->connect(timer.data(),SIGNAL(timeout()),qApp,SLOT(quit()),Qt::QueuedConnection);
  timer->start(secs * 1000); // N seconds timeout
  timer.take()->setParent(qApp);
  return ok;
}

For (2), basically you have to coerce QApplication into finishing up the queued events if you're trying to verify things like QEvents from Mouse + Keyboard have expected outcome. This FlushEvents<>() method is helpful:

template <class T=void> struct FlushEvents {     
 FlushEvents() {
 int n = 0;
 while(++n<20 &&  qApp->hasPendingEvents() ) {
   QApplication::sendPostedEvents();
   QApplication::processEvents(QEventLoop::AllEvents);
   YourThread::microsec_wait(100);
 }
 YourThread::microsec_wait(1*1000);
} };

Usage example below. "dialog" is instance of MyDialog. "baz" is instance of Baz. "dialog" has a member of type Bar. When a Bar selects a Baz, it emits a signal; "dialog" is connected to the signal and we need to make sure the associated slot has gotten the message.

void Bar::select(Baz*  baz) {
  if( baz->isValid() ) {
     m_selected << baz;
     emit SelectedBaz();//<- dialog has slot for this
}  }    

TEST(Dialog,BarBaz) {  /*<code>*/
dialog->setGeometry(1,320,400,300); 
dialog->repaint();
FlushEvents<>(); // see it on screen (for debugging)

//set state of dialog that has a stacked widget
dialog->setCurrentPage(i);
qDebug()<<"on page: "
        <<i;      // (we don't see it yet)
FlushEvents<>();  // Now dialog is drawn on page i 

dialog->GetBar()->select(baz); 
FlushEvents<>(); // *** without this, the next test
                 //           can fail sporadically.

EXPECT_TRUE( dialog->getSelected_Baz_instances()
                                 .contains(baz) );
/*<code>*/
}

Solution 3

I had a similar issue with Qt::QueuedConnection (event is queued automatically if the sender and the receiver belongs to different threads). Without a proper event loop in that situation, the internal state of objects depending on event processing will not be updated. To start an event loop when running QTest, change the macro QTEST_APPLESS_MAIN at the bottom of the file to QTEST_MAIN. Then, calling qApp->processEvents() will actually process events, or you can start another event loop with QEventLoop.

   QSignalSpy spy(&foo, SIGNAL(ready()));
   connect(&foo, SIGNAL(ready()), &bar, SLOT(work()), Qt::QueuedConnection);
   foo.emitReady();
   QCOMPARE(spy.count(), 1);        // QSignalSpy uses Qt::DirectConnection
   QCOMPARE(bar.received, false);   // bar did not receive the signal, but that is normal: there is no active event loop
   qApp->processEvents();           // Manually trigger event processing ...
   QCOMPARE(bar.received, true);    // bar receives the signal only if QTEST_MAIN() is used
Share:
11,116

Related videos on Youtube

TheMeaningfulEngineer
Author by

TheMeaningfulEngineer

Would you like to release working software more often? Is testing the bottleneck of your develpoment process? Do you have coorfdinated beta testing in place? Are developers overwhelemed with unclear bug reports? I help companies refine and automate the testing process resulting in a sustainable delivery of quality software. Let's have a non binding virtual coffe and talk about the problems you're facing.

Updated on June 04, 2022

Comments

  • TheMeaningfulEngineer
    TheMeaningfulEngineer almost 2 years

    I'we started experimenting with unit testing in Qt and would like to hear comments on a scenario that involves unit testing signals and slots.

    Here is an example:

    The code i would like to test is (m_socket is a pointer to QTcpSocket):

    void CommunicationProtocol::connectToCamera()
    {
        m_socket->connectToHost(m_cameraIp,m_port);
    }
    

    Since that is an asynchronous call i can't test a returned value. I would however like to test if the response signal that the socket emits on a successful connection (void connected ()) is in fact emitted.

    I've written the test below:

    void CommunicationProtocolTest::testConnectToCammera()
    {
        QSignalSpy spy(communicationProtocol->m_socket, SIGNAL(connected()));
        communicationProtocol->connectToCamera();
        QTest::qWait(250);
        QCOMPARE(spy.count(), 1);
    }
    

    My motivation was, if the response doesn't happen in 250ms, something is wrong.

    However, the signal is never caught, and I can't say for sure if it's even emitted. But I've noticed that I'm not starting the event loop anywhere in the test project. In the development project, the event loop is started in main with QCoreApplication::exec().


    To sum it up, when unit testing a class that depends on signals and slots, where should the

    QCoreApplication a(argc, argv);
    return a.exec();
    

    be run in the test environment?

    • vahancho
      vahancho about 10 years
      In the main() function? You can use QTEST_MAIN(CommunicationProtocolTest) macro in your unit test.
    • Kuba hasn't forgotten Monica
      Kuba hasn't forgotten Monica about 10 years
      qWait spins its own event loop, so that's not a problem. Try with a longer timeout. Ideally, you would provide your own mockup implementation of a QAbstractSocket, used for testing.
    • TheMeaningfulEngineer
      TheMeaningfulEngineer about 10 years
      @vahancho Thanks, that solved the issue, however it limits me to test only a single class per run.
    • vahancho
      vahancho about 10 years
      @Alan, why? You can test as many components as you need. Just add new test function to your class and perform verification. Or I misunderstood the limitation.
    • TheMeaningfulEngineer
      TheMeaningfulEngineer about 10 years
      @vahancho I'm having one test class per one class that needs testing. For a bigger project the lack of grouping would be missed.
    • vahancho
      vahancho about 10 years
      @Alan, I think it is common practice for unit tests - test class (an executable) per class. You run them with a batch script of so. However you can have tests for multiple classes in one test class too.
    • TheMeaningfulEngineer
      TheMeaningfulEngineer about 10 years
      One executable per class? In my case, that would result in opening a new test project for each class. From the current perspective, it seems a bit redundant.
  • fgiraldeau
    fgiraldeau almost 7 years
    QSignalSpy::wait() requires QTEST_MAIN instead of QTEST_APPLESS_MAIN, otherwise I get QEventLoop: Cannot be used without QApplication.
  • Halfgaar
    Halfgaar about 6 years
    One may also want QTEST_GUILESS_MAIN, otherwise you need a display.
  • user2019716
    user2019716 over 4 years
    works for me. I am testing a thread and it seems that event loop is not processing the events as you mentioned and I need to use you method with a dirty loop as follow. while(i < 100 && result == 0){ thread().msleep(100); qApp->processEvents(); } result is set to 1 when the desired slot is called.