簡體   English   中英

在 python 中使用 pytest 測試基於 QML 的應用程序

[英]Testing QML based app with pytest in python

我想測試我的 QML 前端代碼以及我的 Python 后端代碼(使用 PySide2),最好使用 Pytest,並且能夠像pytest-qt插件一樣發送keyClicksMouseClickssignals 我已經簽出pytest-qml ,但測試代碼是通過 QML 編寫的,然后僅通過 pytest 運行,但我想從 python 本身發送事件等,而不是 QML

基本上,具有這樣的 python 代碼:


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

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

和一個簡單的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()
        }
    }
}

我想做(偽代碼)類似的事情

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

pytest-qt插件可以工作,但函數采用QWidget和 QML 處理QQuickItems ,據我所知,它不處理 QWidgets。

甚至有可能,或者我測試我的應用程序插槽等的唯一選擇是通過pytest-qml嗎? 也許這是最簡單的方法,但也許還有其他選擇:)

編輯:

如果您使用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

然后使用來自已接受答案的代碼 rest 它將起作用,因此顯然它不使用本機對話框非常重要。

如果將來有其他人知道如何使用本機對話框並使用QtQuick.Dialogs 1.3作為最初提出的問題,那就太好了:)。 但這對整體測試來說還是不錯的!

您可以使用相同的 API,因為 pytest-qt 基於 QtTest。 顯然你必須了解應用程序的結構,例如 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

注意:如果 filedialog 使用本機 window,測試可能會失敗,您可以使用 pyinput 之類的工具,但更簡單的選擇是使用 virtualenv。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM