diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index af71be40b89ff2..85bf2d475d72e3 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -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() @@ -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() @@ -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() diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index c0170d182573cb..6dd40bb4fdc7fc 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -1,5 +1,6 @@ """Test case implementation""" +import asyncio import sys import functools import difflib @@ -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 @@ -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: @@ -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): diff --git a/Lib/unittest/test/test_case.py b/Lib/unittest/test/test_case.py index 6b3439781c9de2..9cc49e20c2f954 100644 --- a/Lib/unittest/test/test_case.py +++ b/Lib/unittest/test/test_case.py @@ -1,3 +1,4 @@ +import asyncio import contextlib import difflib import pprint @@ -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() diff --git a/Misc/ACKS b/Misc/ACKS index d8179c8b03ab3f..46b151536d781e 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1540,6 +1540,7 @@ Daniel Stokes Michael Stone Serhiy Storchaka Ken Stox +Petter Strandmark Charalampos Stratakis Dan Stromberg Donald Stufft diff --git a/Misc/NEWS.d/next/Library/2018-03-05-20-01-47.bpo-32992.gfeea.rst b/Misc/NEWS.d/next/Library/2018-03-05-20-01-47.bpo-32992.gfeea.rst new file mode 100644 index 00000000000000..02981b24c78874 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-03-05-20-01-47.bpo-32992.gfeea.rst @@ -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.