
本文介绍如何安全、精准地 mock asyncio.sleep,使其在被测模块(SUT)中按比例加速(如缩短为原时长的 1/10),同时避免污染测试模块自身对 asyncio.sleep 的调用,兼顾测试速度与行为真实性。
本文介绍如何安全、精准地 mock `asyncio.sleep`,使其在被测模块(sut)中按比例加速(如缩短为原时长的 1/10),同时避免污染测试模块自身对 `asyncio.sleep` 的调用,兼顾测试速度与行为真实性。
在异步 Python 单元测试中,asyncio.sleep() 常成为性能瓶颈——例如一个真实业务逻辑中调用 await asyncio.sleep(60),若直接运行测试将导致单测耗时 1 分钟。理想方案是将其“加速”,比如按 10 倍速执行:await asyncio.sleep(6),既大幅缩短测试时间,又保留事件循环调度、协程挂起/恢复等关键行为,有助于暴露竞态条件、未 await 调用或错误的 create_task 使用等问题。
关键挑战在于:sut.py 和 test.py 都通过 import asyncio 引入标准库模块,而 patch("sut.asyncio.sleep") 实际替换的是 sut 模块命名空间中绑定的 sleep 对象。但由于 asyncio 是单例模块,一旦 patch 其属性,所有导入该模块的地方(包括 test.py)看到的 asyncio.sleep 都会变成 Mock 实例——这会导致你在测试体内无法再调用真实的 asyncio.sleep(例如用于构造预期延迟或辅助断言)。
✅ 正确解法不是“只 patch 某个模块的 asyncio”,而是在 patch 前捕获原始函数,并在 mock 的 side_effect 中安全调用它:
import asyncio
import pytest
from unittest.mock import patch
import sut # 被测模块
@pytest.mark.asyncio
async def test_sut_with_accelerated_sleep():
# 1. 提前保存原始 sleep 函数(来自 asyncio 模块本身)
original_sleep = asyncio.sleep
# 2. 定义加速版 sleep:传入时间除以加速因子(如 10)
async def fast_sleep(duration: float):
await original_sleep(duration / 10)
# 3. 仅 patch sut 模块中使用的 sleep(即 sut.asyncio.sleep)
with patch("sut.asyncio.sleep") as mock_sleep:
mock_sleep.side_effect = fast_sleep
# 4. 执行被测逻辑(内部 await sut.asyncio.sleep(...) 将走 fast_sleep)
await sut.long_running_task() # 假设其内部含 await asyncio.sleep(30)
# ✅ 此处仍可正常使用真实 asyncio.sleep(未被污染)
await asyncio.sleep(0.01) # 用于微调、同步或验证时序? 进阶推荐:封装为 pytest fixture,实现跨测试复用与自动清理:
import asyncio
import pytest
from unittest.mock import patch
@pytest.fixture
def fast_sleep(factor: int = 10):
"""Fixture that patches asyncio.sleep in sut to run faster by given factor."""
original_sleep = asyncio.sleep
async def accelerated_sleep(duration: float):
if duration <= 0:
return
await original_sleep(duration / factor)
# 注意:此处 patch "asyncio.sleep" 即可(非 "sut.asyncio.sleep")
# 因为 pytest fixture 作用域内 patch 会生效于所有后续导入 ——
# 但关键在于:我们只在测试执行阶段 patch,且 fixture yield 后自动 unpatch
with patch("asyncio.sleep") as mock_sleep:
mock_sleep.side_effect = accelerated_sleep
yield
# 在测试中直接使用
@pytest.mark.asyncio
async def test_user_login_flow(fast_sleep):
result = await sut.authenticate_user("test", "pass")
assert result["status"] == "success"
# 内部 await asyncio.sleep(5) → 实际等待 0.5s⚠️ 注意事项:
- 不要 patch
"sut.asyncio"整个模块(如@patch("sut.asyncio")),这会覆盖sut中所有asyncio属性(如create_task,gather等),极易引发不可预知错误; - 若
sut.py采用from asyncio import sleep方式导入,则需 patch"sut.sleep"(而非"sut.asyncio.sleep"),请根据实际导入方式调整 patch 目标路径; - 加速因子不宜过大(如 1000×),否则可能使本应串行的异步操作因时间片过短而“伪并行”,掩盖真实竞态问题;
- 对于严格依赖精确时序的测试(如超时逻辑),建议保留原始 sleep 并使用
pytest-asyncio的event_loopfixture 控制运行环境,而非全局加速。
通过上述方式,你既能显著提升异步测试执行效率,又能保持对 asyncio 调度行为的可观测性与可控性,真正实现“快而不失真”的高质量自动化验证。

















