如何用pytest-qt测试Qt菜单?触发QAction遇AttributeError
Qt菜单Action的pytest-qt测试解决方案
问题场景
基于Qt实现的MainWindow类通过create_menu_bar方法创建了菜单,包含顶级Action(sheet_one)和下拉菜单(sheet_five_menu),但使用pytest-qt模拟点击时多次触发AttributeError,尝试findChild、直接访问属性、调用create_menu_bar后操作等方法均失败。
错误原因分析
findChild查找失败:创建Action时未设置objectName,Qt的findChild默认按对象名称(而非显示文本)查找,因此返回None。- 直接访问属性失败:
sheet_one是create_menu_bar方法内的局部变量,未注册为MainWindow的实例属性。 - 调用
create_menu_bar后操作失败:该方法无返回值(默认返回None),因此menu变量为None,不存在trigger方法。
正确测试方法
方法一:给Action设置objectName,用findChild查找
修改原代码,给需要测试的Action添加唯一objectName:
class MainWindow(QtWidgets.QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.create_menu_bar() def create_menu_bar(self): sheet_one = QtWidgets.QAction("sheet_one", self) sheet_one.setObjectName("action_sheet_one") # 添加对象名称 sheet_one.triggered.connect(lambda: self.load_df(sheet_name="sheet one")) # 下拉菜单的Action同理设置objectName sheet_five_one = QtWidgets.QAction("sheet five one", self) sheet_five_one.setObjectName("action_sheet_five_one") sheet_five_two = QtWidgets.QAction("sheet five two", self) sheet_five_two.setObjectName("action_sheet_five_two") menu = self.menuBar() menu.addAction(sheet_one) sheet_five_menu = menu.addMenu("&sheet five") sheet_five_menu.addAction(sheet_five_one) sheet_five_menu.addAction(sheet_five_two)
测试代码中通过findChild定位并触发Action:
def test_menu_actions(qtbot): window = MainWindow() qtbot.addWidget(window) # 触发顶级菜单的sheet_one sheet_one_action = window.findChild(QtWidgets.QAction, "action_sheet_one") assert sheet_one_action is not None sheet_one_action.trigger() # 可添加断言验证load_df是否被正确调用(如结合unittest.mock) # 触发下拉菜单中的sheet_five_one sheet_five_one_action = window.findChild(QtWidgets.QAction, "action_sheet_five_one") assert sheet_five_one_action is not None sheet_five_one_action.trigger()
方法二:遍历菜单树查找Action(无需修改原代码)
通过递归遍历菜单栏的Action和子菜单,按显示文本定位目标:
def test_menu_actions_by_text(qtbot): window = MainWindow() qtbot.addWidget(window) # 递归查找指定文本的Action def find_action_by_text(menu, target_text): for action in menu.actions(): if action.text() == target_text: return action # 检查是否为子菜单,递归查找 sub_menu = action.menu() if sub_menu: found_action = find_action_by_text(sub_menu, target_text) if found_action: return found_action return None # 触发顶级菜单的sheet_one sheet_one_action = find_action_by_text(window.menuBar(), "sheet_one") assert sheet_one_action is not None sheet_one_action.trigger() # 触发下拉菜单中的sheet_five_one sheet_five_one_action = find_action_by_text(window.menuBar(), "sheet five one") assert sheet_five_one_action is not None sheet_five_one_action.trigger()
方法三:将Action设为实例属性(直接访问)
修改原代码,把需要测试的Action注册为MainWindow的实例属性:
class MainWindow(QtWidgets.QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.create_menu_bar() def create_menu_bar(self): # 将Action设为实例属性 self.sheet_one = QtWidgets.QAction("sheet_one", self) self.sheet_one.triggered.connect(lambda: self.load_df(sheet_name="sheet one")) self.sheet_five_one = QtWidgets.QAction("sheet five one", self) self.sheet_five_two = QtWidgets.QAction("sheet five two", self) menu = self.menuBar() menu.addAction(self.sheet_one) sheet_five_menu = menu.addMenu("&sheet five") sheet_five_menu.addAction(self.sheet_five_one) sheet_five_menu.addAction(self.sheet_five_two)
测试代码直接访问实例属性触发Action:
def test_menu_actions_direct(qtbot): window = MainWindow() qtbot.addWidget(window) window.sheet_one.trigger() window.sheet_five_one.trigger()
内容的提问来源于stack exchange,提问作者Willy
相关产品推荐
相关产品推荐

