pytest-mpl 是用于验证 matplotlib 图表逻辑一致性的 pytest 插件,通过序列化 figure 结构生成可版本控制的基准文件,适用于单元测试、团队协作和 ci 中防止图表逻辑变更,不适用于外观细节或动态图验证。

pytest-mpl 是什么,什么时候该用它?
pytest-mpl 是一个 pytest 插件,专为验证 Matplotlib 图表输出是否符合预期而设计。它不比对像素,而是序列化 figure 的底层结构(如 axes、artists、line data 等),生成可版本控制的 .png 或 .json 基准文件(baseline),后续运行时自动比对当前图与基准是否一致。
它适合以下场景:
- 单元测试中需要确保绘图函数每次生成“逻辑相同”的图(比如坐标轴范围、标签、线条数量、颜色映射等不变)
- 团队协作中防止图表意外变更(比如某次重构改了
plt.title()但没人注意到) - CI 中拦截图表回归(CI 环境需配置 headless backend,如
Agg)
不适合:
- 验证渲染外观细节(抗锯齿、字体微偏移、dpi 渲染差异)——这些属于视觉比对范畴,应交给
pytest-plt或专用图像 diff 工具 - 动态图(如动画、交互式 widget)——
pytest-mpl只处理静态Figure对象
如何写一个带 pytest-mpl 的测试函数?
核心是给测试函数加 @pytest.mark.mpl_image_compare 装饰器,并确保函数返回一个 Figure 实例。
import pytest
import matplotlib.pyplot as plt
<p>def test_simple_plot():
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 2])
ax.set_title("Test Plot")
return fig</p>
然后加上装饰器:
import pytest
import matplotlib.pyplot as plt
<p>@pytest.mark.mpl_image_compare
def test_simple_plot():
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 2])
ax.set_title("Test Plot")
return fig</p>
关键点:
- 函数必须返回
Figure对象(不能是ax或None) - 第一次运行时会生成 baseline 文件(默认存于
tests/baseline_images/下对应模块路径) - 后续运行失败时,会在测试目录生成
result.png和diff.png(需手动检查)
常见报错和 baseline 管理陷阱
ValueError: No baseline image found:说明没生成过 baseline,或路径不对。解决方法:
- 确保测试文件路径与
tests/目录结构匹配(例如tests/test_plots.py→ baseline 存在tests/baseline_images/test_plots/test_simple_plot.png) - 首次运行加
--mpl-generate-path=.手动生成(慎用,会覆盖已有 baseline) - 不要手动编辑
.pngbaseline —— 它们是二进制,且内容含哈希校验;改图后应重新生成
Failed: Image files did not match:不是“图不一样”就一定错,可能是:
- 后端差异(本地用
Qt5Agg,CI 用Agg)→ 统一在测试开头加plt.switch_backend('Agg') - 字体路径不同 → 在
pytest.ini中指定:mpl-use-style = classic,或用@pytest.mark.mpl_image_compare(style='classic') - 坐标轴刻度因数据微小变化触发重算 → 用
ax.set_xlim()/set_ylim()显式固定范围
如何控制 baseline 生成和比对行为?
装饰器支持多个参数,最常用的是:
-
baseline_dir:显式指定 baseline 根目录(避免依赖默认路径) -
filename:强制使用指定文件名(而非函数名),方便复用同一 baseline -
tolerance:数值容差(默认 0),用于浮点坐标/颜色值比对(单位是像素,一般设 1–2 即可) -
remove_text:布尔值,设为True可忽略所有文本对象(适合测试布局逻辑,避开字体差异)
示例:
@pytest.mark.mpl_image_compare(
baseline_dir='tests/baseline_custom',
filename='plot_with_fixed_axis.png',
tolerance=1.5
)
def test_axis_stability():
fig, ax = plt.subplots()
ax.scatter([1, 2, 3], [1, 2, 3])
ax.set_xlim(0, 4)
ax.set_ylim(0, 4)
return fig
真正麻烦的不是写测试,而是 baseline 的维护节奏——每次图表逻辑变更都得人工确认 diff 并更新 baseline,否则 CI 就会挂。别跳过这步,也别批量更新。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











