pytest-memray是专为测试场景设计的内存分析工具,可自动为每个测试用例生成独立堆内存快照,精准定位跨测试内存累积问题,支持多进程、C扩展及火焰图分析,且性能开销仅15%~25%。

pytest-memray 是目前最实用的 Python 测试内存分析工具,它能精准定位哪个测试用例、哪行代码在执行时触发了内存暴涨。如果你的 pytest 在跑完几十个用例后突然报 MemoryError,或者 CI 环境里测试进程被 OOM Killer 杀掉,说明不是单个用例爆炸,而是内存持续累积——这正是 pytest-memray 的强项。
为什么 pytest-memray 比 tracemalloc 更适合测试场景
tracemalloc 需手动启停、只能抓峰值、不支持多进程测试(pytest-xdist 下失效),而 pytest-memray 自动为每个测试用例生成独立内存快照,还能合并对比、过滤调用栈、导出火焰图。更重要的是:它不依赖 Python 的引用计数,能捕获 C 扩展(如 NumPy、Pandas)的真实内存分配。
- 它底层用的是
Memray—— 一个由 Instagram 开源的、基于 eBPF 和堆栈采样的高性能内存分析器 - 默认只记录分配点(allocation),不记录释放点,所以看到的是“谁申请了内存”,不是“谁没释放”
- 对测试性能影响约 15%~25%,远低于
objgraph或全量tracemalloc
安装与基础运行命令
别用 pip install memray 单独装——必须装配套的 pytest 插件:
pip install pytest-memray
然后直接加参数运行测试:
pytest tests/ -x --memray --memray-heap --memray-file=memray-report.bin
-
--memray启用插件 -
--memray-heap记录堆内存分配(必选,否则只记栈帧) -
--memray-file指定输出二进制报告路径,后续可离线分析 - 不加
--memray-threshold时,默认只报告峰值 > 10MB 的测试用例
快速定位泄漏测试用例的三步法
假设你发现 test_large_dataframe_processing 运行后内存没回落,但单独跑它又没问题——这是典型的跨测试污染。用以下方式确认:
立即学习“Python免费学习笔记(深入)”;
- 先生成报告:
pytest tests/test_etl.py::test_large_dataframe_processing -x --memray --memray-heap --memray-file=leak.bin - 再用 CLI 查看该用例的内存增长热点:
memray flamegraph leak.bin→ 打开 HTML 火焰图,聚焦pandas.core.frame.DataFrame.__init__或numpy.ndarray.__new__调用链 - 如果火焰图里出现
__del__或__exit__未执行,大概率是 fixture 中没正确 close 文件句柄 / 数据库连接 / 缓存实例
特别注意:若用例中用了 @pytest.fixture(scope="session") 并在其中创建了大对象(如全局缓存字典、预加载模型),memray 会显示这些对象从第一个测试开始就常驻内存——这不是泄漏,是设计使然,但需人工判断是否合理。
容易被忽略的陷阱:临时文件 + mmap + 多进程
很多测试为了速度会用 tempfile.NamedTemporaryFile(delete=False) 写中间数据,再用 numpy.memmap 加载。这时 memray 不会显示文件内容本身占内存(因为 mmap 是虚拟地址映射),但会暴露 mmap.mmap 对象的创建位置。更隐蔽的问题是:
- pytest-xdist 分发测试到多个 worker 时,
memray默认只为每个 worker 生成一个报告,无法跨进程聚合 - 解决办法:加
--memray-per-test参数,强制每个测试用例都写独立报告,再用memray merge *.bin合并分析 - 若测试中调用了
subprocess.run启动子 Python 进程,memray默认不跟踪子进程——需在子进程中显式加memray run python ...
真正难查的不是“哪行代码 malloc 了”,而是“哪次测试结束后没 unmap、没 unlink、没 del cache”。这类问题不会立刻报错,但会让整个测试套件的内存基线缓慢抬升,直到 CI 节点被 OOM 杀死。


















