Home > Net >  How to write unit tests for a pyqt5 QApplication?
How to write unit tests for a pyqt5 QApplication?

Time:11-04

I'm trying to make some unit tests for a pyqt5 application. The problem is that I cannot run multiple tests in a test suite because I'm not clearning up the application properly and the end of every test.


class MainWindowTest(QMainWindow):

    def __init__(self, widgetTypeUnderTest=None, model=None):
        super().__init__()
        self.widgetTypeUnderTest = widgetTypeUnderTest
        self.model = model

        # setting title
        self.setWindowTitle("AccosTest")
        self.setGeometry(100, 100, 500, 600)

        self.mainWindowLayout = QHBoxLayout()

        # container widget for everything else
        widget = QWidget()
        widget.setLayout(self.mainWindowLayout)
        self.setCentralWidget(widget)

        self.show()


class Tests(unittest.TestCase):

    def setUp(self) -> None:
        self.app = QApplication(sys.argv)

    def tearDown(self) -> None:
        self.app.exit()

    def test(self):
        mainWindow = MainWindowTest()

    def test2(self):
        mainWindow = MainWindowTest()


Runing Tests.test1 or Tests.test2 individually does what is required, although this is likely because a second QApplication has not been started. When running both tests together I get a segfault.

Would anybody know the correct commands to properly dismantle the QApplication after every test, since self.app.exit() doesn't seem to be doing the trick. Thanks!

edit

Do you think a better strategy would be to have two threads. One would start the main loop sys.exit(self.app.exec()) and the other would wait for a while and then call exit?

CodePudding user response:

There is normally no need to recreate a QApplication, even for testing (unless you do need to make multiple tests for the behavior of the program when the application is started or quit).

You can get the current existing QApplication using instance():

    def setUp(self) -> None:
        self.app = QApplication.instance() or QApplication.(sys.argv)

In the rare case for which you really need to restart the application, then you need to ensure that the previous application has really been quit. Since that presumes the fact that exec() has been called, you can then destroy the app reference and recreate it again.

Note that you cannot create another application outside of the main thread (or, to be precise, outside the thread in which the first application was created).

CodePudding user response:

Thanks to @musicamante for the answer.

Here it is in code:

Option 1

Use the destructor, using del in python.


class MainWindowTest(QMainWindow):

    def __init__(self, widgetTypeUnderTest=None, model=None):
        super().__init__()
        self.widgetTypeUnderTest = widgetTypeUnderTest
        self.model = model

        # setting title
        self.setWindowTitle("AccosTest")
        self.setGeometry(100, 100, 500, 600)

        self.mainWindowLayout = QHBoxLayout()

        # container widget for everything else
        widget = QWidget()
        widget.setLayout(self.mainWindowLayout)
        self.setCentralWidget(widget)

        self.show()


class Tests(unittest.TestCase):

    def setUp(self) -> None:
        self.app = QApplication(sys.argv)

    def tearDown(self) -> None:
        del self.app

    def test(self):
        mainWindow = MainWindowTest()

    def test2(self):
        mainWindow = MainWindowTest()

Option 2

Make QApplication global and

app = QApplication(sys.argv)


class Tests(unittest.TestCase):

    def setUp(self) -> None:
        self.app = app.instance()

    def tearDown(self) -> None:
        self.app.closeAllWindows()

    def test(self):
        mainWindow = MainWindowTest()

    def test2(self):
        mainWindow = MainWindowTest()

It seems that Option 2 should be preferred because you shouldn't need to make and destroy the QApplication after every test. However, my personal preference is Option 1 to keep unit tests completely independent.

  • Related