Unit and functional testing a PySide-based applica

2019-01-21 10:19发布

问题:

I'm building a PySide 1.1.0-based application, and have been looking for good examples to look at for unit and functional testing my application. I want to be able to do functional testing of the UI (simulating clicks, key presses, etc), unit testing of UI slots that alter the layout of the UI (presumably using a partially-mocked sender and receiver), as well as unit testing of code that involves widgets, but without requiring any windows to be rendered.

As one example, I dynamically create submenus of one menu in the menubar when an item is added to a model (QAbstractItemModel-derived object) that provides data to a QTreeView. The model and submenu must stay in sync, so I want to be able to write a unit test that submits data to the controller that manages the model and submenu, and asserts that both the model and submenu were properly updated.

I would prefer to NOT have to set up a QApplication in my test code if I can avoid it. I also would like to not have to display any windows when I only care about validating data structures in widgets, not their visualization.

I can't find anything of suitable value at http://www.pyside.org or in my Google searches. Does anyone have any experience or know of good sample code that I should look at?

回答1:

I've been playing around a bit now with unit-testing pyside code and came to the conclusion that combining python's unittest module with qt's QTest module works pretty good.

You will have to have a QApplication object instantiated, but you do not need to run its exec_ method, because you don't need the event loop to be running.

Here is an example on how I test if a QCheckBox in a dialog does what it is supposed to do:

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(), "")

This completely works off-screen, involves clicking widgets and typing text in a QLineEdit.

Here is how I test a (rather simple) 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

I hope this helps a littlebit.



回答2:

In my case, I was getting an error 'QPixmap: Must construct a QApplication before a QPaintDevice'.

If you need to have a QApplication instance for your tests (eg use QPixmap), here's one way to do it. Just create a singleton so that you are ensured one and only one QApplication instance.

This is buried as a helper for tests in the PySide source.

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()

and then subclass 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)

hope this helps