pytest测试中apscheduler未清理会导致进程不退出或runtimeerror,应禁用自动启动、mock方法、用freezegun控制时间、fixture中显式shutdown、避免session级复用,并为asyncioscheduler配置pytest-asyncio。

pytest 运行时 APScheduler 仍在后台执行任务
这是最典型的冲突现象:测试用例跑完后 pytest 进程不退出,或出现 RuntimeError: cannot schedule new futures after shutdown,根本原因是 APScheduler 的默认 BackgroundScheduler 启动后会持续运行线程,而 pytest 默认不主动清理全局调度器实例。
实操建议:
- 所有测试中避免调用
start()—— 即使是yield_fixture或setup_method里也不该直接启动调度器 - 改用
BlockingScheduler仅用于调试,测试中必须禁用自动启动;生产代码里若用了BackgroundScheduler,测试时应 mock 其start、shutdown和add_job方法 - 在 conftest.py 中统一 patch:
pytest.fixture(autouse=True)里用monkeypatch.setattr('apscheduler.schedulers.background.BackgroundScheduler.start', lambda self: None)
测试中 job 执行导致断言失败或状态污染
比如一个每秒触发的 add_job 在测试期间意外修改了数据库或全局变量,让后续断言不可靠。这不是并发问题,而是测试隔离缺失。
实操建议:
- 永远不要在测试中依赖真实时间推进 —— 用
freezegun.freeze_time控制时间,再手动调用scheduler._process_jobs()(需先pause())来“单步执行”任务 - 如果 job 逻辑较重,把核心逻辑抽成独立函数,在测试中直接调用该函数,而非通过 scheduler 触发
- 检查 job 是否有隐式副作用:例如写日志到文件、发 HTTP 请求、改模块级变量 —— 这些都应在测试前用
patch或临时重置
fixture 中 scheduler 实例未正确 cleanup
常见错误是写了 @pytest.fixture 返回一个已 start() 的 BackgroundScheduler,但没配 scope="function" 或漏掉 yield 后的 shutdown(),导致多个测试共享同一调度器,job 重复注册或状态残留。
实操建议:
- fixture 必须显式
yield并在finally块中调用scheduler.shutdown(wait=False);wait=False 防止 pytest 等待线程自然结束(可能卡住) - 避免
scope="session"—— APScheduler 不支持跨 session 复用,且线程对象无法序列化 - 若需复用 scheduler 配置,只 fixture 配置 dict,每次测试新建实例,而不是 fixture scheduler 实例本身
使用 asyncio 版本时 event loop 冲突
当项目用 AsyncIOScheduler,而 pytest 默认不提供 running event loop,就会报 RuntimeError: There is no current event loop in thread,尤其在 async def test_* 中直接 await job 函数时更明显。
实操建议:
- 测试 async scheduler 时,必须启用
pytest-asyncio插件,并在测试函数上加@pytest.mark.asyncio - 不要在 fixture 中调用
asyncio.get_event_loop()—— 改用asyncio.new_event_loop()+asyncio.set_event_loop()显式管理,且确保每个测试用独立 loop - 如果 job 是 async function,测试中别用
scheduler.add_job(..., 'interval', seconds=1),改用scheduler.add_job(job_func, 'date', run_date=datetime.now())精确控制触发时机
真正麻烦的不是 scheduler 启不起来,而是它“静默运行”——没有报错,但悄悄改了状态、发了请求、占了端口。测试中任何非确定性行为,第一反应该怀疑是不是 scheduler 没被停干净,或者 job 被重复添加了两次。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











