From fd7b1b55301b203861b06831fbc8fe7af03a483e Mon Sep 17 00:00:00 2001 From: Xtreak Date: Tue, 10 Sep 2019 16:39:22 +0100 Subject: [PATCH 1/6] Initial pass of async_case docs with an example. --- Doc/library/unittest.rst | 77 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index 320d898fc8d4ca..eed983e57a377e 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -1486,8 +1486,85 @@ Test cases .. versionadded:: 3.8 +.. class:: async_case.IsolatedAsyncioTestCase(methodName='runTest') + This class provides an API similar to :class:`TestCase` and also accepts + coroutines as test functions. + .. versionadded:: 3.8 + + .. coroutinemethod:: asyncSetUp() + + Method called to prepare the test fixture. This is called after :meth:`setUp`. + This is called immediately before calling the test method; other than + :exc:`AssertionError` or :exc:`SkipTest`, any exception raised by this method + will be considered an error rather than a test failure. The default implementation + does nothing. + + .. coroutinemethod:: asyncTearDown() + + Method called immediately after the test method has been called and the + result recorded. This is called before :meth:`tearDown`. This is called even if + the test method raised an exception, so the implementation in subclasses may need + to be particularly careful about checking internal state. Any exception, other than + :exc:`AssertionError` or :exc:`SkipTest`, raised by this method will be + considered an additional error rather than a test failure (thus increasing + the total number of reported errors). This method will only be called if + the :meth:`asyncSetUp` succeeds, regardless of the outcome of the test method. + The default implementation does nothing. + + .. method:: addAsyncCleanup(function, /, *args, **kwargs) + + This method accepts a coroutine that can be used as a cleanup function. + + .. method:: run(result=None) + + Sets up a new event loop to run the test, collecting the result into + the :class:`TestResult` object passed as *result*. If *result* is + omitted or ``None``, a temporary result object is created (by calling + the :meth:`defaultTestResult` method) and used. The result object is + returned to :meth:`run`'s caller. At the end of the test all the tasks + in the event loop are cancelled. + + + An example illustrating the order:: + + from unittest.async_case import IsolatedAsyncioTestCase + + events = [] + + class Test(IsolatedAsyncioTestCase): + + def setUp(self): + self._sync_connection = ExpensiveSyncConnection() + events.append("setUp") + + async def asyncSetUp(self): + self._async_connection = await ExpensiveAsyncConnection() + events.append("asyncSetUp") + + async def test_response(self): + response = await self._async_connection.get("https://example.com") + self.assertEqual(response.status_code, 200) + self.addAsyncCleanup(self.on_cleanup) + + response = self._sync_connection.get("https://example.com") + self.assertEqual(response.status_code, 200) + + def tearDown(self): + self._sync_connection.close() + events.append("tearDown") + + async def asyncTearDown(self): + await self._async_connection.close() + events.append("asyncTearDown") + + async def on_cleanup(self): + events.append("cleanup") + + test = Test("test_response") + test.run() + assert events == ["setUp", "asyncSetUp", "asyncTearDown", "tearDown", "cleanup"] .. class:: FunctionTestCase(testFunc, setUp=None, tearDown=None, description=None) From 2fc2ba0623bcf66d0f4a879c4849c17233fb449b Mon Sep 17 00:00:00 2001 From: Xtreak Date: Tue, 10 Sep 2019 16:46:41 +0100 Subject: [PATCH 2/6] Fix indentation and NEWS entry. --- Doc/library/unittest.rst | 20 +++++++++++--------- Misc/NEWS.d/3.8.0b1.rst | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index eed983e57a377e..9967e5416b4395 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -1533,23 +1533,25 @@ Test cases events = [] + class Test(IsolatedAsyncioTestCase): + def setUp(self): self._sync_connection = ExpensiveSyncConnection() - events.append("setUp") + events.append("setUp") async def asyncSetUp(self): self._async_connection = await ExpensiveAsyncConnection() - events.append("asyncSetUp") + events.append("asyncSetUp") async def test_response(self): - response = await self._async_connection.get("https://example.com") - self.assertEqual(response.status_code, 200) - self.addAsyncCleanup(self.on_cleanup) + response = await self._async_connection.get("https://example.com") + self.assertEqual(response.status_code, 200) + self.addAsyncCleanup(self.on_cleanup) response = self._sync_connection.get("https://example.com") - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, 200) def tearDown(self): self._sync_connection.close() @@ -1557,10 +1559,10 @@ Test cases async def asyncTearDown(self): await self._async_connection.close() - events.append("asyncTearDown") + events.append("asyncTearDown") - async def on_cleanup(self): - events.append("cleanup") + async def on_cleanup(self): + events.append("cleanup") test = Test("test_response") test.run() diff --git a/Misc/NEWS.d/3.8.0b1.rst b/Misc/NEWS.d/3.8.0b1.rst index 84b0350134fb10..edac368730113f 100644 --- a/Misc/NEWS.d/3.8.0b1.rst +++ b/Misc/NEWS.d/3.8.0b1.rst @@ -808,7 +808,7 @@ detect classes that can be passed to `hex()`, `oct()` and `bin()`. .. nonce: LoeUNh .. section: Library -Implement ``unittest.AsyncTestCase`` to help testing asyncio-based code. +Implement ``unittest.async_case.IsolatedAsyncioTestCase`` to help testing asyncio-based code. .. From 7cf96fbdc2231c451232fe5ddcfcbe99e29c78cd Mon Sep 17 00:00:00 2001 From: Xtreak Date: Tue, 10 Sep 2019 16:47:48 +0100 Subject: [PATCH 3/6] Fix indentation. --- Doc/library/unittest.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index 9967e5416b4395..ccf1db5a08a7c4 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -1555,7 +1555,7 @@ Test cases def tearDown(self): self._sync_connection.close() - events.append("tearDown") + events.append("tearDown") async def asyncTearDown(self): await self._async_connection.close() From 866ce960a86d30656ed5b8b959dcaa6c6f88a413 Mon Sep 17 00:00:00 2001 From: Xtreak Date: Wed, 11 Sep 2019 10:35:48 +0100 Subject: [PATCH 4/6] Fix import path and add an example to whatsnew document. --- Doc/library/unittest.rst | 4 ++-- Doc/whatsnew/3.8.rst | 26 ++++++++++++++++++++++++++ Misc/NEWS.d/3.8.0b1.rst | 2 +- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index ccf1db5a08a7c4..54bf9d6a6edd4d 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -1486,7 +1486,7 @@ Test cases .. versionadded:: 3.8 -.. class:: async_case.IsolatedAsyncioTestCase(methodName='runTest') +.. class:: IsolatedAsyncioTestCase(methodName='runTest') This class provides an API similar to :class:`TestCase` and also accepts coroutines as test functions. @@ -1529,7 +1529,7 @@ Test cases An example illustrating the order:: - from unittest.async_case import IsolatedAsyncioTestCase + from unittest import IsolatedAsyncioTestCase events = [] diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 2b4eb63d61f658..89524b30840600 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -1111,6 +1111,32 @@ unittest * Several mock assert functions now also print a list of actual calls upon failure. (Contributed by Petter Strandmark in :issue:`35047`.) +* :mod:`unittest` module gained support for coroutines to be used as test cases + with :class:`unittest.IsolatedAsyncioTestCase`. + (Contributed by Andrew Svetlov in :issue:`32972`.) + + Example:: + + import unittest + + + class TestRequest(unittest.IsolatedAsyncioTestCase): + + async def asyncSetUp(self): + self.connection = await ExpensiveConnection() + + async def test_get(self): + response = await self.connection.get("https://example.com") + self.assertEqual(response.status_code, 200) + + async def asyncTearDown(self): + await self.connection.close() + + + if __name__ == "__main__": + unittest.main() + + venv ---- diff --git a/Misc/NEWS.d/3.8.0b1.rst b/Misc/NEWS.d/3.8.0b1.rst index edac368730113f..43a88a37c5cb08 100644 --- a/Misc/NEWS.d/3.8.0b1.rst +++ b/Misc/NEWS.d/3.8.0b1.rst @@ -808,7 +808,7 @@ detect classes that can be passed to `hex()`, `oct()` and `bin()`. .. nonce: LoeUNh .. section: Library -Implement ``unittest.async_case.IsolatedAsyncioTestCase`` to help testing asyncio-based code. +Implement ``unittest.IsolatedAsyncioTestCase`` to help testing asyncio-based code. .. From 0b42ec2703fa4445a8c4eff762e151a53cca0e3e Mon Sep 17 00:00:00 2001 From: Xtreak Date: Wed, 11 Sep 2019 11:25:01 +0100 Subject: [PATCH 5/6] Make example use test discovery. Remove sync connection example. --- Doc/library/unittest.rst | 13 ++++++------- Doc/whatsnew/3.8.rst | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index 54bf9d6a6edd4d..06e0b2b05d3ff7 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -1542,17 +1542,15 @@ Test cases events.append("setUp") async def asyncSetUp(self): - self._async_connection = await ExpensiveAsyncConnection() + self._async_connection = await AsyncConnection() events.append("asyncSetUp") async def test_response(self): + events.append("test_response") response = await self._async_connection.get("https://example.com") self.assertEqual(response.status_code, 200) self.addAsyncCleanup(self.on_cleanup) - response = self._sync_connection.get("https://example.com") - self.assertEqual(response.status_code, 200) - def tearDown(self): self._sync_connection.close() events.append("tearDown") @@ -1564,9 +1562,10 @@ Test cases async def on_cleanup(self): events.append("cleanup") - test = Test("test_response") - test.run() - assert events == ["setUp", "asyncSetUp", "asyncTearDown", "tearDown", "cleanup"] + if __name__ == "__main__": + unittest.main() + + After running the test ``events`` would contain ``["setUp", "asyncSetUp", "test_response", "asyncTearDown", "tearDown", "cleanup"]`` .. class:: FunctionTestCase(testFunc, setUp=None, tearDown=None, description=None) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 89524b30840600..07202ea4ff08d6 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -1123,7 +1123,7 @@ unittest class TestRequest(unittest.IsolatedAsyncioTestCase): async def asyncSetUp(self): - self.connection = await ExpensiveConnection() + self.connection = await AsyncConnection() async def test_get(self): response = await self.connection.get("https://example.com") From ecd8db8281f691bcfc6c2e1fda28d2c6ff17d700 Mon Sep 17 00:00:00 2001 From: Xtreak Date: Wed, 11 Sep 2019 11:34:44 +0100 Subject: [PATCH 6/6] Drop sync connection examples. --- Doc/library/unittest.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index 06e0b2b05d3ff7..8c9affe9db952c 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -1538,7 +1538,6 @@ Test cases def setUp(self): - self._sync_connection = ExpensiveSyncConnection() events.append("setUp") async def asyncSetUp(self): @@ -1552,7 +1551,6 @@ Test cases self.addAsyncCleanup(self.on_cleanup) def tearDown(self): - self._sync_connection.close() events.append("tearDown") async def asyncTearDown(self):