在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 的级联,只是在那种情况下你需要某种仿函数数组。但我建议避免这种情况并重组实现,这样就可以分别测试每个组件。