1

我正在为我的 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);

从这里开始,我必须要求确认我要删除的每个文件,但我无法执行任何其他操作,直到我自己用鼠标单击或使用我试图找到的任何解决方案。QMessageBox我使用 qDebug 观察到,在单击所有这些(可能是一个或多个)之前,它不会进一步执行 mouseClick 功能。

所有这些QMessageBox都是应用程序上的局部变量,没有什么是静态的。

4

2 回答 2

1

我们通过在消息框上添加抽象层解决了类似的问题。我们有一个全局对象,它具有“显示”消息框和对话框的功能,如下所示:

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);

我希望这个小例子可以帮助您了解我们是如何为我们解决这个问题的,也许它也会对您有所帮助。

祝你今天过得愉快 :)

于 2021-07-16T12:21:15.457 回答
0

对于这个问题,我有一个解决方案,不需要对生产代码进行任何修改。此流程中的主要问题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 来查找 activate 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;
};

它会在接收到 for 类型QEvent::WindowActivate的事件时调用 lambda QMessageBox。您可以执行与自身相关的任何检查,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或任何将在您的纠缠实现中弹出的对话框。也可以测试小部件\对话框的级联,只有在这种情况下,您才需要某种函子数组。但我建议避免这种情况并重组实现,以便可以分别测试每个组件。

于 2021-07-16T16:59:54.677 回答