点击Qtest中多个QMessageBox的按钮



我正在为我的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或任何将在纠缠实现中弹出的对话框。此外,还可以测试窗口小部件\对话框的级联,只有在这种情况下,您才需要某种函数数组。但我建议避免这种情况,并重组实施,以便可以单独测试每个组件。

相关内容

  • 没有找到相关文章

最新更新