Testing Traits Futures code

This section gives some hints and tips for unit-testing code that uses Traits Futures. Those tests face two main challenges:

  • By design, Traits Futures relies on a running GUI event loop; that typically means that each test that uses Traits Futures will need to find some way to run the event loop in order for futures to deliver their results.

  • It’s important to fully shut down executors after each test, to avoid leaked threads and potential for test interactions.

An example test

Here’s an example of testing a simple future.

"""
Example of testing a simple future using the GuiTestAssistant.
"""

import unittest

from pyface.toolkit import toolkit_object
from traits_futures.api import submit_call, TraitsExecutor

#: Maximum timeout for blocking calls, in seconds. A successful test should
#: never hit this timeout - it's there to prevent a failing test from hanging
#: forever and blocking the rest of the test suite.
SAFETY_TIMEOUT = 5.0


#: Note that the GuiTestAssistant is currently only available for Qt, not
#: for wxPython. To run this unit test, you'll need PyQt or PySide 2 installed.
GuiTestAssistant = toolkit_object("util.gui_test_assistant:GuiTestAssistant")


class TestMyFuture(GuiTestAssistant, unittest.TestCase):
    def setUp(self):
        GuiTestAssistant.setUp(self)
        self.traits_executor = TraitsExecutor()

    def tearDown(self):
        # Request the executor to stop, and wait for that stop to complete.
        self.traits_executor.shutdown(timeout=SAFETY_TIMEOUT)
        GuiTestAssistant.tearDown(self)

    def test_my_future(self):
        future = submit_call(self.traits_executor, pow, 3, 5)

        # Wait for the future to complete.
        self.assertEventuallyTrueInGui(
            lambda: future.done, timeout=SAFETY_TIMEOUT
        )

        self.assertEqual(future.result, 243)


if __name__ == "__main__":
    unittest.main()

Some points of interest in the above example:

  • In order for the result of our future execution to be delivered to the main thread (a.k.a. the GUI thread), the event loop for the main thread needs to be running. We make use of the GuiTestAssistant class from Pyface to make it easy to run the event loop until a particular condition occurs.

  • In the main test method, after submitting the call and receiving the future future, we want to wait until the future has completed and all communications from the background task have completed. We do that by using the assertEventuallyTrueInGui method. At that point, we can check that the result of the future is the expected one.

  • We also need to shut down the executor itself at the end of the test; we use the shutdown method for this.

  • In all potentially blocking calls, we provide a timeout. This should help prevent a failing test from blocking the entire test run if something goes wrong. However, note that if the timeout on the shutdown method fails then in addition to the test failing you may see segmentation faults or other peculiar side-effects, especially at process termination time, as a result of pieces of cleanup occurring out of order.

If you don’t need the result of the future (for example because you’re using the future for its side-effect rather than to perform a computation) then it’s safe to remove the wait for future.done, so long as you keep the shutdown call.