带有 pytest-qt 的 travis 在没有退出或创建 QmessageBox 的情况下失败
travis with pytest-qt fails without exiting nor creating QmessageBox
上下文
我尝试为使用 Qt5
到 python3
编写的图形用户界面 (GUI) 创建集成测试(因此使用 pyqt5
)。
我使用 pytest
和插件 pytest-qt
来测试 GUI。
我测试的 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 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处理图形windows):
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 &
有问题,但我不知道如何解决。
欢迎任何提示或帮助!
在 中,我根据经验选择了 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
上下文
我尝试为使用 Qt5
到 python3
编写的图形用户界面 (GUI) 创建集成测试(因此使用 pyqt5
)。
我使用 pytest
和插件 pytest-qt
来测试 GUI。
我测试的 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 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处理图形windows):
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 &
有问题,但我不知道如何解决。
欢迎任何提示或帮助!
在
#!/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