Test modal dialog with Qt Test

2019-02-13 17:59发布

I am trying to write a unit test for a GUI application using the QTestLib. The problem is that one of the slots creates a modal dialog using exec() and I found no possibility to interact with the dialog.

The slots which creates the dialog is connected to a QAction. So the first problem is that the test blocks when I trigger the QAction in the test since this results in the call to exec(). Therefore, I tried creating a QThread that performs the interaction. However, this did not help.

Things I already tried (all performed from within the "interaction helper" thread):

  1. Send key clicks using QTest::keyClicks()
    • Results in error message "QCoreApplication::sendEvent(): Cannot send events to objects owned by a different thread"
  2. Post QKeyEvents using QCoreApplication::postEvent()
    • Doesn't work, i.e. nothing happens. I guess because the events end up in the event loop of the thread that owns the dialog, which will not be reached until the dialog is closed and exec() returns. See Edit below
  3. Invoking Slots on the dialog using QMetaObject::invokeMethod()
    • Doesn't work, i.e. nothing happens. I guess for the same reason as postEvent() doesn't work. See Edit below

So the question is: Is there any way to interact programmatically with a modal dialog that was opened using the exec() method?

Edit: Actually, method 3 is working. The problem was a different one: I passed the arguments to invokeMethod() to the "interaction helper" thread and for some reason, accessing the arguments did not work from that thread (I got no SEG errors but they were simply empty). I guess that method 2 is also working and I simply had the same problem as with method 3 but I didn't test that.

4条回答
啃猪蹄的小仙女
2楼-- · 2019-02-13 18:10

The solution I use in command line applications which use Qt libraries meant for GUIs is the singleShot, as this answer alludes. In those cases it looks like this:

QCoreApplication app(argc, argv);

// ...

QTimer::singleShot(0, &app, SLOT(quit()));
return app.exec();

So in your case I imagine it would look something like this:

QDialog * p_modalDialog = getThePointer(); // you will have to replace this with
                                           // a real way of getting the pointer

QTimer::singleShot(0, p_modalDialog, SLOT(accept()));

p_modalDialog->exec(); // called somewhere else in your case
                       // but it will be automatically accepted.
查看更多
孤傲高冷的网名
3楼-- · 2019-02-13 18:18

related question's answer has some extra details about flushing the event queue during a test: Qt event loop and unit testing?

查看更多
唯我独甜
4楼-- · 2019-02-13 18:26

You can keep the interaction in the same thread by delaying its execution until the dialog event loop starts.

For example just before the exec() call, you use either QTimer::singleShot with 0 as interval or QMetaObject::invokeMethod with connection type Qt::QueuedConnection to invoke the slot that needs to be executed while the dialog is shown.

查看更多
走好不送
5楼-- · 2019-02-13 18:27

You can also post the events before calling exec(). As soon as the dialog has been constructed after the exec() call, the events will be executed.

For example to test an Esc key press (means reject/close the dialog):

// create a dialog
QDialog d = ...
//post an Escape key press and release event
QApplication::postEvent(&d, new QKeyEvent(QEvent::KeyPress  , Qt::Key_Escape, Qt::NoModifier) );
QApplication::postEvent(&d, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Escape, Qt::NoModifier) );
// execute and check result
int ret = d.exec();
QCOMPARE(ret, static_cast<int>(QDialog::Rejected));
查看更多
登录 后发表回答