我正在尝试在 PyQt5 中实现长时间运行的任务。我的灵感是这个页面。
使用该页面,我已经设法让事情在预测试阶段运行无错误(到目前为止)。现在我想使用测试来构建它。
在之前的一些测试中,我设法QFileDialog
通过调用emit
并查看连接的插槽是否触发来测试信号(因为它会发生来自类的预先存在的信号)。
但是,当我尝试根据 Maya Posch 的公式为“任务管理器”类设计测试时,我失败了。这是一个 MRE:(取消注释sys.exit
以作为应用程序运行的行,或者运行pytest
- 必须安装 NB pytest-qt):
import sys, time
from unittest import mock
from PyQt5 import QtWidgets, QtCore, QtGui
class LongRunningTask(QtCore.QObject):
finished_signal = QtCore.pyqtSignal()
progress_signal = QtCore.pyqtSignal(int)
def run(self):
print(f'this {self} runs...')
time.sleep(1)
self.progress_signal.emit(50)
time.sleep(1)
print('...run ends')
self.progress_signal.emit(100)
self.finished_signal.emit()
def report_task_progress(self, value):
print(f'new progress value: {value}')
class TaskManager():
def __init__(self, task_class):
assert issubclass(task_class, LongRunningTask)
self.thread = QtCore.QThread()
self.task = task_class()
self.task.moveToThread(self.thread)
self.thread.started.connect(self.task.run)
self.task.progress_signal.connect(self.task.report_task_progress)
self.task.finished_signal.connect(self.task.deleteLater)
self.task.finished_signal.connect(self.thread.quit)
self.thread.finished.connect(self.thread.deleteLater)
self.thread.start() # can be commented out - see below
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.show()
# NB horrible errors happen if you make this a local variable
self.tm = TaskManager(LongRunningTask)
app = QtWidgets.QApplication([])
main_window = MainWindow()
# uncomment to run as app:
# sys.exit(app.exec())
def test_constructor_should_set_started_connect(request, qtbot):
print(f'\n>>>>>> test: {request.node.nodeid}')
tm = TaskManager(LongRunningTask)
with mock.patch.object(tm.task, 'run') as mock_run:
# NB as stated in the docs, user code can't trigger the "started" signal directly
# NB this fails even if you don't start the thread in the app code (i.e. comment out self.thread.start())
tm.thread.start()
def check_called():
mock_run.assert_called_once()
qtbot.waitUntil(check_called, timeout=1000)
def test_constructor_should_set_progress_connect(request, qtbot):
print(f'\n>>>>>> test: {request.node.nodeid}')
tm = TaskManager(LongRunningTask)
with mock.patch.object(tm.task, 'report_task_progress') as mock_report:
tm.task.progress_signal.emit(33)
def check_called():
mock_report.assert_called_once()
qtbot.waitUntil(check_called, timeout=1000)
之后
...发现qtbot.waitSignal
,并取得了一些成功,并带有一些信号...所以我稍微修改了一下:
TaskManager
现在给出了两种新方法,目的是查看我是否可以检查是否正确运行QtCore.QTimer.singleShot
:
def run_something_async(self):
def call_async():
self.delayed_method()
QtCore.QTimer.singleShot(0, call_async)
def delayed_method(self):
print('... delayed')
...不幸的是,在合并后waitSignal
,以下所有测试都失败了,各不相同,如下所述:
def test_constructor_should_set_started_connect(request, qtbot):
print(f'\n>>>>>> test: {request.node.nodeid}')
tm = TaskManager(LongRunningTask)
with mock.patch.object(tm.task, 'run') as mock_run:
with qtbot.waitSignal(tm.thread.start, timeout=1000):
# this fails with 'builtin_function_or_method' object has no attribute 'connect'
# NB as stated in the docs, user code can't trigger the "started" signal directly
tm.thread.start()
def check_called():
mock_run.assert_called_once()
qtbot.waitUntil(check_called, timeout=1000)
def test_constructor_should_set_progress_connect(request, qtbot):
print(f'\n>>>>>> test: {request.node.nodeid}')
tm = TaskManager(LongRunningTask)
with mock.patch.object(tm.task, 'report_task_progress') as mock_report:
with qtbot.waitSignal(tm.task.progress_signal, timeout=1000):
tm.task.progress_signal.emit(33)
def check_called():
mock_report.assert_called_once()
qtbot.waitUntil(check_called, timeout=1000)
# fails with "AssertionError: Expected 'report_task_progress' to have been called once. Called 0 times."
def test_single_shot_fires(request, qtbot):
print(f'\n>>>>>> test: {request.node.nodeid}')
tm = TaskManager(LongRunningTask)
with mock.patch.object(tm, 'delayed_method') as mock_delayed:
with qtbot.waitSignal(QtCore.QTimer.timeout, timeout=1000):
# this fails with "AttributeError: 'PyQt5.QtCore.pyqtSignal' object has no attribute 'connect'"
tm.run_something_async()
def check_called():
mock_delayed.assert_called_once()
qtbot.waitUntil(check_called, timeout=1000)