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.
# (C) Copyright 2018-2021 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in LICENSE.txt and may be redistributed only under # the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! """ 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.executor = TraitsExecutor() def tearDown(self): # Request the executor to stop, and wait for that stop to complete. self.executor.stop() self.assertEventuallyTrueInGui( lambda: self.executor.stopped, timeout=SAFETY_TIMEOUT ) GuiTestAssistant.tearDown(self) def test_my_future(self): executor = self.executor future = submit_call(executor, pow, 3, 5) # Wait for the future to complete. self.assertEventuallyTrueInGui( lambda: future.done, timeout=SAFETY_TIMEOUT ) self.assertEqual(future.result, 243)
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
pyface.ui.qt4.util.gui_test_assistant.GuiTestAssistantclass 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
futurefuture, we want to wait until the future has completed and all communications from the background task have completed. We do that by using the
pyface.ui.qt4.util.gui_test_assistant.GuiTestAssistant.assertEventuallyTrueInGuimethod. 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. Note that the
stopmethod is not blocking and does not actually stop the executor - instead, it requests cancellation of all running futures and prevents new jobs from being scheduled. For the executor to eventually reach the
STOPPEDstate, the GUI event loop must again be running, so we make a second use of
tearDownmethod in the example.
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
call and then wait for the executor to stop: the executor won’t reach
state until all futures have completed.