我试图为用Qt5
至python3
(因此使用pyqt5
)编写的图形用户界面(GUI)创建集成测试。我将pytest
与pytest-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'
我在git存储库中包含以下文件的MWE中重现此错误:
用python GUI.py
编写的GUI:
#!/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 &
存在问题,但我不知道如何解决。
欢迎任何提示或帮助!
在my previous answer中,我根据经验选择了100ms,但是根据资源的不同,时间可能会有所不同,因此我不必放置会失败的时间,而是实现了每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