`pytest-qt` 函数 `mouseMove()` 不工作
`pytest-qt` Function `mouseMove()` Not Working
此问题已被问到 here, here, , , and here and there is apparently still a bug regarding it in Qt5, noted here。到目前为止,我没有发现任何问题可以解决我的问题。
我正在尝试测试当我的鼠标悬停在工具栏按钮上时是否会显示正确的状态栏消息。
设置
OS: Windows 10 Professional x64-bit, Build 1909
Python: 3.8.10 x64 位
PyQt:5.15.4
pytest-qt: 4.0.2
IDE: VSCode 1.59.0
项目目录
gui/
├───gui/
│ │ main.py
│ │ __init__.py
│ │
│ ├───controller/
│ │ controller.py
│ │ __init__.py
│ │
│ ├───model/
│ │ model.py
│ │ __init__.py
│ │
│ └───view/
│ view.py
│ __init__.py
├───resources/
│ │ __init__.py
│ │
│ └───icons
│ │ main.ico
│ │ __init__.py
│ │
│ └───toolbar
│ new.png
│ __init__.py
└───tests/
│ conftest.py
│ __init__.py
│
└───unit_tests
test_view.py
__init__.py
代码
gui/main.py
:
from PyQt5.QtWidgets import QApplication
from gui.controller.controller import Controller
from gui.model.model import Model
from gui.view.view import View
class MainApp:
def __init__(self) -> None:
self.controller = Controller()
self.model = self.controller.model
self.view = self.controller.view
def show(self) -> None:
self.view.showMaximized()
if __name__ == "__main__":
app = QApplication([])
root = MainApp()
root.show()
app.exec_()
gui/view.py
:
from typing import Any
from PyQt5.QtCore import QSize
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QAction, QFrame, QGridLayout, QStatusBar, QToolBar, QWidget
from pyvistaqt import MainWindow
from resources.icons import toolbar
class View(MainWindow):
def __init__(
self, controller, parent: QWidget = None, *args: Any, **kwargs: Any
) -> None:
super().__init__(parent, *args, **kwargs)
self.controller = controller
# Set the window name
self.setWindowTitle("GUI Demo")
# Create the container frame
self.container = QFrame()
# Create the layout
self.layout = QGridLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
# Set the layout
self.container.setLayout(self.layout)
self.setCentralWidget(self.container)
# Create and position widgets
self._create_actions()
self._create_menubar()
self._create_toolbar()
self._create_statusbar()
def _create_actions(self):
self.new_icon = QIcon(toolbar.NEW_ICO)
self.new_action = QAction(self.new_icon, "&New Project...", self)
self.new_action.setStatusTip("Create a new project...")
def _create_menubar(self):
self.menubar = self.menuBar()
self.file_menu = self.menubar.addMenu("&File")
self.file_menu.addAction(self.new_action)
def _create_toolbar(self):
self.toolbar = QToolBar("Main Toolbar")
self.toolbar.setIconSize(QSize(16, 16))
self.addToolBar(self.toolbar)
self.toolbar.addAction(self.new_action)
def _create_statusbar(self):
self.statusbar = QStatusBar(self)
self.setStatusBar(self.statusbar)
gui/model.py
:
from typing import Any
class Model(object):
def __init__(self, controller, *args: Any, **kwargs: Any):
self.controller = controller
gui/controller.py
:
from typing import Any
from gui.model.model import Model
from gui.view.view import View
class Controller(object):
def __init__(self, *args: Any, **kwargs: Any):
self.model = Model(controller=self, *args, **kwargs)
self.view = View(controller=self, *args, **kwargs)
resources/icons/toolbar/__init__.py
:
import importlib.resources as rsrc
from resources.icons import toolbar
with rsrc.path(toolbar, "__init__.py") as path:
NEW_ICO = str((path.parent / "new.png").resolve())
test/conftest.py
:
from typing import Any, Callable, Generator, List, Sequence, Union
import pytest
import pytestqt
from pytestqt.qtbot import QtBot
from gui.main import MainApp
from PyQt5 import QtCore
pytest_plugins: Union[str, Sequence[str]] = ["pytestqt.qtbot",]
"""A ``pytest`` global variable that registers plugins for use in testing."""
@pytest.fixture(autouse=True)
def clear_settings() -> Generator[None, None, None]:
yield
QtCore.QSettings().clear()
@pytest.fixture
def app(qtbot: QtBot) -> Generator[MainApp, None, None]:
# Setup
root = MainApp()
root.show()
qtbot.addWidget(root.view)
# Run
yield root
# Teardown - None
test/unit_tests/test_view.py
:
import time
from PyQt5 import QtCore, QtWidgets
import pytest
from pytestqt import qt_compat
from pytestqt.qt_compat import qt_api
from pytestqt.qtbot import QtBot
from gui.main import MainApp
def test_toolbar_newbutton_hover(app: MainApp, qapp: QtBot, qtbot: QtBot):
# Arrange
new_button = app.view.toolbar.widgetForAction(app.view.new_action)
new_button.setMouseTracking(True)
qtbot.addWidget(new_button)
# Act
qtbot.mouseMove(new_button)
qapp.processEvents()
time.sleep(5) # See if statusbar message appears
# Assert
assert app.view.statusbar.currentMessage() == "Create a new project..."
问题:
状态栏消息永远不会更新,鼠标只会偶尔移动到工具栏按钮上。我不知道如何让这个测试通过。
您的代码有 2 个问题:
- 您不应使用 time.sleep,因为它会阻止 GUI。
- 您已将鼠标放在按钮的中央但没有移动它。
def test_toolbar_newbutton_hover(app: MainApp, qapp: QtBot, qtbot: QtBot):
new_button = app.view.toolbar.widgetForAction(app.view.new_action)
new_button.setMouseTracking(True)
qtbot.addWidget(new_button)
# Act
qtbot.wait(1000)
qtbot.mouseMove(new_button)
qtbot.wait(1000)
qtbot.mouseMove(new_button, new_button.rect().bottomRight() - QtCore.QPoint(10, 10))
# Assert
assert app.view.statusbar.currentMessage() == "Create a new project..."
此问题已被问到 here, here,
我正在尝试测试当我的鼠标悬停在工具栏按钮上时是否会显示正确的状态栏消息。
设置
OS: Windows 10 Professional x64-bit, Build 1909
Python: 3.8.10 x64 位
PyQt:5.15.4
pytest-qt: 4.0.2
IDE: VSCode 1.59.0
项目目录
gui/
├───gui/
│ │ main.py
│ │ __init__.py
│ │
│ ├───controller/
│ │ controller.py
│ │ __init__.py
│ │
│ ├───model/
│ │ model.py
│ │ __init__.py
│ │
│ └───view/
│ view.py
│ __init__.py
├───resources/
│ │ __init__.py
│ │
│ └───icons
│ │ main.ico
│ │ __init__.py
│ │
│ └───toolbar
│ new.png
│ __init__.py
└───tests/
│ conftest.py
│ __init__.py
│
└───unit_tests
test_view.py
__init__.py
代码
gui/main.py
:
from PyQt5.QtWidgets import QApplication
from gui.controller.controller import Controller
from gui.model.model import Model
from gui.view.view import View
class MainApp:
def __init__(self) -> None:
self.controller = Controller()
self.model = self.controller.model
self.view = self.controller.view
def show(self) -> None:
self.view.showMaximized()
if __name__ == "__main__":
app = QApplication([])
root = MainApp()
root.show()
app.exec_()
gui/view.py
:
from typing import Any
from PyQt5.QtCore import QSize
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QAction, QFrame, QGridLayout, QStatusBar, QToolBar, QWidget
from pyvistaqt import MainWindow
from resources.icons import toolbar
class View(MainWindow):
def __init__(
self, controller, parent: QWidget = None, *args: Any, **kwargs: Any
) -> None:
super().__init__(parent, *args, **kwargs)
self.controller = controller
# Set the window name
self.setWindowTitle("GUI Demo")
# Create the container frame
self.container = QFrame()
# Create the layout
self.layout = QGridLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
# Set the layout
self.container.setLayout(self.layout)
self.setCentralWidget(self.container)
# Create and position widgets
self._create_actions()
self._create_menubar()
self._create_toolbar()
self._create_statusbar()
def _create_actions(self):
self.new_icon = QIcon(toolbar.NEW_ICO)
self.new_action = QAction(self.new_icon, "&New Project...", self)
self.new_action.setStatusTip("Create a new project...")
def _create_menubar(self):
self.menubar = self.menuBar()
self.file_menu = self.menubar.addMenu("&File")
self.file_menu.addAction(self.new_action)
def _create_toolbar(self):
self.toolbar = QToolBar("Main Toolbar")
self.toolbar.setIconSize(QSize(16, 16))
self.addToolBar(self.toolbar)
self.toolbar.addAction(self.new_action)
def _create_statusbar(self):
self.statusbar = QStatusBar(self)
self.setStatusBar(self.statusbar)
gui/model.py
:
from typing import Any
class Model(object):
def __init__(self, controller, *args: Any, **kwargs: Any):
self.controller = controller
gui/controller.py
:
from typing import Any
from gui.model.model import Model
from gui.view.view import View
class Controller(object):
def __init__(self, *args: Any, **kwargs: Any):
self.model = Model(controller=self, *args, **kwargs)
self.view = View(controller=self, *args, **kwargs)
resources/icons/toolbar/__init__.py
:
import importlib.resources as rsrc
from resources.icons import toolbar
with rsrc.path(toolbar, "__init__.py") as path:
NEW_ICO = str((path.parent / "new.png").resolve())
test/conftest.py
:
from typing import Any, Callable, Generator, List, Sequence, Union
import pytest
import pytestqt
from pytestqt.qtbot import QtBot
from gui.main import MainApp
from PyQt5 import QtCore
pytest_plugins: Union[str, Sequence[str]] = ["pytestqt.qtbot",]
"""A ``pytest`` global variable that registers plugins for use in testing."""
@pytest.fixture(autouse=True)
def clear_settings() -> Generator[None, None, None]:
yield
QtCore.QSettings().clear()
@pytest.fixture
def app(qtbot: QtBot) -> Generator[MainApp, None, None]:
# Setup
root = MainApp()
root.show()
qtbot.addWidget(root.view)
# Run
yield root
# Teardown - None
test/unit_tests/test_view.py
:
import time
from PyQt5 import QtCore, QtWidgets
import pytest
from pytestqt import qt_compat
from pytestqt.qt_compat import qt_api
from pytestqt.qtbot import QtBot
from gui.main import MainApp
def test_toolbar_newbutton_hover(app: MainApp, qapp: QtBot, qtbot: QtBot):
# Arrange
new_button = app.view.toolbar.widgetForAction(app.view.new_action)
new_button.setMouseTracking(True)
qtbot.addWidget(new_button)
# Act
qtbot.mouseMove(new_button)
qapp.processEvents()
time.sleep(5) # See if statusbar message appears
# Assert
assert app.view.statusbar.currentMessage() == "Create a new project..."
问题:
状态栏消息永远不会更新,鼠标只会偶尔移动到工具栏按钮上。我不知道如何让这个测试通过。
您的代码有 2 个问题:
- 您不应使用 time.sleep,因为它会阻止 GUI。
- 您已将鼠标放在按钮的中央但没有移动它。
def test_toolbar_newbutton_hover(app: MainApp, qapp: QtBot, qtbot: QtBot):
new_button = app.view.toolbar.widgetForAction(app.view.new_action)
new_button.setMouseTracking(True)
qtbot.addWidget(new_button)
# Act
qtbot.wait(1000)
qtbot.mouseMove(new_button)
qtbot.wait(1000)
qtbot.mouseMove(new_button, new_button.rect().bottomRight() - QtCore.QPoint(10, 10))
# Assert
assert app.view.statusbar.currentMessage() == "Create a new project..."