测试#

Matplotlib 使用 pytest 框架。

测试位于 lib/matplotlib/tests,而针对 pytest 测试基础设施的自定义设置在 matplotlib.testing 中。

要求#

要运行测试,您需要 为开发设置 Matplotlib。特别注意 测试的额外依赖

备注

我们将假设您希望在一个开发环境中运行测试。

虽然你可以针对常规安装的 Matplotlib 版本运行测试,但这是一种远不常见的使用情况。你仍然需要 额外的依赖项 来进行测试。你还需要从仓库中获取参考图像,因为它们不随预构建的 Matplotlib 包分发。

运行测试#

在你的开发仓库的根目录下运行:

pytest

pytest 可以通过许多 命令行参数 进行配置。其中一些特别有用的参数包括:

-v--verbose

更详细

-n NUM

在 NUM 个进程上并行运行测试(需要 pytest-xdist

--capture=no-s

不要捕获标准输出

要从命令行运行单个测试,您可以提供文件路径,后面可以选择性地加上由两个冒号分隔的函数,例如,(测试不需要安装,但应安装 Matplotlib):

pytest lib/matplotlib/tests/test_simplification.py::test_clipping

如果你想通过 python -m pytestpytest 作为一个模块使用,那么你需要避免 pytest 的导入模式与 Python 的搜索路径之间的冲突:

  • 在较新的 Python 版本中,您可以使用 -P 参数 :external+python:std:option:`禁用“不安全的导入路径” <-P>`(即,停止将当前目录添加到导入路径中):

    python -P -m pytest
    
  • 在较旧的 Python 版本中,您可以启用 隔离模式 (这将阻止将当前目录添加到导入路径中,但会产生其他影响):

    python -I -m pytest
    
  • 在任何 Python 环境中,将 pytest导入模式 设置为旧的 prepend 模式(但请注意,这将破坏 pytest 的断言重写):

    python -m pytest --import-mode prepend
    

查看图像测试输出#

基于 图像 测试的输出存储在 result_images 目录中。这些图像可以使用 visualize_tests 工具编译成一个包含数百张图像的HTML页面:

python tools/visualize_tests.py

图像测试失败也可以使用 triage_tests 工具进行分析:

python tools/triage_tests.py

分类工具允许你接受或拒绝测试失败,并将新图像复制到存储基线测试图像的文件夹中。分类工具要求 QT 已安装。

编写一个简单的测试#

Matplotlib 的许多元素可以使用标准测试进行测试。例如,以下是来自 matplotlib/tests/test_basic.py 的测试:

def test_simple():
    """
    very simple example test
    """
    assert 1 + 1 == 2

Pytest 通过搜索文件名以 "test_" 开头的文件,然后在这些文件中查找以 "test" 开头的函数或以 "Test" 开头的类来确定哪些函数是测试。

一些测试有内部副作用,需要在执行后进行清理(例如创建的图形或修改的 rcParams)。pytest 夹具 matplotlib.testing.conftest.mpl_test_settings 将自动清理这些副作用;无需进一步操作。

测试中的随机数据#

随机数据是生成示例数据的非常方便的方法,然而随机性在测试中是有问题的(因为测试必须是确定性的!)。为了解决这个问题,在每个测试中设置种子。对于numpy的默认随机数生成器,使用:

import numpy as np
rng = np.random.default_rng(19680801)

然后在生成随机数时使用 rng

种子是 John Hunter 的 生日。

编写图像比较测试#

编写基于图像的测试仅比简单测试稍难一些。主要考虑的是,您必须在 image_comparison 装饰器中指定“基线”或预期的图像。例如,此测试生成单个图像并自动测试它:

from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt

@image_comparison(baseline_images=['line_dashes'], remove_text=True,
                  extensions=['png'], style='mpl20')
def test_line_dashes():
    fig, ax = plt.subplots()
    ax.plot(range(10), linestyle=(0, (3, 3)), lw=5)

第一次运行此测试时,将没有基准图像进行比较,因此测试将失败。将输出图像(在本例中为 result_images/test_lines/test_line_dashes.png)复制到源目录中 baseline_images 树的正确子目录(在本例中为 lib/matplotlib/tests/baseline_images/test_lines)。将此新文件置于源代码修订控制下(使用 git add)。重新运行测试时,它们现在应该通过。

建议新测试使用 style='mpl20' ,因为这会生成较小的图形并反映 Matplotlib 默认绘图的较新外观。此外,如果文本(标签、刻度标签等)不是测试的真正部分,请使用 remove_text=True ,因为这将生成较小的图形并减少不同平台上字体不匹配的可能问题。

比较两种创建图像的方法#

基准图像在 Matplotlib 仓库中占用大量空间。图像比较测试的另一种方法是使用 check_figures_equal 装饰器,该装饰器应用于一个函数,该函数接受两个 Figure 参数,并使用两种不同的方法(被测试的方法和基准方法)在图上绘制相同的图像。装饰器将负责设置图形,然后收集绘制的结果并进行比较。

例如,此测试比较了两种不同的方法来绘制相同的圆:使用 matplotlib.patches.Circle 补丁绘制圆与使用圆的参数方程绘制圆

from matplotlib.testing.decorators import check_figures_equal
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np

@check_figures_equal()
def test_parametric_circle_plot(fig_test, fig_ref):

    xo, yo= (.5, .5)
    radius = 0.4

    ax_test = fig_test.subplots()
    theta = np.linspace(0, 2 * np.pi, 150)
    l, = ax_test.plot(xo + (radius * np.cos(theta)),
                      yo + (radius * np.sin(theta)), c='r')

    ax_ref = fig_ref.subplots()
    red_circle_ref = mpatches.Circle((xo, yo), radius, ec='r', fc='none',
                                     lw=l.get_linewidth())
    ax_ref.add_artist(red_circle_ref)

    for ax in [ax_ref, ax_test]:
        ax.set(xlim=(0,1), ylim=(0,1), aspect='equal')

两个比较装饰器都有一个容差参数 tol,用于指定两幅图像之间颜色值差异的容差,其中255是最大差异。如果平均像素差异大于此值,则测试失败。

有关其使用的更多信息,请参阅 image_comparisoncheck_figures_equal 的文档。

在 matplotlib.tests 中创建一个新模块#

我们尝试将测试按其主要测试的模块进行分类。例如,与 mathtext.py 模块相关的测试位于 test_mathtext.py 中。

使用 GitHub Actions 进行 CI#

GitHub Actions 是一个托管在云端的CI系统。

GitHub Actions 配置为接收 GitHub 仓库新提交的通知,并在检测到这些新提交时运行构建或测试。它会在 .github/workflows 中查找 YAML 文件以了解如何测试项目。

GitHub Actions 已经为 主 Matplotlib GitHub 仓库 启用 -- 例如,请参见 测试工作流程

GitHub Actions 应该会在 YAML 工作流文件放入你的个人 Matplotlib 分支后自动启用。通常不需要查看这些工作流,因为任何提交到主 Matplotlib 仓库的拉取请求都会被测试。测试工作流在分支仓库中会被跳过,但你可以从 GitHub 网页界面 手动触发运行。

你可以在 your_GitHub_user_name/matplotlib 查看 GitHub Actions 的结果 -- 这里有一个 例子

使用 tox#

Tox 是一个用于在多个 Python 环境中运行测试的工具,包括多个版本的 Python(例如,3.10、3.11)甚至不同的 Python 实现(例如,CPython、PyPy、Jython 等),只要这些版本在系统的 $PATH 中可用(考虑使用系统的包管理器,例如 apt-get、yum 或 Homebrew 来安装它们)。

tox 使得在提交拉取请求之前,轻松确定你的工作副本是否引入了任何回归问题。以下是使用方法:

$ pip install tox
$ tox

你也可以在部分环境中运行 tox:

$ tox -e py310,py311

Tox 按顺序处理所有内容,因此测试多个环境可能需要很长时间。为了加快速度,您可以尝试使用一个名为 detox 的新并行化版本的 tox。试试这个:

$ pip install -U -i http://pypi.testrun.org detox
$ detox

Tox 使用一个名为 tox.ini 的文件进行配置。如果你想添加新的测试环境(例如 py33)或调整依赖项或测试运行方式,你可能需要编辑此文件。有关 tox.ini 文件的更多信息,请参阅 Tox 配置规范

构建旧版本的 Matplotlib#

当运行 git bisect 以查看哪个提交引入了某个错误时,您可能(很少)需要构建非常旧版本的 Matplotlib。需要考虑以下约束:

  • Matplotlib 1.3(或更早版本)需要 numpy 1.8(或更早版本)。

测试 Matplotlib 的发布版本#

在已发布的版本(例如 PyPI 包或 conda 包)上运行测试也需要额外的设置。

备注

对于终端用户来说,通常不需要在发布的 Matplotlib 版本上运行测试。官方发布版本在发布前已经过测试。

安装额外的依赖项#

安装 测试所需的额外依赖项

获取参考图像#

许多测试将绘图结果与参考图像进行比较。参考图像不是常规打包版本(pip 轮或 conda 包)的一部分。如果你想使用参考图像运行测试,你需要获取与你想要测试的 Matplotlib 版本相匹配的参考图像。

为此,可以从 PyPI 下载匹配的源代码分发包 matplotlib-X.Y.Z.tar.gz,或者克隆 git 仓库并执行 git checkout vX.Y.Z。将文件夹 lib/matplotlib/tests/baseline_images 复制到你的 matplotlib 安装目录下的 matplotlib/tests 文件夹中进行测试。正确的目标文件夹可以通过以下方式找到:

python -c "import matplotlib.tests; print(matplotlib.tests.__file__.rsplit('/', 1)[0])"

对于测试 mpl_toolkits ,需要类似地复制 lib/mpl_toolkits/*/tests/baseline_images

运行测试#

要在您安装的 Matplotlib 版本上运行所有测试:

pytest --pyargs matplotlib.tests

测试发现范围可以缩小到单个测试模块,甚至单个函数:

pytest --pyargs matplotlib.tests.test_simplification.py::test_clipping