Qt事件循环和单元测试? [英] Qt event loop and unit testing?

查看:331
本文介绍了Qt事件循环和单元测试?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



这是一个例子:



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

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

由于这是一个异步调用,我无法测试返回的值。然而,我想测试一下socket的成功连接发送的响应信号( void connected())是否真的发出了。



我在下面写了测试:

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

我的动机是,如果回应在250ms内没有发生,



然而,信号从来没有被捕获,我无法确定如果它甚至发射。但是我注意到,我没有在测试项目的任何地方开始事件循环。在开发项目中,事件循环主要以 QCoreApplication :: exec()开始。






总而言之,当单元测试依赖于信号和插槽的课程时,应该在哪里

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

在测试环境中运行

解决方案

好问题。我遇到的主要问题是(1)需要让app do app.exec()仍然关闭,以阻止自动构建,(2)需要确保待处理的事件在依赖于信号的结果之前被处理/插槽调用。



对于(1),您可以尝试在main()中注释掉app.exec()。那么如果有人在你测试的类中有FooWidget.exec(),那么它将被阻止/挂起。这样做很有用,强制qApp退出:

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

//如果QMenu.exec()被调用
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>定时器(新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),基本上你必须强制QApplication完成排队如果您尝试验证类似的事件,则鼠标+键盘的QEvent 可能会有预期的结果。这个 FlushEvents<>()方法是有帮助的:

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

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

  void Bar :: select(Baz * baz){
if(baz-> isValid()){
m_selected<<巴兹;
发出SelectedBaz(); //< - 对话框有这个
的插槽}

TEST(Dialog,BarBaz){/ *< code> * /
dialog-> setGeometry(1,320,400,300);
dialog-> repaint();
FlushEvents<>(); //在屏幕上看到(用于调试)

//设置具有堆栈窗口的对话框的状态
dialog-> setCurrentPage(i);
qDebug()<<一页:
< FlushEvents<>(); //现在对话框在页面i上绘制

dialog-> GetBar() - > select(baz);
FlushEvents<>(); // ***没有这个,下一个测试
//可以零星地失败。

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


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?

解决方案

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

这篇关于Qt事件循环和单元测试?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆