Boucle d'événement Qt et test de l'unité?

nous avons commencé à expérimenter les tests unitaires dans Qt et nous aimerions entendre des commentaires sur un scénario qui implique des signaux et des slots de test unitaires.

Voici un exemple:

Le code que j'aimerais tester est (m_socket est un pointeur sur QTcpSocket):

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

comme c'est un appel asynchrone, Je ne peux pas tester une valeur retournée. Je voudrais cependant tester si le signal de réponse que la socket émet sur une connexion réussie (void connected ()) est en fait émettre.

j'ai écrit le test ci-dessous:

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

ma motivation était, si la réponse n'arrive pas en 250ms, quelque chose ne va pas.

cependant, le signal n'est jamais capté, et je ne peux pas dire avec certitude s'il est émis. Mais j'ai remarqué que je ne lance pas la boucle de l'événement nulle part dans le projet de test. Dans le projet de développement, la boucle de l'événement est lancée principalement avec QCoreApplication::exec().


pour résumer, lorsque l'unité teste une classe qui dépend des signaux et des slots, où les

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

être exécuté dans l'environnement de test?

15
demandé sur Paul Sweatte 2014-02-06 18:50:58

3 réponses

je me rends compte que c'est un vieux fil, mais comme je l'ai frappé et que d'autres le feront, il n'y a pas de réponse et la réponse de peter et d'autres commentaires manquent toujours le point D'utiliser QSignalSpy.

Pour vous répondre question "où la QCoreApplication fonction exec est nécessaire", fondamentalement, la réponse est, il n'est pas. QTest et QSignalSpy a déjà construit dans.

ce que vous devez vraiment faire dans votre cas de test est "exécuter" la boucle d'événement existante.

en Supposant que vous utilisez Qt 5: http://doc.qt.io/qt-5/qsignalspy.html#wait

Donc à modifier votre exemple pour utiliser la fonction d'attente:

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);
}

cela devrait vous donner le comportement désiré sans avoir à créer une autre boucle d'événement.

7
répondu Dan Hogan 2016-11-22 18:44:25

bonne question. Les principaux problèmes que j'ai frappé sont (1) Besoin de laisser l'application Faire app.exec() encore proche à la fin de ne pas bloquer les compilations automatisées et (2) ayant besoin de s'assurer que les événements en attente sont traités avant de compter sur le résultat des appels de signal/slot.

Pour (1), vous pourriez essayer de commenter l'application.exec() dans main(). mais alors si quelqu'un a FooWidget.exec () dans leur classe que vous testez, il va bloquer/pendre. Quelque chose comme ça est pratique pour forcer qApp à sortir:

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;
}

pour (2), en gros vous devez forcer QApplication à terminer les événements en file d'attente si vous essayez de vérifier des choses comme QEvents de la souris + clavier ont un résultat attendu. Ce FlushEvents<>() méthode est utile:

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);
} };

exemple d'Utilisation ci-dessous. "dialog" est l'instance de MyDialog. "baz" est un exemple de Baz. "dialog" a un membre de la barre de type. Quand une barre sélectionne une Baz, elle émet un signal; "dialog" est connecté au signal et nous devons assurez-vous que la fente associée a reçu le 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>*/
}
4
répondu peter karasev 2014-04-09 03:00:03

j'ai eu un problème similaire avec Qt::QueuedConnection (l'événement est mis en file d'attente automatiquement si l'expéditeur et le destinataire appartiennent à des threads différents). Sans boucle d'événement appropriée dans cette situation, l'état interne des objets dépendant du traitement de l'événement ne sera pas mis à jour. Pour lancer une boucle d'événement lors de L'exécution de QTest, changez la macro QTEST_APPLESS_MAIN à la fin du fichier QTEST_MAIN. Puis, appelant qApp->processEvents() va réellement traiter les événements, ou vous pouvez lancer une autre boucle d'événement avec 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
0
répondu fgiraldeau 2017-07-17 21:07:20