Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Doc/library/unittest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,11 @@ Test cases
and report failures, and some inquiry methods allowing information about the
test itself to be gathered.

.. versionchanged:: 3.8
Any test method, along with :meth:`setUp` and :meth:`tearDown` can
optionally be coroutine functions. In this case, they will be executed
on the default loop when running the test.

Methods in the first group (running the test) are:

.. method:: setUp()
Expand All @@ -702,6 +707,9 @@ Test cases
any exception raised by this method will be considered an error rather than
a test failure. The default implementation does nothing.

.. versionchanged:: 3.8
This method can optionally be a coroutine function.


.. method:: tearDown()

Expand All @@ -715,6 +723,9 @@ Test cases
the :meth:`setUp` succeeds, regardless of the outcome of the test method.
The default implementation does nothing.

.. versionchanged:: 3.8
This method can optionally be a coroutine function.


.. method:: setUpClass()

Expand Down
21 changes: 18 additions & 3 deletions Lib/unittest/case.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Test case implementation"""

import asyncio
import sys
import functools
import difflib
Expand Down Expand Up @@ -369,6 +370,9 @@ class TestCase(object):
of the classes are instantiated automatically by parts of the framework
in order to be run.

The setUp, tearDown, and any test methods may optionally be coroutine
functions and will be executed in the default loop.

When subclassing TestCase, you can set these attributes:
* failureException: determines which exception will be raised when
the instance's assertion methods fail; test methods raising this
Expand Down Expand Up @@ -608,14 +612,14 @@ def run(self, result=None):
self._outcome = outcome

with outcome.testPartExecutor(self):
self.setUp()
self._runIfCoroutine(self.setUp())
if outcome.success:
outcome.expecting_failure = expecting_failure
with outcome.testPartExecutor(self, isTest=True):
testMethod()
self._runIfCoroutine(testMethod())
outcome.expecting_failure = False
with outcome.testPartExecutor(self):
self.tearDown()
self._runIfCoroutine(self.tearDown())

self.doCleanups()
for test, reason in outcome.skipped:
Expand Down Expand Up @@ -1329,6 +1333,17 @@ def assertNotRegex(self, text, unexpected_regex, msg=None):
msg = self._formatMessage(msg, standardMsg)
raise self.failureException(msg)

def _runIfCoroutine(self, possible_coroutine):
"""Execute the argument in the default loop if it is a coroutine.

Otherwise, just return the argument. This function wraps test
methods and allows them to be async.
"""
if asyncio.iscoroutine(possible_coroutine):
loop = asyncio.get_event_loop()
return loop.run_until_complete(possible_coroutine)
else:
return possible_coroutine

def _deprecate(original_func):
def deprecated_func(*args, **kwargs):
Expand Down
31 changes: 31 additions & 0 deletions Lib/unittest/test/test_case.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
import contextlib
import difflib
import pprint
Expand Down Expand Up @@ -1828,6 +1829,36 @@ def test2(self):
testcase.run()
self.assertEqual(MyException.ninstance, 0)

def test_coroutines(self):
# Test that test methods, setUp and tearDown can
# be coroutines.
class TestCase(unittest.TestCase):
def __init__(self, method_name):
super().__init__(method_name)
self.setUp_called = False
self.tearDown_called = False
self.test1_called = False

async def setUp(self):
# Await 0 seconds to prevent the entire function
# from executing immediately.
await asyncio.sleep(0.0)
self.setUp_called = True

async def tearDown(self):
await asyncio.sleep(0.0)
self.tearDown_called = True

async def test1(self):
await asyncio.sleep(0.0)
self.test1_called = True

testcase = TestCase('test1')
testcase.run()
self.assertTrue(testcase.setUp_called)
self.assertTrue(testcase.tearDown_called)
self.assertTrue(testcase.test1_called)


if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -1540,6 +1540,7 @@ Daniel Stokes
Michael Stone
Serhiy Storchaka
Ken Stox
Petter Strandmark
Charalampos Stratakis
Dan Stromberg
Donald Stufft
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Test methods, ``setUp`` and ``tearDown`` in ``unittest.TestCase`` may now be
coroutine functions and will be excuted in the default ``asyncio`` loop.
Patch by Petter Strandmark.