在Qtest中点击几个QMessageBox的按钮
Click on button for several QMessageBox in Qtest
我正在为我的 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 观察到它不会进一步执行 mouseClick 函数,直到所有 QMessageBox
都被点击(可能是一个或多个)。
所有QMessageBox
在应用程序上都是局部变量,没有什么是静态的。
我们通过在消息框上添加抽象层解决了类似的问题。我们有一个具有 'display' 消息框和对话框功能的全局对象,如下所示:
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);
}
};
当我们want/need执行一个消息框时,我们会'exec'它通过:
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
或任何将在您的纠缠实现中弹出的对话框。
也可以测试 widgets\dialogs 的级联,只是在那种情况下你需要某种仿函数数组。但我建议避免这种情况并重组实现,这样就可以分别测试每个组件。
我正在为我的 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 观察到它不会进一步执行 mouseClick 函数,直到所有 QMessageBox
都被点击(可能是一个或多个)。
所有QMessageBox
在应用程序上都是局部变量,没有什么是静态的。
我们通过在消息框上添加抽象层解决了类似的问题。我们有一个具有 'display' 消息框和对话框功能的全局对象,如下所示:
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);
}
};
当我们want/need执行一个消息框时,我们会'exec'它通过:
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
或任何将在您的纠缠实现中弹出的对话框。
也可以测试 widgets\dialogs 的级联,只是在那种情况下你需要某种仿函数数组。但我建议避免这种情况并重组实现,这样就可以分别测试每个组件。