我正在为我的GUI应用程序创建一个测试。在测试的某个时刻,我想点击一个按钮,请求用户确认,然后确认我必须删除的每个文件。所以,在测试中,按下我正在做的按钮:
QTest::mouseClick(m_widget->removeButton, Qt::LeftButton);
但现在,对于我得到的第一个QMessageBox
,我可以点击"是":
QKeyEvent *evr = new QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier);
QApplication::postEvent(&m_widget->reply, evr);
从这里开始,我必须要求确认我要删除的每个文件,但在我自己用鼠标点击或找到任何解决方案之前,我无法执行任何其他操作。我在qDebug中观察到,在单击所有QMessageBox
之前,它不会进一步执行mouseClick函数(它可能是一个或多个(。
所有的QMessageBox
都是应用程序上的本地变量,没有什么是静态的。
我们通过在消息框上添加一层抽象来解决类似的问题。我们有一个全局对象,具有如下"显示"消息框和对话框的功能:
struct QtFuncs
{
typedef std::function<int(QMessageBox*)> MessageBoxExec;
MessageBoxExec messageBoxExec = [](QMessageBox* mb) { return mb->exec(); };
// more functions for dialogs and standard message boxes (open file, ...)
};
struct QtGlobalFuncs
{
static QtFuncs& instance()
{
static auto fn = QtFuncs();
return fn;
}
static int messageBoxExec(QMessageBox* box)
{
return instance().messageBoxExec(box);
}
};
当我们想要/需要执行一个消息框时,我们会通过"执行"它
QMessageBox box(QMessageBox::Critical, "hi", "do you want to greet bob?", QMessageBox::Yes | QMessageBox::No);
auto button = QtGlobalFuncs::messageBoxExec(&box);
请注意,此方法要求您将所有QMessageBox::exec
调用替换为QtGlobalFuncs::messageBoxExec
。在我们的测试场景中,我们将覆盖内部函数:
int nTimesExecCalled = 0;
QtGlobalFuncs::instance().messageBoxExec = [&nTimesExecCalled](auto box)
{
int res = QMessageBox::Yes;
if (nTimesExecCalled)
res = QMessageBox::No;
++nTimesExecCalled;
return res;
};
QMessageBox box(QMessageBox::Critical, "hi", "do you want to greet bob?", QMessageBox::Yes | QMessageBox::No);
auto button = QtGlobalFuncs::messageBoxExec(&box);
我希望这个小例子能帮助你理解我们是如何为我们解决这个问题的,也许它也会帮助你。
祝你今天愉快:(
我有一个解决这个问题的方案,它不需要对生产代码进行任何修改。这个流中的主要问题是QMessageBox
通常是用它自己的消息循环(通过exec()
方法(调用的。这意味着像这样的直接解决方案将不起作用:
p_button->show();
QTest::qWaitForWindowActive(p_btn);
QTest::mouseClick(p_button, Qt::LeftButton);//connected to msgbox.exec()
//next row will be not executed since we are still in event loop of msgbox
QTest::keyEvent(QTest::Click, qApp->activeWindow(), Qt::Key_Return);
因此,您需要期望QMessageBox在出现之前。一种方法是创建eventFilter,它将查找激活的QMessageBox
。这样,如果需要,还可以验证QMessageBox
属性。
想象一下你有这样的功能:
QAbstractButton* CreateWidget(QWidget* ip_parent)
{
auto p_btn = new QPushButton(ip_parent);
QObject::connect(p_btn, &QAbstractButton::pressed, []() {
QMessageBox msgBox;
msgBox.setText("Are you sure?");
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.exec();
});
return p_btn;
}
它将创建一个按钮,该按钮将在按下时执行QMessageBox
。要测试它,你可以使用像这样的助手:
class MessageWatcher : public QObject {
public:
using tDialogChecker = std::function<void(QMessageBox*)>;
MessageWatcher(tDialogChecker i_checker, QObject* ip_parent = nullptr)
: QObject(ip_parent)
, m_checker(i_checker)
{
qApp->installEventFilter(this);
}
bool eventFilter(QObject* ip_obj, QEvent* ip_event) override
{
if (auto p_dlg = qobject_cast<QMessageBox*>(ip_obj)) {
if (ip_event->type() == QEvent::WindowActivate) {
m_checker(p_dlg);
return true;
}
}
return false;
}
private:
tDialogChecker m_checker;
};
当它接收到QMessageBox
的类型为QEvent::WindowActivate
的事件时,它将调用lambda。您可以执行任何与QMessageBox
本身相关的检查,也可以在那里执行该QMessageBox
的闭包。考虑一下这个简单的测试:
class WidgetLibTest : public QObject {
Q_OBJECT
private slots:
void WidgetLibCheck();
};
void WidgetLibTest::WidgetLibCheck()
{
MessageWatcher watcher([](auto ip_msg_box)
{
auto closer = qScopeGuard([ip_msg_box] { QTest::keyEvent(QTest::Click, ip_msg_box, Qt::Key_Return); });
QCOMPARE(ip_msg_box->text(), "Are you sure?");
});
auto p_btn = std::unique_ptr<QAbstractButton>(CreateWidget(nullptr));
p_btn->show();
QTest::qWaitForWindowActive(p_btn.get());
QTest::mouseClick(p_btn.get(), Qt::LeftButton);//will execute QMessageBox
}
qScopeGuard
是必需的,因为当QCOMPARE
失败时,它将调用return,从而跳过代码的其余部分。所以,要每次关闭QMessageBox,即使检查不正确,也需要使用它。
以类似的方式,您也可以测试QProgressDialog
或任何将在纠缠实现中弹出的对话框。此外,还可以测试窗口小部件\对话框的级联,只有在这种情况下,您才需要某种函数数组。但我建议避免这种情况,并重组实施,以便可以单独测试每个组件。