【问题标题】:travis with pytest-qt fails without exiting nor creating QmessageBox带有 pytest-qt 的 travis 失败,没有退出也没有创建 QmessageBox
【发布时间】:2020-04-20 02:49:31
【问题描述】:

上下文

我尝试为使用Qt5python3(因此使用pyqt5)编写的图形用户界面(GUI)创建集成测试。 我使用pytestpytest-qt 来测试GUI。

我测试了很大程度上受question启发的GUI,所以pytest -v -s命令运行良好。

由于我的存储库位于Github,我使用Travis-CI 来执行我的集成测试。

错误

但是,当我推动 Github 并启动 Travis 测试时,有时会出现以下错误:

Exceptions caught in Qt event loop:
________________________________________________________________________________
Traceback (most recent call last):
  File "/home/travis/build/XXXX/Test/GUI_test.py", line 29, in handle_dialog
    yes_button = messagebox.button(QtWidgets.QMessageBox.Yes)
AttributeError: 'Example' object has no attribute 'button'

MWE

我使用我的 git 存储库中包含的以下文件在 MWE 中重现此错误:

用python写的GUIGUI.py

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys

from PyQt5 import QtGui, QtCore, QtWidgets

from PyQt5.QtWidgets import *
from PyQt5.QtCore import QCoreApplication, Qt, QObject
from PyQt5.QtGui import QIcon


class Example(QMainWindow):
    def __init__(self, parent = None):
        super().__init__()
        self.initUI(self)

    def initUI(self, MainWindow):
        # centralwidget
        MainWindow.resize(346, 193)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        # The Action to quit
        self.toolb_action_Exit = QAction(QIcon('exit.png'), 'Exit', self)
        self.toolb_action_Exit.setShortcut('Ctrl+Q')
        self.toolb_action_Exit.triggered.connect(self.close)

        # The Button
        self.btn_prt = QtWidgets.QPushButton(self.centralwidget)
        self.btn_prt.setGeometry(QtCore.QRect(120, 20, 89, 25))
        self.btn_prt.clicked.connect(lambda: self.doPrint() )
        self.btn_quit = QtWidgets.QPushButton(self.centralwidget)
        self.btn_quit.setGeometry(QtCore.QRect(220, 20, 89, 25))
        self.btn_quit.clicked.connect(lambda: self.close() )

        # The textEdit
        self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
        self.textEdit.setGeometry(QtCore.QRect(10, 60, 321, 81))

        # Show the frame
        MainWindow.setCentralWidget(self.centralwidget)
        self.show()

    def doPrint(self):
        print('TEST doPrint')

    def closeEvent(self, event):
        # Ask a question before to quit.
        self.replyClosing = QMessageBox.question(self, 'Message',
            "Are you sure to quit?", QMessageBox.Yes |
            QMessageBox.No, QMessageBox.No)

        if self.replyClosing == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()


def main_GUI():
    print('start')
    app = QApplication(sys.argv)
    imageViewer = Example()
    return app, imageViewer


if __name__ == '__main__':
    app, imageViewer =main_GUI()
    rc= app.exec_()
    print('App end is exit code {}'.format(rc))
    sys.exit(rc)

pytest 用于创建单元测试 GUI_test.py 的文件:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os, sys 

from PyQt5 import QtGui, QtCore, QtWidgets, QtTest
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QCoreApplication, Qt, QObject

import pytest
import warnings
from pytestqt.plugin import QtBot, capture_exceptions
import mock

@pytest.fixture(scope="module")
def Viewer(request):
    print("  SETUP GUI")
    GUI= __import__('GUI')
    app, imageViewer = GUI.main_GUI()
    with capture_exceptions() as exceptions:
        qtbotbis = QtBot(app)
        QtTest.QTest.qWait(0.5 *1000)
        yield app, imageViewer, qtbotbis

        ######### EXIT ##########
        app.quitOnLastWindowClosed()
        def handle_dialog():
            messagebox = QtWidgets.QApplication.activeWindow()
            yes_button = messagebox.button(QtWidgets.QMessageBox.Yes)
            qtbotbis.mouseClick(yes_button, QtCore.Qt.LeftButton, delay=1)
        QtCore.QTimer.singleShot(100, handle_dialog)
        qtbotbis.mouseClick(imageViewer.btn_quit, QtCore.Qt.LeftButton, delay=1)
        assert imageViewer.isHidden()

        app.closeAllWindows()
        app.quit()
        app.exit()
        app.closingDown()
        QtTest.QTest.qWait(0.5 *1000)
        with mock.patch.object(QApplication, "exit"):
            app.exit()
            assert QApplication.exit.call_count == 1
            print("[Notice] So a mock.patch is used to count if the signal is emitted.")
        print("  TEARDOWN GUI")


class Test_GUI_CXS() : 

    def test_buttons(self, Viewer, caplog):
        app, mainWindow, qtbot = Viewer

        qtbot.mouseClick( mainWindow.btn_prt, QtCore.Qt.LeftButton )

控制travis作业.travis.yml的文件(可以根据documentation p32处理图形窗口):

language: python
python:
  - "3.7"

sudo: required
dist: bionic

jobs:
  include:
  - stage: test
    name: PyTest-GUI
    before_install:
    - python -m pip install --upgrade pip
    - pip install -r ./requirement.txt
    - sudo apt-get install -y libdbus-1-3 libxkbcommon-x11-0 dzen2
    install:
      - "export DISPLAY=:99.0"
      - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac+extension GLX +render -noreset"
      - sleep 3
    before_script:
      - "herbstluftwm &"
      - sleep 1
    script:
    - pytest -s -v ./GUI_test.py
    addons:
      apt:
        packages:
        - x11-utils
        - libxkbcommon-x11-0
        - herbstluftwm
        - xvfb
    services: xvfb

以及包含所需库的文件requirement.txt

pyqt5
mock
pytest
pytest-qt

其他尝试

我尝试在调试模式下运行 travis 作业。因此,在通过ssh 连接安装所有依赖项后,我尝试运行命令pytest 并得到相同的错误。

但是,如果我这样做 herbstluftwm & 然后 pytest 测试运行良好并且没有出现错误。 因此,我假设在正常的travis作业中herbstluftwm &这个命令有问题,但是不知道怎么解决。

欢迎任何提示或帮助!

【问题讨论】:

    标签: python github pyqt5 travis-ci pytest-qt


    【解决方案1】:

    my previous answer 中,我凭经验选择了 100 毫秒,但根据资源的不同,时间可能会有所不同,这样我就不必设置一个可能失败的时间,并实现一个每隔 T 秒运行一次的函数,直到我找到 QMessageBox。

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    
    from PyQt5 import QtCore, QtWidgets, QtTest
    
    import mock
    
    import pytest
    from pytestqt.plugin import QtBot, capture_exceptions
    
    
    def get_messagebox(t=100, max_attemps=-1):
        messagebox = None
        attempt = 0
        loop = QtCore.QEventLoop()
    
        def on_timeout():
            nonlocal attempt, messagebox
    
            attempt += 1
            active_window = QtWidgets.QApplication.activeWindow()
    
            if isinstance(active_window, QtWidgets.QMessageBox):
                messagebox = active_window
                loop.quit()
            elif max_attemps > 0:
                if attempt > max_attemps:
                    loop.quit()
            else:
                QtCore.QTimer.singleShot(t, on_timeout)
    
        QtCore.QTimer.singleShot(t, on_timeout)
        loop.exec_()
        return messagebox
    
    
    @pytest.fixture(scope="module")
    def Viewer(request):
        print("  SETUP GUI")
        GUI = __import__("GUI")
        app, imageViewer = GUI.main_GUI()
        with capture_exceptions():
            qtbotbis = QtBot(app)
            QtTest.QTest.qWait(0.5 * 1000)
            yield app, imageViewer, qtbotbis
    
            app.quitOnLastWindowClosed()
    
            def handle_dialog():
                messagebox = get_messagebox()
                yes_button = messagebox.button(QtWidgets.QMessageBox.Yes)
                qtbotbis.mouseClick(yes_button, QtCore.Qt.LeftButton, delay=1)
    
            QtCore.QTimer.singleShot(10, handle_dialog)
            qtbotbis.mouseClick(imageViewer.btn_quit, QtCore.Qt.LeftButton, delay=1)
            assert imageViewer.isHidden()
    
            app.closeAllWindows()
            app.quit()
            app.exit()
            app.closingDown()
            QtTest.QTest.qWait(0.5 * 1000)
            with mock.patch.object(QtWidgets.QApplication, "exit"):
                app.exit()
                assert QtWidgets.QApplication.exit.call_count == 1
                print("[Notice] So a mock.patch is used to count if the signal is emitted.")
            print("  TEARDOWN GUI")
    
    
    class Test_GUI_CXS:
        def test_buttons(self, Viewer, caplog):
            app, mainWindow, qtbot = Viewer
    
            qtbot.mouseClick(mainWindow.btn_prt, QtCore.Qt.LeftButton)
    

    另一方面,在 travis 中进行测试只需要以下配置:

    language: python
    python:
      - "3.7"
    
    dist: bionic
    
    jobs:
      include:
        - stage: test
          name: PyTest-GUI
          before_script:
            - python -m pip install --upgrade pip
            - pip install -r ./requirement.txt
          script: 
            - pytest -s -v ./GUI_test.py
          addons:
            apt:
              packages:
                - libxkbcommon-x11-0
          services:
            - xvfb
    

    考虑以上通过测试:

    【讨论】:

      猜你喜欢
      • 2021-04-01
      • 1970-01-01
      • 2021-11-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多