异步编程 101:如何测试 async 代码

视频原文:Strategies for testing Async code - PyCon 2019

同时参考了:

  • Testing Asyncio Python Code with Pytest

前面几篇关于异步编程的文章:

  • 异步编程 101: 是什么、小试Python asyncio

  • 异步编程 101:Python async await发展简史

  • 异步编程 101:写一个事件循环

  • 异步编程 101:asyncio中的 for 循环

  • 异步编程 101:asyncio 进阶上篇

异步编程,本质上是通过合作(cooperation)来达成并发效果,也即:需要 wait 的时候,也就是发生 IO 的时候,把控制权交给主事件循环。 (yield control when 'awaiting' asynchronous results.) 这个过程有点像事件循环完成了操作系统的工作,可以将事件循环看作是操作系统,然后把协程看作是线程这么来理解。整个事件循环是在一个线程里面的,意味着任务切换更加高效,无需上下文转换。

异步代码很高效,但是也有很蛋疼的地方,那就是测试。

0x01 : async 测试实例

来通过一个简单的例子看一下吧:一个Cat类,有一个 move 方法,这个方法是异步的。

640?wx_fmt=png

然后用 unittest 写一个测试类,你能发现下面代码的问题吗?

640?wx_fmt=png

herd(grafield, 'forward') 返回的是一个协程对象(coroutine object),如果你不await他,什么也不会发生。而coroutine object是 truthy 的,所以assertTrue() 是能够通过的。如果你运行一下 test,会看到coroutine herd was nerver awaited的 warning。

640?wx_fmt=png

下图这样调用await还是不对的,因为await关键字只能出现在async函数里面。

640?wx_fmt=png

一个解决方案是加入事件循环:

640?wx_fmt=png

这能work,但是估计你也看出来了,这很麻烦。如果我有多个方法,难道我需要每个 test方法都加一个事件循环吗?更重要的是,我只是想做一下单元测试,事件循环在这个时候实际上是一个底层细节,我不需要关心。

在 Python3.7 中,asyncio新增了一个方法:asyncio.run(),为你隐藏了事件循环的细节,所以能够让代码更加简洁:

640?wx_fmt=png

0x02 pytest-asyncio

安装:pip install pytest-asyncio,这实际上是pytest的一个插件。

640?wx_fmt=png

用法很简单,重要的是我们得知道工作原理。之前的代码问题在于,pytest 默认的的 runner 会将所有的函数当作普通函数处理,而对于 async 函数, 调用的时候返回的是一个 coroutine object。所以我们得想办法告诉 pytest 使用一个 eventloop 来运行测试方法。

一种方法是,实例化一个eventloop 然后注入到 tests里面,比如:

import asyncio	
import pytest	

	
async def say(what, when):	
    await asyncio.sleep(when)	
    return what	

	
@pytest.fixture	
def event_loop():	
    loop = asyncio.get_event_loop()	
    yield loop	
    loop.close()	

	

	
def test_say(event_loop):	
    expected = 'This should fail!'	
    assert expected == event_loop.run_until_complete(say('Hello!', 0))

这种方法不方便之处在于每次都需要手动注入 eventloop,更优雅的方法是调整 test runner,让它识别 async 函数,当作 asyncio tasks 来执行。

pytest-asyncio完成的功能就是这样的,它的 API 非常简单,你只需要为 async function 添加一个 @pytest.mark.asyncio 修饰器即可:

import pytest	
from say import say	

	
@pytest.mark.asyncio	
async def test_say():	
    assert 'Hello!' == await say('Hello!', 0)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值