如何检查对信号的响应
How to check response to a signal
在 QFileDialog
的子类中有一个方法,on_dir_entered
,应该在 QFileDialog
的信号 directoryEntered
触发时调用,因此:
self.directoryEntered.connect(self.on_dir_entered)
问题是信号需要很长时间才能生效。最初我的灵感来自著名的 PyQt5 专家 eyllanesc 。通过独立测试,这种使用 QTimer.singleShot()
的技术可以工作,尽管我从一开始就对它有模糊的怀疑。事实上,在我的机器上我得到了这种东西的“测试泄漏”,特别是当有不止一种这样的测试方法时:奇怪的错误显然发生在测试本身之外:
TEARDOWN ERROR: Exceptions caught in Qt event loop:
...所以我回到了 pytest-qt 文档,发现有多种方法可用 wait...
似乎可以解决信号或其他事件花费不可忽略的时间的问题有作用。所以我做了几次尝试来测试信号 directoryEntered
:
def test_directoryEntered_triggers_on_dir_entered(request, qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = save_project_dialog_class.SaveProjectDialog(project)
with mock.patch.object(fd, 'on_dir_entered') as mock_entered:
fd.directoryEntered.emit('dummy')
qtbot.waitSignal(fd.directoryEntered, timeout=1000)
mock_entered.assert_called_once()
然后
def test_directoryEntered_triggers_on_dir_entered(qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = SaveProjectDialog(project)
qtbot.waitSignal(fd.directoryEntered, timeout=1000)
with mock.patch.object(fd, 'on_dir_entered') as mock_entered:
fd.directoryEntered.emit('dummy')
mock_entered.assert_called_once()
然后
def test_directoryEntered_triggers_on_dir_entered(qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = SaveProjectDialog(project)
with mock.patch.object(fd, 'on_dir_entered') as mock_entered:
fd.directoryEntered.emit('dummy')
qtbot.wait(1000)
mock_entered.assert_called_once()
全部失败:该方法被调用0次。
我也试过:
def test_directoryEntered_triggers_on_dir_entered(qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = SaveProjectDialog(project)
with mock.patch.object(fd, 'on_dir_entered') as mock_entered:
fd.directoryEntered.emit('dummy')
def check_called():
mock_entered.assert_called_once()
qtbot.waitUntil(check_called)
...这次超时(默认 5000 毫秒)。我已经仔细检查并三次检查了在此信号上设置 connect
的代码是否已执行。
稍后
通过在被调用的插槽 (on_dir_entered
) 中放置一个 print
语句,我现在明白了问题所在:尽管有 with mock.patch...
行,但该方法没有被模拟!
以我对模拟等的了解程度较低。我倾向于假设这是因为使用带有 emit()
的信号来触发事件:我想不出另一种解释.
注意此信号由 QFileDialog
中的一个或两个事件“自然地”触发(例如单击“转到父目录”QToolButton
)。也许你必须那样做......所以我试过这个:
def test_directoryEntered_triggers_on_dir_entered(request, qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = save_project_dialog_class.SaveProjectDialog(project)
to_parent_button = fd.findChild(QtWidgets.QToolButton, 'toParentButton')
print(f'qtbot {qtbot} type(qtbot) {type(qtbot)}')
with mock.patch.object(SaveProjectDialog, 'on_dir_entered') as mock_entered:
qtbot.mouseClick(to_parent_button, QtCore.Qt.LeftButton)
def check_called():
mock_entered.assert_called_once()
qtbot.waitUntil(check_called, timeout=1000)
超时。 0 个电话。我再次能够确定正在调用真正的方法,并且补丁不起作用。
我做错了什么,有没有办法用 pytest-qt 的东西来测试它?
我想我可能已经找到了答案。 pytest-qt的高手不妨评论一下。
我想我已经解决了(这可能非常明显)上面使用 qtbot.waitUntil()
的尝试中的问题是,一旦执行离开上下文管理器块,我设置的补丁就不再应用,确实,对该方法的调用被延迟了,这就是重点。
因此,整体测试方法装饰器补丁是一种方法(或者在设置补丁上下文管理器后保留缩进...)。我发现以下两项都通过了,而且都不是误报(即,如果我注释掉设置 connect
的应用程序代码行,它们就会失败):
@mock.patch('SaveProjectDialog.on_dir_entered')
def test_directoryEntered_signal_triggers_on_dir_entered(mock_entered, request, qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = SaveProjectDialog(project)
fd.directoryEntered.emit('dummy')
def check_called():
mock_entered.assert_called_once()
qtbot.waitUntil(check_called, timeout=1000)
@mock.patch('SaveProjectDialog.on_dir_entered')
def test_on_click_toParentButton_triggers_on_dir_entered(mock_entered, request, qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = SaveProjectDialog(project)
to_parent_button = fd.findChild(QtWidgets.QToolButton, 'toParentButton')
qtbot.mouseClick(to_parent_button, QtCore.Qt.LeftButton)
def check_called():
mock_entered.assert_called_once()
qtbot.waitUntil(check_called, timeout=1000)
更新
事实证明,使用qtbot.waitUntil
可能是不必要的,使用QCoreApplication.processEvents()
通常可以通过这样的测试。因此上面的第二个测试将被重写:
@mock.patch('SaveProjectDialog.on_dir_entered')
def test_on_click_toParentButton_triggers_on_dir_entered(mock_entered, request, qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = SaveProjectDialog(project)
to_parent_button = fd.findChild(QtWidgets.QToolButton, 'toParentButton')
qtbot.mouseClick(to_parent_button, QtCore.Qt.LeftButton)
QtCore.QCoreApplication.processEvents()
mock_entered.assert_called_once()
在 QFileDialog
的子类中有一个方法,on_dir_entered
,应该在 QFileDialog
的信号 directoryEntered
触发时调用,因此:
self.directoryEntered.connect(self.on_dir_entered)
问题是信号需要很长时间才能生效。最初我的灵感来自著名的 PyQt5 专家 eyllanesc QTimer.singleShot()
的技术可以工作,尽管我从一开始就对它有模糊的怀疑。事实上,在我的机器上我得到了这种东西的“测试泄漏”,特别是当有不止一种这样的测试方法时:奇怪的错误显然发生在测试本身之外:
TEARDOWN ERROR: Exceptions caught in Qt event loop:
...所以我回到了 pytest-qt 文档,发现有多种方法可用 wait...
似乎可以解决信号或其他事件花费不可忽略的时间的问题有作用。所以我做了几次尝试来测试信号 directoryEntered
:
def test_directoryEntered_triggers_on_dir_entered(request, qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = save_project_dialog_class.SaveProjectDialog(project)
with mock.patch.object(fd, 'on_dir_entered') as mock_entered:
fd.directoryEntered.emit('dummy')
qtbot.waitSignal(fd.directoryEntered, timeout=1000)
mock_entered.assert_called_once()
然后
def test_directoryEntered_triggers_on_dir_entered(qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = SaveProjectDialog(project)
qtbot.waitSignal(fd.directoryEntered, timeout=1000)
with mock.patch.object(fd, 'on_dir_entered') as mock_entered:
fd.directoryEntered.emit('dummy')
mock_entered.assert_called_once()
然后
def test_directoryEntered_triggers_on_dir_entered(qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = SaveProjectDialog(project)
with mock.patch.object(fd, 'on_dir_entered') as mock_entered:
fd.directoryEntered.emit('dummy')
qtbot.wait(1000)
mock_entered.assert_called_once()
全部失败:该方法被调用0次。
我也试过:
def test_directoryEntered_triggers_on_dir_entered(qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = SaveProjectDialog(project)
with mock.patch.object(fd, 'on_dir_entered') as mock_entered:
fd.directoryEntered.emit('dummy')
def check_called():
mock_entered.assert_called_once()
qtbot.waitUntil(check_called)
...这次超时(默认 5000 毫秒)。我已经仔细检查并三次检查了在此信号上设置 connect
的代码是否已执行。
稍后
通过在被调用的插槽 (on_dir_entered
) 中放置一个 print
语句,我现在明白了问题所在:尽管有 with mock.patch...
行,但该方法没有被模拟!
以我对模拟等的了解程度较低。我倾向于假设这是因为使用带有 emit()
的信号来触发事件:我想不出另一种解释.
注意此信号由 QFileDialog
中的一个或两个事件“自然地”触发(例如单击“转到父目录”QToolButton
)。也许你必须那样做......所以我试过这个:
def test_directoryEntered_triggers_on_dir_entered(request, qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = save_project_dialog_class.SaveProjectDialog(project)
to_parent_button = fd.findChild(QtWidgets.QToolButton, 'toParentButton')
print(f'qtbot {qtbot} type(qtbot) {type(qtbot)}')
with mock.patch.object(SaveProjectDialog, 'on_dir_entered') as mock_entered:
qtbot.mouseClick(to_parent_button, QtCore.Qt.LeftButton)
def check_called():
mock_entered.assert_called_once()
qtbot.waitUntil(check_called, timeout=1000)
超时。 0 个电话。我再次能够确定正在调用真正的方法,并且补丁不起作用。
我做错了什么,有没有办法用 pytest-qt 的东西来测试它?
我想我可能已经找到了答案。 pytest-qt的高手不妨评论一下。
我想我已经解决了(这可能非常明显)上面使用 qtbot.waitUntil()
的尝试中的问题是,一旦执行离开上下文管理器块,我设置的补丁就不再应用,确实,对该方法的调用被延迟了,这就是重点。
因此,整体测试方法装饰器补丁是一种方法(或者在设置补丁上下文管理器后保留缩进...)。我发现以下两项都通过了,而且都不是误报(即,如果我注释掉设置 connect
的应用程序代码行,它们就会失败):
@mock.patch('SaveProjectDialog.on_dir_entered')
def test_directoryEntered_signal_triggers_on_dir_entered(mock_entered, request, qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = SaveProjectDialog(project)
fd.directoryEntered.emit('dummy')
def check_called():
mock_entered.assert_called_once()
qtbot.waitUntil(check_called, timeout=1000)
@mock.patch('SaveProjectDialog.on_dir_entered')
def test_on_click_toParentButton_triggers_on_dir_entered(mock_entered, request, qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = SaveProjectDialog(project)
to_parent_button = fd.findChild(QtWidgets.QToolButton, 'toParentButton')
qtbot.mouseClick(to_parent_button, QtCore.Qt.LeftButton)
def check_called():
mock_entered.assert_called_once()
qtbot.waitUntil(check_called, timeout=1000)
更新
事实证明,使用qtbot.waitUntil
可能是不必要的,使用QCoreApplication.processEvents()
通常可以通过这样的测试。因此上面的第二个测试将被重写:
@mock.patch('SaveProjectDialog.on_dir_entered')
def test_on_click_toParentButton_triggers_on_dir_entered(mock_entered, request, qtbot, tmpdir):
project = mock.Mock()
project.main_window = QtWidgets.QWidget()
project.home_dir_path = pathlib.Path(str(tmpdir))
fd = SaveProjectDialog(project)
to_parent_button = fd.findChild(QtWidgets.QToolButton, 'toParentButton')
qtbot.mouseClick(to_parent_button, QtCore.Qt.LeftButton)
QtCore.QCoreApplication.processEvents()
mock_entered.assert_called_once()