使用PySide构建的应用程序如何进行单元和功能测试?

26

我正在构建一个基于PySide 1.1.0的应用程序,并一直在寻找好的示例来进行单元测试和功能测试。我希望能够对UI进行功能测试(模拟点击、按键等),对改变UI布局的UI slots进行单元测试(可能使用部分mocked sender和receiver),以及对涉及小部件的代码进行单元测试,但不需要渲染任何窗口。

例如,当将项目添加到提供数据给QTreeView的模型(QAbstractItemModel派生对象)时,我会动态创建菜单栏中一个菜单的子菜单。模型和子菜单必须保持同步,因此我希望能编写一个单元测试,向管理模型和子菜单的控制器提交数据,并断言模型和子菜单都已正确更新。

如果可以避免,我不想在我的测试代码中设置QApplication。我也不想在仅关心验证小部件中的数据结构而不是其可视化时显示任何窗口。

http://www.pyside.org或我的Google搜索中,我找不到合适的内容。是否有人有经验或知道我应该查看哪些好的示例代码?


我也非常关注这个问题的解决方案,因为我面临着完全相同的问题。 - Chris
1
你看过这个网站吗:http://johnnado.com/pyqt-qtest-example/ 它是关于PyQt的,但基本上是一样的。 - eric
@eric 那个链接已经失效了。有其他的链接吗? - A. Hendry
https://web.archive.org/web/20160303001130/johnnado.com/pyqt-qtest-example - eric
2个回答

42

我现在正在尝试对pyside代码进行单元测试,并得出结论:将python的unittest模块与qt的QTest模块相结合效果不错。

您需要实例化一个QApplication对象,但不需要运行其exec_方法,因为您不需要运行事件循环。

下面是我测试对话框中的QCheckBox是否按预期工作的示例:

class Test_PwsAddEntryDialog(TestCase):
    """Tests the class PwsAddEntryDialog."""

    def test_password_strength_checking_works(self):
        """Tests if password strength checking works, if the corresponding check
        box is checked.
        """
        d = PwsAddEntryDialog()
        # test default of internal flag
        self.assertFalse(d.testPasswordStrength)
        # type something
        QTest.keyClicks(d.editSecret, "weak", 0, 10)
        # make sure that entered text is not treated as a password
        self.assertEqual(d.labelPasswordStrength.text(), "")
        # click 'is password' checkbox
        QTest.mouseClick(d.checkIsPassword, Qt.LeftButton)
        # test internal flag changed
        self.assertTrue(d.testPasswordStrength)
        # test that label now contains a warning
        self.assertTrue(d.labelPasswordStrength.text().find("too short") > 0)
        # click checkbox again
        QTest.mouseClick(d.checkIsPassword, Qt.LeftButton)
        # check that internal flag once again changed
        self.assertFalse(d.testPasswordStrength)
        # make sure warning disappeared again
        self.assertEqual(d.labelPasswordStrength.text(), "")

这完全可以在屏幕外运行,涉及单击小部件并在 QLineEdit 中输入文本。

以下是我如何测试一个(相当简单的)QAbstractListModel

class Test_SectionListModel(TestCase):
    """Tests the class SectionListModel."""

    def test_model_works_as_expected(self):
        """Tests if the expected rows are generated from a sample pws file
        content.
        """
        model = SectionListModel(SAMPLE_PASSWORDS_DICT)
        l = len(SAMPLE_PASSWORDS_DICT)
        self.assertEqual(model.rowCount(None), l)
        i = 0
        for section in SAMPLE_PASSWORDS_DICT.iterkeys():
            self.assertEqual(model.data(model.index(i)), section)
            i += 1

我希望这能有所帮助。


4
在我的情况下,我遇到了一个错误“QPixmap:必须在QPaintDevice之前构建一个QApplication”。
如果您需要在测试中使用QApplication实例(例如使用QPixmap),这里有一种方法。只需创建一个单例,以确保您拥有一个且仅有一个QApplication实例。
这是PySide源代码中测试的助手中隐藏的功能。
import unittest

from PySide.QtGui import QApplication
_instance = None

class UsesQApplication(unittest.TestCase):
    '''Helper class to provide QApplication instances'''

    qapplication = True

    def setUp(self):
        '''Creates the QApplication instance'''

        # Simple way of making instance a singleton
        super(UsesQApplication, self).setUp()
        global _instance
        if _instance is None:
            _instance = QApplication([])

        self.app = _instance

    def tearDown(self):
        '''Deletes the reference owned by self'''
        del self.app
        super(UsesQApplication, self).tearDown()

然后子类化UsesQApplication

from PySide import QtGui

class Test(UsesQApplication):

    def setUp(self):
        #If you override setup, tearDown, make sure
        #to have a super call
        super(TestFilterListItem, self).setUp()

    def tearDown(self):
        super(TestFilterListItem, self).tearDown()

    def testName(self):
        pix = QtGui.QPixmap(20,20)
        self.assertTrue(True)

希望这能帮到您。

8
我在每个使用QtGui的测试模块开头只需执行 if QtGui.qApp == None: QtGui.QApplication([]) 就可以了。 - strubbly
对于PySide2及更高版本,“QApplication对象可通过instance()函数访问,该函数返回等效于全局qApp指针的指针。” 因此,if not QtWidgets.QApplication.instance(): APP = QtWidgets.QApplication([]) https://doc.qt.io/qtforpython-5/PySide2/QtWidgets/QApplication.html#detailed-description - Lorem Ipsum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接