asyncio + pytest 大坑:asyncio.create_task() 在测试中“卡死不动”

行,我给你压缩到一半长度,保留结论 + 关键机制 + 实操建议,直接能发博客那种。

 


 

 

⚠️ asyncio + pytest 大坑:

asyncio.create_task()

 在测试中“卡死不动”

 

 

结论先行
带 asyncio.create_task() 的后台异步代码,不要用 pytest 测,直接写脚本跑。

 

这是我在真实项目里踩到的一个非常隐蔽的坑,结论非常明确:

代码没问题,问题在测试框架。

 


 

 

问题现象

 

 

在 pytest 里测试一个异步 TokenKeeper(后台轮询 DB):

 

  • asyncio.create_task(self._refresh_loop()) 已执行

  • await asyncio.sleep(50) 明确让出执行权

  • 后台 task 完全不执行

  • 只有在 Ctrl+C 中断 pytest 时,后台 task 才突然开始跑

 

 

同样代码,独立脚本运行一切正常

 


 

 

根本原因(一句话版)

 

 

pytest 接管了 event loop,但不保证后台 task 的调度。

 

在 pytest(pytest-asyncio / anyio)里:

 

  • event loop 是“测试框架控制的”

  • 测试 coroutine 是主任务

  • create_task 创建的后台任务 不属于测试任务树

  • pytest 在某些情况下 不会主动调度这些 task

 

 

所以你看到的是:

 

task 创建了,但永远拿不到执行时间片

 


 

 

为什么 

asyncio.sleep()

 也没用?

 

 

这是个常见误区。

 

sleep() 只是让当前 coroutine让权,

不等于 pytest 会调度后台 task

 

结果就是:

你 sleep 50 秒,后台 task 还是不跑。

 


 

 

一个明确的“踩雷信号”

 

 

如果你发现:

 

  • 后台 task 只在 Ctrl+C 时才继续执行

  • 或测试结束时突然打印一堆后台日志

 

 

可以直接下结论:

 

你在用 pytest 测“服务型异步代码”,方式是错的。

 


 

 

哪些 async 代码不该用 pytest 测

 

 

只要命中任意一条,就别用 pytest:

 

  • asyncio.create_task

  • 无限循环 / poll / keep-alive

  • 后台刷新、守护任务

  • 依赖 event loop 生命周期的逻辑

 

 

这些是“服务”,不是“函数”。

 


 

 

正确做法(强烈建议)

 

 

 

✅ 用独立脚本测试

 

async def main():
    keeper = AsyncTokenKeeper(...)
    await keeper.start()

    token = await keeper.wait_ready()
    print("token ready:", token)

    await asyncio.sleep(60)

asyncio.run(main())

用:

python scripts/run_async_token_keeper.py

一切正常,逻辑符合直觉。

 


 

 

实战总结(重点)

 

 

  1. pytest ≠ asyncio 运行时

  2. create_task ≠ 一定会执行

  3. 后台 async 服务 ≠ 单元测试对象

  4. 服务型代码要“跑起来看”,不是“断言看”

  5. 怀疑 asyncio 前,先怀疑测试框架

 

 


 

 

最后一句话

 

 

异步服务代码,别迷信测试框架。
脚本才是最诚实的测试方式。

 

pytest 适合测函数,

不适合测“会一直跑的东西”。

 

—— 踩坑记录

 


 


这次的坑有点大,留个机会,供大家参考

posted @ 2026-01-30 00:07  就是想学习  阅读(3)  评论(0)    收藏  举报