【问题标题】:Qt event loop and unit testing?Qt 事件循环和单元测试?
【发布时间】:2014-03-03 14:07:25
【问题描述】:

我们开始在 Qt 中进行单元测试,并希望在涉及单元测试信号和槽的场景中听到 cmets。

这是一个例子:

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

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

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

我已经写了下面的测试:

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

我的动机是,如果在 250 毫秒内没有响应,那就是有问题。

但是,信号从未被捕获,我无法确定它是否已发出。但是我注意到我没有在测试项目的任何地方启动事件循环。在开发项目中,事件循环在 main 中以QCoreApplication::exec() 启动。


综上所述,当对依赖于信号和槽的类进行单元测试时,

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

在测试环境中运行?

【问题讨论】:

  • 在 main() 函数中?您可以在单元测试中使用QTEST_MAIN(CommunicationProtocolTest) 宏。
  • qWait 旋转它自己的事件循环,所以这不是问题。尝试更长的超时时间。理想情况下,您将提供自己的 QAbstractSocket 模型实现,用于测试。
  • @vahancho 谢谢,这解决了这个问题,但是它限制了我每次运行只能测试一个类。
  • @Alan,为什么?您可以根据需要测试任意数量的组件。只需向您的班级添加新的测试功能并执行验证。或者我误解了限制。
  • @vahancho 我每个需要测试的课程都有一个测试课程。对于更大的项目,缺少分组会被忽略。

标签: qt unit-testing event-handling qt4 event-loop


【解决方案1】:

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

对于 (1),您可以尝试在 main() 中注释掉 app.exec()。 BUT 那么如果有人在你正在测试的类中有 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;
}

对于 (2),基本上,如果您试图验证来自鼠标 + 键盘的 QEvents 之类的事情是否具有预期结果,则基本上您必须强制 QApplication 完成排队的事件。这个FlushEvents&lt;&gt;() 方法很有帮助:

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

使用示例如下。 “对话”是 MyDialog 的实例。 “baz”是 Baz 的实例。 "dialog" 有一个 Bar 类型的成员。 当 Bar 选择 Baz 时,它会发出信号; “对话框”连接到信号,我们需要 确保关联的插槽已收到消息。

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>*/
}

【讨论】:

    【解决方案2】:

    我意识到这是一个旧线程,但是当我点击它以及其他人会发现它时,没有答案,而 peter 和其他 cmets 的答案仍然错过了使用 QSignalSpy 的重点。

    回答你关于“哪里需要 QCoreApplication exec 函数”的原始问题,基本上答案是,不需要。 QTest 和 QSignalSpy 已经内置了。

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

    假设您使用的是 Qt 5: http://doc.qt.io/qt-5/qsignalspy.html#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);
    }
    

    这应该会为您提供所需的行为,而无需创建另一个事件循环。

    【讨论】:

    • QSignalSpy::wait() 需要QTEST_MAIN 而不是QTEST_APPLESS_MAIN,否则我会得到QEventLoop: Cannot be used without QApplication
    • 可能还需要QTEST_GUILESS_MAIN,否则你需要一个显示器。
    【解决方案3】:

    Qt::QueuedConnection 也有类似的问题(如果发送方和接收方属于不同的线程,则事件会自动排队)。在这种情况下,如果没有适当的事件循环,将不会更新依赖于事件处理的对象的内部状态。要在运行 QTest 时启动事件循环,请将文件底部的宏 QTEST_APPLESS_MAIN 更改为 QTEST_MAIN。然后,调用qApp-&gt;processEvents() 将实际处理事件,或者您可以使用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
    

    【讨论】:

    • 为我工作。我正在测试一个线程,似乎事件循环没有像你提到的那样处理事件,我需要使用你的方法和一个脏循环,如下所示。 while(i processEvents(); } 当调用所需的插槽时,结果设置为 1。
    猜你喜欢
    • 1970-01-01
    • 2011-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-09
    • 1970-01-01
    • 2018-06-16
    相关资源
    最近更新 更多