[TIP] Testing asyncio coroutines

Stefan Scherfke stefan at sofa-rockers.org
Wed Feb 18 23:58:53 PST 2015


> Am 2015-02-19 um 08:40 schrieb Ronny Pfannschmidt <opensource at ronnypfannschmidt.de>:
> 
> 
> the coroutine approach will be added to pytest after the 2.7 release 

Cool.  Let me know when I can help you with testing it. :-)


> On Tuesday, February 17, 2015 5:09:13 PM CEST, Stefan Scherfke wrote:
>> Hi all,
>> 
>> I currently try to find a nice way to test asyncio coroutines.
>> 
>> TL;DR: I found a possible solution, but I’m not sure if it’s correct or
>> too hacky.
>> 
>> ---
>> 
>> A naïve approach would be:
>> 
>> 
>>    # tests/test_coros.py
>>    import asyncio
>> 
>>    def test_coro():
>>        loop = asyncio.get_event_loop()
>> 
>>        @asyncio.coroutine
>>        def do_test():
>>            yield from asyncio.sleep(0.1)
>>            assert 0  # onoes!
>> 
>>        loop.run_until_complete(do_test())
>> 
>> 
>> I can also easily pull out getting a loop instance:
>> 
>> 
>>    # tests/conftest.py
>>    import asyncio
>> 
>> 
>>    @pytest.yield_fixture
>>    def loop():
>>        loop = asyncio.new_event_loop()
>>        asyncio.set_event_loop(loop)
>>        yield loop
>>        loop.close()
>> 
>> 
>>    # tests/test_coros.py
>>    def test_coro(loop):
>>        @asyncio.coroutine
>>        def do_test():
>>            yield from asyncio.sleep(0.1)
>>            assert 0  # onoes!
>> 
>>        loop.run_until_complete(do_test())
>> 
>> 
>> However, I actually want my tests to look like:
>> 
>>    # tests/test_coros.py
>>    @asyncio.coroutine
>>    def test_coro(loop):
>>        yield from asyncio.sleep(0.1)
>>        assert 0
>> 
>> 
>> I’ve come up with a (somewhat hacky?) solution:
>> 
>>    # tests/conftest.py
>>    import asyncio
>> 
>> 
>>    @pytest.yield_fixture
>>    def loop():
>>        loop = asyncio.new_event_loop()
>>        asyncio.set_event_loop(loop)
>>        yield loop
>>        loop.close()
>> 
>> 
>>    def pytest_pycollect_makeitem(collector, name, obj):
>>        """Collect asyncio coroutines as normal functions, not as generators."""
>>        if asyncio.iscoroutinefunction(obj):
>>            return list(collector._genfunctions(name, obj))
>> 
>> 
>>    def pytest_pyfunc_call(pyfuncitem):
>>        """If ``pyfuncitem.obj`` is an asyncio coroutinefunction, execute it via
>>        the event loop instead of calling it directly."""
>>        testfunction = pyfuncitem.obj
>> 
>>        if not asyncio.iscoroutinefunction(testfunction):
>>            return
>> 
>>        # Copied from _pytest/python.py:pytest_pyfunc_call()
>>        funcargs = pyfuncitem.funcargs
>>        testargs = {}
>>        for arg in pyfuncitem._fixtureinfo.argnames:
>>            testargs[arg] = funcargs[arg]
>>        coro = testfunction(**testargs)  # Will no execute the test yet!
>> 
>>        # Run the coro in the event loop
>>        loop = testargs.get('loop', asyncio.get_event_loop())
>>        loop.run_until_complete(coro)
>> 
>>        return True  # TODO: What to return here?
>> 
>> 
>> It looks like it works normaly, that means I can use fixtutes in my
>> coroutine tests and if asserts fail, I get a correct report.
>> 
>> I’m not sure though what to return from pytest_pyfunc_call().  Also,
>> I’m not sure if this is the intended way to solve this.  What do you
>> think?  What can I do better?
>> 
>> Cheers,
>> Stefan
>> 
>> 




More information about the testing-in-python mailing list