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 theassertEventuallyTrueInGui
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.