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

pie*_*eel 35 python matplotlib python-unittest

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

mwa*_*kom 31

根据我的经验,图像对比测试最终会带来比它们更值得的麻烦.如果您希望在多个系统(如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)
Run Code Online (Sandbox Code Playgroud)

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

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))
Run Code Online (Sandbox Code Playgroud)

或者,等效地,

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))
Run Code Online (Sandbox Code Playgroud)

  • 使用您的解决方案,您需要返回绘图的结果.有没有办法在不必退货的情况下做同样的事情?通过"捕捉"无花果?我的情节功能现在"返回"无效,如果这是我可以改变的唯一方式,但我不愿意. (4认同)

Tom*_*iak 14

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

import matplotlib.pyplot as plt

def plot_data(x, y, title):
    plt.figure()
    plt.title(title)
    plt.plot(x, y)
    plt.show()
Run Code Online (Sandbox Code Playgroud)

为了在您的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
Run Code Online (Sandbox Code Playgroud)

这将检查是否title使用参数调用方法my title以及该figure方法是否plot_dataplt对象内部调用。

更详细的解释:

所述@mock.patch("module.plt")装饰“贴剂”的plt模块内的进口module.py并注入它作为一个mock对象(mock_plt)到test_module作为参数。这个模拟对象(作为 传递mock_plt)现在可以在我们的测试中使用来记录plot_data(我们正在测试的函数)所做plt的一切 - 那是因为所有对pltby的调用plot_data现在都将在我们的模拟对象上进行。

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


ely*_*ase 13

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')
Run Code Online (Sandbox Code Playgroud)

来自文档:

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

  • 我试过你的方式,我得到了一个错误.在matplotlib.testing.decorators中有一个"import matplotlib.tests",它产生一个ImportError:没有名为tests的模块.我做了一些研究,确实我的matplolib文件中没有测试模块,文档中说的很少.有人知道如何解决它吗? (2认同)
  • 看起来这种方式已被弃用“MatplotlibDeprecationWarning:ImageComparisonTest 类在 Matplotlib 3.0 中已弃用,并将在 3.2 中删除。@image_comparison(baseline_images=['spines_axes_positions'])” (2认同)