What's the best way to write unit tests for code using the Python 3.4 asyncio
library? Assume I want to test a TCP client (SocketConnection
):
import asyncio import unittest class TestSocketConnection(unittest.TestCase): def setUp(self): self.mock_server = MockServer("localhost", 1337) self.socket_connection = SocketConnection("localhost", 1337) @asyncio.coroutine def test_sends_handshake_after_connect(self): yield from self.socket_connection.connect() self.assertTrue(self.mock_server.received_handshake())
When running this test case with the default test runner, the test will always succeed as the method executes only up until the first yield from
instruction, after which it returns before executing any assertions. This causes tests to always succeed.
Is there a prebuilt test runner that is able to handle asynchronous code like this?
First you need to create a test file. Then import the unittest module, define the testing class that inherits from unittest. TestCase, and lastly, write a series of methods to test all the cases of your function's behavior. First, you need to import a unittest and the function you want to test, formatted_name() .
Async IO takes long waiting periods in which functions would otherwise be blocking and allows other functions to run during that downtime. (A function that blocks effectively forbids others from running from the time that it starts until the time that it returns.)
There are two ways you can use assertRaises: using keyword arguments. Just pass the exception, the callable function and the parameters of the callable function as keyword arguments that will elicit the exception. Make a function call that should raise the exception with a context.
Since Python 3.8 unittest comes with the IsolatedAsyncioTestCase function, designed for this purpose.
from unittest import IsolatedAsyncioTestCase class Test(IsolatedAsyncioTestCase): async def test_functionality(self): result = await functionality() self.assertEqual(expected, result)
I temporarily solved the problem using a decorator inspired by Tornado's gen_test:
def async_test(f): def wrapper(*args, **kwargs): coro = asyncio.coroutine(f) future = coro(*args, **kwargs) loop = asyncio.get_event_loop() loop.run_until_complete(future) return wrapper
Like J.F. Sebastian suggested, this decorator will block until the test method coroutine has finished. This allows me to write test cases like this:
class TestSocketConnection(unittest.TestCase): def setUp(self): self.mock_server = MockServer("localhost", 1337) self.socket_connection = SocketConnection("localhost", 1337) @async_test def test_sends_handshake_after_connect(self): yield from self.socket_connection.connect() self.assertTrue(self.mock_server.received_handshake())
This solution probably misses some edge cases.
I think a facility like this should added to Python's standard library to make asyncio
and unittest
interaction more convenient out of the box.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With