应使用tracemalloc在测试函数内启停并过滤无关路径,而非pytest_runtest_makereport钩子或psutil RSS监控;CI中需禁用GC、fork插件及确保测试隔离。

pytest中用pytest_runtest_makereport钩子捕获测试前后内存快照
直接在钩子里做内存测量不可靠——因为钩子执行时机不保证在测试函数真正开始/结束的瞬间,容易漏掉初始化或清理阶段的分配。更稳的做法是把内存采样逻辑下沉到测试函数内部,再用钩子统一收集和比对。
实操建议:
- 用
tracemalloc(Python 3.4+ 内置)在setup_method或setUp里调用tracemalloc.start(),在teardown_method里调用tracemalloc.stop() - 避免在
pytest_runtest_makereport里调用tracemalloc.get_traced_memory()——此时trace可能已停,返回(0, 0) - 把统计结果存进
item._mem_stats = {...}这样的私有属性,供后续钩子读取
为什么不用psutil.Process().memory_info().rss做全程监控
它反映的是整个Python进程的物理内存占用,但测试间共享进程,前一个测试残留的对象、缓存、导入模块都会污染后一个测试的RSS值,导致误报“泄漏”。尤其在pytest -xvs跑单个测试时,RSS波动常达几MB,毫无参考价值。
适用场景仅限粗粒度排查:比如怀疑某个测试集整体吃内存,可配合--tb=no -q批量运行后看RSS趋势,但不能定位到具体函数或对象。
立即学习“Python免费学习笔记(深入)”;
常见错误现象:psutil.Process().memory_info().rss在测试前后差值为正,但tracemalloc没发现新增追踪块——说明只是OS没及时回收页,不是Python层泄漏。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
tracemalloc过滤噪音:只关注当前测试触发的新分配
默认tracemalloc会记录所有Python分配,包括第三方库初始化、pytest自身开销。必须过滤,否则top_stats(10)里全是pytest/python/importlib路径,看不出业务问题。
实操建议:
- 启动时加
tracemalloc.start(25) # 25帧栈深度足够定位到test_*.py - 调用
tracemalloc.take_snapshot()后,用filter_traces排除无关路径:snapshot.filter_traces((tracemalloc.Filter(False, "*site-packages*"), tracemalloc.Filter(False, "*pytest*"))) - 对比两个snapshot时,用
stats = snapshot1.compare_to(snapshot2, "lineno"),优先看lineno而非filename,能准确定位到哪行代码新分配了内存
CI中稳定复现泄漏的关键:禁用GC + 单测隔离
本地跑不出来的泄漏,在CI里突然爆发,大概率是GC被干扰。Python的GC在多线程/子进程/信号中断下行为不稳定,而pytest默认启用--workers或某些插件会触发fork,导致tracemalloc状态错乱。
必须做的配置:
- CI命令加
--disable-warnings --tb=short -p no:forked,禁用pytest-forked等可能fork的插件 - 测试函数开头手动
import gc; gc.disable(),结尾gc.enable(); gc.collect(),确保所有引用计数归零后再拍快照 - 每个测试用独立
tmp_path和monkeypatch,避免os.environ、全局dict、缓存类实例跨测试存活
最容易被忽略的是:tracemalloc无法追踪C扩展分配的内存(如numpy array底层malloc),如果泄漏来自这类组件,得换valgrind --tool=memcheck或python -m memory_profiler配合@profile逐行标定。

















