n3m*_*3mo 2 c++ qt unit-testing
我想测试QMessageBox我创建的自定义。特别是,我的 API 将自动执行exec()的循环QMessageBox,并且我想在用户单击按钮或关闭小部件时检查结果。
问题是我被困在exec()循环中了。
我发现的唯一解决方案是创建一个QTimer在循环执行之前执行我想要的操作的解决方案。对此解决方案的引用是this和this。
测试工作正常,但 Qt 中确实没有更原生的方法来测试sexec()的循环QDialog吗?我的意思是,有办法激活/模拟信号QTest,没有什么可以模拟循环exec()吗?
为什么要“模拟”事件循环?您想运行真正的事件循环!您只想将代码注入其中,这就是计时器(或一般事件)的用途。
Qt Test 在事件循环周围有各种方便的包装器,例如keyClick传递事件并排空事件队列等,但您不需要模拟任何东西。如果您找不到所需的东西,您可以自己制作。
您正在使用的 API 当然已损坏:它假装异步操作是同步的。它应该将控件反转为连续传递样式,即:
// horrible
int Api::askForValue();
// better
Q_SIGNAL void askForValueResult(int);
void Api::askForValue(); // emits the signal
template <typename F>
void Api::askForValue(F &&fun) { // convenience wrapper
auto *conn = new QMetaObject::Connection;
*conn = connect(this, &Class::askForValueResult,
[fun = std::forward<F>(fun),
c = std::unique_ptr<QMetaObject::Connection>(conn)]
(int val){
fun(val);
QObject::disconnect(*c);
});
askForValue();
}
Run Code Online (Sandbox Code Playgroud)
同步API使得很多应用程序代码不得不面对重入,而且这不是一个假设的问题。对此的疑问并不少见。很少有人意识到这个问题有多严重。
但假设您被迫坚持使用可怕的 API:您真正想要的是对显示的消息窗口做出反应。您可以通过在QEvent::WindowActivate. 只需在应用程序实例上安装事件过滤器对象即可。这可以包含在一些辅助代码中:
/// Invokes setup, waits for a new window of a type T to be activated,
/// then invokes fun on it. Returns true when fun was invoked before the timeout elapsed.
template <typename T, typename F1, typename F2>
bool waitForWindowAnd(F1 &&setup, F2 &&fun, int timeout = -1) {
QEventLoop loop;
QWidgetList const exclude = QApplication::topLevelWidgets();
struct Filter : QObject {
QEventLoop &loop;
F2 &fun;
bool eventFilter(QObject *obj, QEvent *ev) override {
if (ev.type() == QEvent::WindowActivate)
if (auto *w = qobject_cast<T*>(obj))
if (w->isWindow() && !exclude.contains(w)) {
fun(w);
loop.exit(0);
}
return false;
}
Filter(QEventLoop &loop, F2 &fun) : loop(loop), fun(fun) {}
} filter{loop, fun};
qApp->installEventFilter(&filter);
QTimer::singleShot(0, std::forward<F1>(setup));
if (timeout > -1)
QTimer::singleShot(timeout, &loop, [&]{ loop.exit(1); });
return loop.exec() == 0;
}
Run Code Online (Sandbox Code Playgroud)
可以制作更多的包装器来排除常见的要求:
/// Invokes setup, waits for new widget of type T to be shown,
/// then clicks the standard OK button on it. Returns true if the button
/// was clicked before the timeout.
template <typename T, typename F>
bool waitForStandardOK(F &&setup, int timeout = -1) {
return waitForWindowAnd<T>(setup,
[](QWidget *w){
if (auto *box = w->findChild<QDialogButtonBox*>())
if (auto *ok = box->standardButton(QDialogButtonBox::Ok))
ok->click();
}, timeout);
}
Run Code Online (Sandbox Code Playgroud)
然后,假设您想测试阻塞 API QDialogInput::getInt:
waitForStandardOK<QDialog>([]{ QInputDialog::getInt(nullptr, "Hello", "Gimme int!"); }, 500);
Run Code Online (Sandbox Code Playgroud)
您还可以创建一个包装器来构建绑定调用,而无需使用 lambda:
/// Binds and invokes the setup call, waits for new widget of type T to be shown,
/// then clicks the standard OK button on it. Returns true if the button
/// was clicked before the timeout.
template<typename T, class ...Args>
void waitForStandardOKCall(int timeout, Args&&... setup) {
auto bound = std::bind(&invoke<Args&...>, std::forward<Args>(args)...);
return waitForStandardOK<T>(bound, timeout);
}
Run Code Online (Sandbox Code Playgroud)
因此:
waitForStandardOKCall<QDialog>(500, &QInputDialog::getInt, nullptr, "Hello", "Gimme int!");
Run Code Online (Sandbox Code Playgroud)
有关std::bind完美转发的更多信息,请参阅此问题。
| 归档时间: |
|
| 查看次数: |
1039 次 |
| 最近记录: |