Qt事件循环和单元测试?

The*_*eer 23 qt unit-testing qt4 event-handling event-loop

我开始尝试在Qt中进行单元测试,并希望听到有关涉及单元测试信号和插槽的场景的评论.

这是一个例子:

我想测试的代码是(m_socket是指向QTcpSocket):

void CommunicationProtocol::connectToCamera()
{
    m_socket->connectToHost(m_cameraIp,m_port);
}
Run Code Online (Sandbox Code Playgroud)

由于这是一个异步调用,我无法测试返回的值.但是,我想测试套接字在成功的connection(void connected ())上发出的响应信号是否实际发出.

我在下面写了测试:

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

我的动机是,如果响应不会在250毫秒内发生,那就是错误的.

然而,信号永远不会被捕获,我无法确定它是否发射.但我注意到我没有在测试项目的任何地方启动事件循环.在开发项目中,事件循环以main开头QCoreApplication::exec().


总结一下,当单元测试一个依赖于信号和插槽的类时,应该在哪里

QCoreApplication a(argc, argv);
return a.exec();
Run Code Online (Sandbox Code Playgroud)

在测试环境中运行?

Dan*_*gan 10

我意识到这是一个老线程,但是当我点击它和其他人的时候,没有答案,彼得和其他评论的答案仍然没有使用QSignalSpy.

为了回答你关于"需要QCoreApplication exec功能的地方"的原始问题,基本上答案是,它不是.QTest和QSignalSpy已经内置了.

您在测试用例中真正需要做的是"运行"现有的事件循环.

假设您使用的是Qt 5:http: //doc.qt.io/qt-5/qsignalspy.html#wait

所以要修改你的例子以使用wait函数:

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);
}
Run Code Online (Sandbox Code Playgroud)

这应该给你所需的行为,而不必创建另一个事件循环.

  • `QSignalSpy :: wait()`需要`QTEST_MAIN`而不是`QTEST_APPLESS_MAIN`,否则我得到`QEventLoop:没有QApplication`就不能使用. (4认同)
  • 人们可能也想要`QTEST_GUILESS_MAIN`,否则你需要一个显示器. (2认同)

pet*_*sev 6

好问题.我遇到的主要问题是(1)需要让应用程序执行app.exec()但仍然关闭以阻止自动构建和(2)需要确保在依赖信号结果之前处理待处理事件/ slot调用.

对于(1),您可以尝试在main()中注释掉app.exec().但是,如果有人在他们正在测试的类中有FooWidget.exec(),那么它将阻止/挂起.像这样的东西迫使qApp退出:

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;
}
Run Code Online (Sandbox Code Playgroud)

对于(2),如果你试图验证像QEvent鼠标+键盘这样的东西有预期的结果,你基本上必须强制QApplication完成排队的事件.这种FlushEvents<>()方法很有帮助:

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);
} };
Run Code Online (Sandbox Code Playgroud)

下面的用法示例."对话框"是MyDialog的实例."巴兹"是巴兹的例子."dialog"具有Bar类型的成员.当Bar选择Baz时,它会发出一个信号; "dialog"连接到信号,我们需要确保相关的插槽已经获得了消息.

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>*/
}
Run Code Online (Sandbox Code Playgroud)