比我更简洁的方法是按名称查找小部件及其布局位置?

时间:2017-03-27 18:16:33

标签: python python-3.x pyqt pytest pyqt5

我正在为PyQt5 MainWindow GUI编写功能测试。目前我正在检查正确的小部件是否在网格布局的正确坐标中。我已经确定了如何通过其objectName找到一个小部件,并找出了在GUI的(网格)布局中“挖掘”到小部件所需的模块层次结构,但我觉得我必须这么做。我一直在阅读PyQt4 / 5文档并在SO上搜索类似的问题,但没有找到可以简化我的任务的工具。

理想情况下,我希望命令类似于以下伪代码中的命令:

Bob_widget = widget['Bob']  # lookup widget by objectName
assert Bob_widget.layout.coordinates == (0, 0)

伪代码的第一行的功能可以通过将每个小部件名称和类型传递到findChild方法然后制作字典来完成,但我想知道是否有内置的等价物。

伪代码的第二行更像是一种痛苦。如何通过GUI浏览以找到小部件进行了一些研究。对于我的GUI代码:

import sys
from collections import namedtuple

from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QGridLayout,
                             QLabel, QDoubleSpinBox)

gui_userinput = namedtuple('gui_userinput', ['key', 'string', 'value', 'range'])

variable_a = gui_userinput(key='variable_a', string='Variable A', value=165.00,
                           range=(0.00, 10000.00))
variable_b = gui_userinput(key='variable_b', string='Variable B', value=135.00,
                           range=(0.00, 10000.00))

gui_userinput_order = (variable_a, variable_b)


class Gui(QMainWindow):
    def __init__(self, parent=None):

        super(Gui, self).__init__(parent)

        self.setObjectName('toplevel')
        self.setupUi()

    def setupUi(self):

        centralWidget = QWidget()
        centralWidget.setObjectName('centralwidget')
        self.setCentralWidget(centralWidget)

        centralLayout = QGridLayout()
        self.setObjectName('centrallayout')

        for i, widget in enumerate(gui_userinput_order):

            wlabel = QLabel(widget.string)
            wlabel.setObjectName(widget.key + '_label')

            wbox = QDoubleSpinBox()
            wbox.setObjectName(widget.key)
            wbox.setRange(*widget.range)
            wbox.setValue(widget.value)

            centralLayout.addWidget(wlabel, 0, i)
            centralLayout.addWidget(wbox, 1, i)

        centralWidget.setLayout(centralLayout)


if __name__ == '__main__':

    app = QApplication(sys.argv)
    ui = Gui()
    ui.show()
    sys.exit(app.exec_())

从MainWindow获取一个小部件需要一个如下语句:

widget_0_0 = self.ui.centralWidget().layout()\
            .itemAtPosition(0, 0).widget()

“GUI有一个中央小部件,它有一个(网格)布局,在网格坐标(0,0)上有一个小部件容器,用于保存我想要的小部件。”

所以我可以编写下面最小例子的测试代码:

import sys
from PyQt5.QtWidgets import QApplication, QDoubleSpinBox, QLabel

import mini_ui

app = QApplication(sys.argv)


class TestMainGUi:

    def setup(self):
        self.ui = mini_ui.Gui()

    def test_all_widgets_exist(self):
        """The user finds the expected labels and doublespinboxes."""
        for widget in mini_ui.gui_userinput_order:
            found_box = self.ui.findChild(QDoubleSpinBox, widget.key)
            assert found_box.value() == widget.value
            found_label = self.ui.findChild(QLabel, widget.key + '_label')
            assert found_label.text() == widget.string

    def test_label_0_0(self):
        """The user sees a label for 'Variable A' in the top left corner."""
        label_a = self.ui.findChild(QLabel, 'variable_a_label')
        print('Found', label_a, 'named', label_a.objectName(), 'in GUI.')
        widget_0_0 = self.ui.centralWidget().layout()\
            .itemAtPosition(0, 0).widget()
        print('Found widget type', type(widget_0_0), 'at grid 0, 0')
        print('It is named: ', widget_0_0.objectName())

        assert widget_0_0.text() == 'Variable A'
        assert widget_0_0 is label_a

    def test_alternate_widget_acces(self):
        """Just hacking away trying to find a more terse widget reference"""
        label_a = self.ui.findChild(QLabel, 'variable_a_label')

        assert 1 == 1

    def teardown(self):
        pass

并更改第二个测试以迭代匹配组件小部件到网格坐标小部件,但这感觉不对。除了令人费解,如果有任何关于GUI的更改以改变层次结构,测试将无法工作 - 测试代码必须知道GUI的精细结构。

是否有更简洁,更健壮的方法来测试GUI布局的内容?是否有我缺少的内置工具?

感谢任何指导。

1 个答案:

答案 0 :(得分:0)

我有一个部分解决方案,用于按名称查找布局,这意味着测试现在可能不知道确切的GUI结构。

如果在使用centralWidget.setLayout()将其应用于centralWidget之前将名称指定给布局,则布局名称丢失。如果您设置了centralWidget的布局,并且然后centralWidget.layout()指定了名称,则可以按名称找到布局。

所以,修改mini_ui为:

centralWidget.setLayout(centralLayout)
centralWidget.layout().setObjectName('centrallayout')

然后功能测试可以找到布局并将其分配给变量:

def test_alternate_widget_access(self):
    """Just hacking away trying to find a more terse widget reference"""
    main_layout = self.ui.findChild(QGridLayout, 'centrallayout')
    found_widget = main_layout.itemAtPosition(0, 0).widget()