繁体   English   中英

如何针对使用 matplotlib 的代码编写单元测试?

[英]How can I write unit tests against code that uses matplotlib?

我正在开发一个 python (2.7) 程序,该程序生成许多不同的 matplotlib 图(数据不是随机的)。 我愿意实施一些测试(使用 unittest)以确保生成的数字是正确的。 例如,我将预期的图形(数据或图像)存储在某个位置,然后运行我的函数并将结果与​​参考进行比较。 有没有办法做到这一点 ?

根据我的经验,图像比较测试最终带来的麻烦多于其价值。 如果您想跨多个系统(如 TravisCI)运行持续集成,这些系统可能具有略微不同的字体或可用的绘图后端,则尤其如此。 即使功能完全正常工作,也需要做很多工作来保持测试通过。 此外,以这种方式进行测试需要将图像保存在您的 git 存储库中,如果您经常更改代码,这会很快导致存储库膨胀。

在我看来,更好的方法是 (1) 假设 matplotlib 将实际正确绘制图形,并且 (2) 对绘图函数返回的数据运行数值测试。 (如果您知道在哪里查看,您也可以在Axes对象中找到这些数据。)

例如,假设您要测试这样的简单函数:

import numpy as np
import matplotlib.pyplot as plt
def plot_square(x, y):
    y_squared = np.square(y)
    return plt.plot(x, y_squared)

你的单元测试可能看起来像

def test_plot_square1():
    x, y = [0, 1, 2], [0, 1, 2]
    line, = plot_square(x, y)
    x_plot, y_plot = line.get_xydata().T
    np.testing.assert_array_equal(y_plot, np.square(y))

或者,等效地,

def test_plot_square2():
    f, ax = plt.subplots()
    x, y = [0, 1, 2], [0, 1, 2]
    plot_square(x, y)
    x_plot, y_plot = ax.lines[0].get_xydata().T
    np.testing.assert_array_equal(y_plot, np.square(y))

您还可以使用unittest.mock来模拟matplotlib.pyplot并检查是否对它进行了具有适当参数的适当调用。 假设您在module.py有一个plot_data(data)函数(假设它位于package/src/ )您想要测试,它看起来像这样:

import matplotlib.pyplot as plt

def plot_data(x, y, title):
    plt.figure()
    plt.title(title)
    plt.plot(x, y)
    plt.show()

为了在您的test_module.py文件中测试此功能,您需要:

import numpy as np

from unittest import mock
import package.src.module as my_module  # Specify path to your module.py


@mock.patch("%s.my_module.plt" % __name__)
def test_module(mock_plt):
    x = np.arange(0, 5, 0.1)
    y = np.sin(x)
    my_module.plot_data(x, y, "my title")

    # Assert plt.title has been called with expected arg
    mock_plt.title.assert_called_once_with("my title")

    # Assert plt.figure got called
    assert mock_plt.figure.called

这将检查是否使用参数my title调用了title方法,以及plot_dataplt对象的plot_data内调用了figure方法。

更详细的解释:

@mock.patch("module.plt")装饰器“修补”导入到module.pyplt模块,并将其作为mock对象 ( mock_plt ) 作为参数注入到test_module中。 这个模拟对象(作为mock_plt传递)现在可以在我们的测试中使用来记录plot_data (我们正在测试的函数)对plt所做的一切 - 那是因为plot_dataplt的所有调用现在都将在我们的模拟中进行对象。

此外,除了assert_call_once_with之外,您可能还想使用其他类似的方法,例如assert_not_calledassert_called_once等。

Matplotlib 有一个测试基础设施 例如:

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

@image_comparison(baseline_images=['spines_axes_positions'])
def test_spines_axes_positions():
    # SF bug 2852168
    fig = plt.figure()
    x = np.linspace(0,2*np.pi,100)
    y = 2*np.sin(x)
    ax = fig.add_subplot(1,1,1)
    ax.set_title('centered spines')
    ax.plot(x,y)
    ax.spines['right'].set_position(('axes',0.1))
    ax.yaxis.set_ticks_position('right')
    ax.spines['top'].set_position(('axes',0.25))
    ax.xaxis.set_ticks_position('top')
    ax.spines['left'].set_color('none')
    ax.spines['bottom'].set_color('none')

文档

第一次运行此测试时,将没有可比较的基线图像,因此测试将失败。 将输出图像(在本例中为 result_images/test_category/spines_axes_positions.*)复制到源目录中的基线图像树的正确子目录(在本例中为 lib/matplotlib/tests/baseline_images/test_category)。 重新运行测试时,它们现在应该通过了。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM