简体   繁体   English

在 python 中使用 pytest 测试基于 QML 的应用程序

[英]Testing QML based app with pytest in python

I would like to test my QML frontend code along with my Python backend code(using PySide2) with Pytest preferably, and be able to send keyClicks , MouseClicks and signals just like pytest-qt plugin does.我想测试我的 QML 前端代码以及我的 Python 后端代码(使用 PySide2),最好使用 Pytest,并且能够像pytest-qt插件一样发送keyClicksMouseClickssignals I have already checked out pytest-qml , but the test code is written via QML, and then only ran via via pytest, but I would like to send events and such from python itself, not QML我已经签出pytest-qml ,但测试代码是通过 QML 编写的,然后仅通过 pytest 运行,但我想从 python 本身发送事件等,而不是 QML

Basically, having the python code as such:基本上,具有这样的 python 代码:


"""
Slots, Signals, context class etc etc...
"""

app = QGuiApplication([])
engine = QQmlApplicationEngine()
engine.load(QUrl.fromLocalFile("main.qml"))
app.exec_()

and a simple main.qml file, as such,和一个简单的main.qml文件,

import QtQuick 2.15
import QtQuick.Layouts 1.15

import QtQuick.Window 2.2
import QtQuick.Controls 2.15


ApplicationWindow {
    id: mywin
    width: Screen.desktopAvailableWidth
    height: Screen.desktopAvailableHeight
    visible: true
    FileDialog {
            id: openDialog
            title: "mydialog"
            onAccepted: {
            }
        }
    Button {
        objectName: "mybtn"
        width: 200
        height: 200
        id: btn
        text: "hello"
        onClicked: {
            openDialog.open()
        }
    }
}

I would like to do (pseudo-code)something like我想做(伪代码)类似的事情

def test_file_open():
    #Grab QQuickItem(btn)
    #Send mouse event to click btn
    #Send string to file dialog
    # assert string sent ==  string selected

The pytest-qt plugin would work, but functions take QWidget and QML deals with QQuickItems , which as far as I know doesnt deal with QWidgets. pytest-qt插件可以工作,但函数采用QWidget和 QML 处理QQuickItems ,据我所知,它不处理 QWidgets。

Is it even possible, or my only option to test my app slots etc is via the pytest-qml ?甚至有可能,或者我测试我的应用程序插槽等的唯一选择是通过pytest-qml吗? Perhaps its the easiest way, but perhaps there are other options:)也许这是最简单的方法,但也许还有其他选择:)

Edit:编辑:

If you use import Qt.labs.platform 1.1 instead of the import QtQuick.Dialogs 1.3 , and force QML to not use native dialog, then just change如果您使用import Qt.labs.platform 1.1而不是import QtQuick.Dialogs 1.3 ,并强制 QML使用本机对话框,则只需更改

    # assert myfiledialog.property("fileUrl").toLocalFile() == filename  # uses QDialog
    assert myfiledialog.property("currentFile").toLocalFile() == filename # using QLabs Dialog

And then using the rest of the code from accepted answer it will work, so apparently its very important that it does not use a native dialog.然后使用来自已接受答案的代码 rest 它将起作用,因此显然它不使用本机对话框非常重要。

If anyone else in the future knows how to make it work with native dialog and using QtQuick.Dialogs 1.3 as the original question presented, it would be nice:).如果将来有其他人知道如何使用本机对话框并使用QtQuick.Dialogs 1.3作为最初提出的问题,那就太好了:)。 But this is still nice to test overall!但这对整体测试来说还是不错的!

You can use the same API since pytest-qt is based on QtTest.您可以使用相同的 API,因为 pytest-qt 基于 QtTest。 Obviously you must understand the structure of the application, for example that the FileDialog is just a QObject that only manages a QWindow that has the dialog, in addition to managing the positions of the items with respect to the windows.显然你必须了解应用程序的结构,例如 FileDialog 只是一个 QObject,它只管理一个具有对话框的 QWindow,此外还管理项目相对于 windows 的位置。

import os
from pathlib import Path

from PySide2.QtCore import QUrl
from PySide2.QtQml import QQmlApplicationEngine

CURRENT_DIR = Path(__file__).resolve().parent


def build_engine():
    engine = QQmlApplicationEngine()
    filename = os.fspath(CURRENT_DIR / "main.qml")
    url = QUrl.fromLocalFile(filename)
    engine.load(url)
    return engine


def main():
    app = QGuiApplication([])
    engine = build_engine()
    app.exec_()


if __name__ == "__main__":
    main()
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Dialogs 1.3
import QtQuick.Layouts 1.15
import QtQuick.Window 2.2

ApplicationWindow {
    id: mywin

    width: Screen.desktopAvailableWidth
    height: Screen.desktopAvailableHeight
    visible: true

    FileDialog {
        id: openDialog

        objectName: "myfiledialog"
        title: "mydialog"
        onAccepted: {
        }
    }

    Button {
        id: btn

        objectName: "mybtn"
        width: 200
        height: 200
        text: "hello"
        onClicked: {
            openDialog.open();
        }
    }

}
import os

from PySide2.QtCore import QCoreApplication, QObject, Qt, QPointF
from PySide2.QtGui import QGuiApplication
from PySide2.QtQuick import QQuickItem
from PySide2.QtWidgets import QApplication

import pytest

from app import build_engine


@pytest.fixture(scope="session")
def qapp():
    QCoreApplication.setOrganizationName("qapp")
    QCoreApplication.setOrganizationDomain("qapp.com")
    QCoreApplication.setAttribute(Qt.AA_DontUseNativeDialogs)
    yield QApplication([])


def test_app(tmp_path, qtbot):
    engine = build_engine()

    assert QCoreApplication.testAttribute(Qt.AA_DontUseNativeDialogs)
    
    with qtbot.wait_signal(engine.objectCreated, raising=False):
        assert len(engine.rootObjects()) == 1
    root_object = engine.rootObjects()[0]
    root_item = root_object.contentItem()

    mybtn = root_object.findChild(QQuickItem, "mybtn")
    assert mybtn is not None

    center = QPointF(mybtn.width(), mybtn.height()) / 2
    qtbot.mouseClick(
        mybtn.window(),
        Qt.LeftButton,
        pos=root_item.mapFromItem(mybtn, center).toPoint(),
    )
    qtbot.wait(1000)
    qfiledialog = None
    for window in QGuiApplication.topLevelWindows():
        if window is not root_object:
            qfiledialog = window
    assert qfiledialog is not None, QGuiApplication.topLevelWindows()

    file = tmp_path / "foo.txt"
    file.touch()
    filename = os.fspath(file)

    for letter in filename:
        qtbot.keyClick(qfiledialog, letter, delay=100)

    qtbot.wait(1000)

    qtbot.keyClick(qfiledialog, Qt.Key_Return)

    qtbot.wait(1000)

    myfiledialog = root_object.findChild(QObject, "myfiledialog")
    assert myfiledialog is not None

    assert myfiledialog.property("fileUrl").toLocalFile() == filename

Note: The test may fail if the filedialog uses the native window, you could use tools like pyinput but a simpler option is to use virtualenv.注意:如果 filedialog 使用本机 window,测试可能会失败,您可以使用 pyinput 之类的工具,但更简单的选择是使用 virtualenv。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM