python-testtools/doc/twisted-support.rst

145 lines
4.9 KiB
ReStructuredText

.. _twisted-support:
Twisted support
===============
testtools provides support for testing Twisted code. Install the
``testtools[twisted]`` extra to use this.
Matching Deferreds
------------------
testtools provides support for making assertions about synchronous
:py:class:`~twisted.internet.defer.Deferred`\s.
A "synchronous" :py:class:`~twisted.internet.defer.Deferred` is one that does
not need the reactor or any other asynchronous process in order to fire.
Normal application code can't know when a
:py:class:`~twisted.internet.defer.Deferred` is going to fire, because that is
generally left up to the reactor. Well-written unit tests provide fake
reactors, or don't use the reactor at all, so that
:py:class:`~twisted.internet.defer.Deferred`\s fire synchronously.
These matchers allow you to make assertions about when and how
:py:class:`~twisted.internet.defer.Deferred`\s fire, and about what values
they fire with.
See also `Testing Deferreds without the reactor`_ and the `Deferred howto`_.
.. autofunction:: testtools.twistedsupport.succeeded
:noindex:
.. autofunction:: testtools.twistedsupport.failed
:noindex:
.. autofunction:: testtools.twistedsupport.has_no_result
:noindex:
Running tests in the reactor
----------------------------
testtools provides support for running asynchronous Twisted tests: tests that
return a :py:class:`~twisted.internet.defer.Deferred` and run the reactor
until it fires and its callback chain is completed.
Here's how to use it::
from testtools import TestCase
from testtools.twistedsupport import AsynchronousDeferredRunTest
class MyTwistedTests(TestCase):
run_tests_with = AsynchronousDeferredRunTest
def test_foo(self):
# ...
return d
Note that you do *not* have to use a special base ``TestCase`` in order to run
Twisted tests, you should just use the regular :py:class:`testtools.TestCase`
base class.
You can also run individual tests within a test case class using the Twisted
test runner::
class MyTestsSomeOfWhichAreTwisted(TestCase):
def test_normal(self):
pass
@run_test_with(AsynchronousDeferredRunTest)
def test_twisted(self):
# ...
return d
See :py:class:`~testtools.twistedsupport.AsynchronousDeferredRunTest` and
:py:class:`~testtools.twistedsupport.AsynchronousDeferredRunTestForBrokenTwisted`
for more information.
Controlling the Twisted logs
----------------------------
Users of Twisted Trial will be accustomed to all tests logging to
``_trial_temp/test.log``. By default,
:py:class:`~testtools.twistedsupport.AsynchronousDeferredRunTest` will *not*
do this, but will instead:
1. suppress all messages logged during the test run
2. attach them as the ``twisted-log`` detail (see :ref:`details`) which is
shown if the test fails
The first behavior is controlled by the ``suppress_twisted_logging`` parameter
to :py:class:`~testtools.twistedsupport.AsynchronousDeferredRunTest`, which is
set to ``True`` by default. The second is controlled by the
``store_twisted_logs`` parameter, which is also ``True`` by default.
If ``store_twisted_logs`` is set to ``False``, you can still get the logs
attached as a detail by using the
:py:class:`~testtools.twistedsupport.CaptureTwistedLogs` fixture. Using the
:py:class:`~testtools.twistedsupport.CaptureTwistedLogs` fixture is equivalent
to setting ``store_twisted_logs`` to ``True``.
For example::
class DoNotCaptureLogsTests(TestCase):
run_tests_with = partial(AsynchronousDeferredRunTest,
store_twisted_logs=False)
def test_foo(self):
log.msg('logs from this test are not attached')
def test_bar(self):
self.useFixture(CaptureTwistedLogs())
log.msg('logs from this test *are* attached')
Converting Trial tests to testtools tests
-----------------------------------------
* Use the :py:class:`~testtools.twistedsupport.AsynchronousDeferredRunTest` runner
* Make sure to upcall to :py:meth:`.TestCase.setUp` and
:py:meth:`.TestCase.tearDown`
* Don't use ``setUpClass`` or ``tearDownClass``
* Don't expect setting ``.todo``, ``.timeout`` or ``.skip`` attributes to do
anything
* Replace
:py:meth:`twisted.trial.unittest.SynchronousTestCase.flushLoggedErrors`
with
:py:func:`~testtools.twistedsupport.flush_logged_errors`
* Replace :py:meth:`twisted.trial.unittest.TestCase.assertFailure` with
:py:func:`~testtools.twistedsupport.assert_fails_with`
* Trial spins the reactor a couple of times before cleaning it up,
:py:class:`~testtools.twistedsupport.AsynchronousDeferredRunTest` does not. If
you rely on this behavior, use
:py:class:`~testtools.twistedsupport.AsynchronousDeferredRunTestForBrokenTwisted`.
.. _Deferred Howto: http://twistedmatrix.com/documents/current/core/howto/defer.html
.. _Testing Deferreds without the reactor:
http://twistedmatrix.com/documents/current/core/howto/trial.html#testing-deferreds-without-the-reactor