Подтвердить что ты не робот

Как я могу написать модульные тесты против кода, использующего matplotlib?

Я работаю над программой python (2.7), которая создает много разных фигур matplotlib (данные не являются случайными). Я готов выполнить некоторый тест (используя unittest), чтобы убедиться, что полученные цифры верны. Например, я сохраняю ожидаемую цифру (данные или изображение) в некотором месте, я запускаю свою функцию и сравниваю результат со ссылкой. Есть ли способ сделать это?

4b9b3361

Ответ 1

В моем опыте тесты на сравнение изображений в конечном итоге приносят больше проблем, чем их стоит. Это особенно важно, если вы хотите запустить непрерывную интеграцию в нескольких системах (например, 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)

Теперь ваш unit test может выглядеть как

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))

Ответ 2

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')

Из docs:

При первом запуске этого теста изображение базовой линии не будет сравните против, поэтому испытание терпит неудачу. Скопируйте выходные изображения (в этот случай result_images/test_category/spines_axes_positions. *) к правильный подкаталог дерева baseline_images в исходном каталоге (в этом случае lib/matplotlib/tests/baseline_images/test_category). При повторном тестировании они должны пройти.

Ответ 3

Вы также можете использовать unittest.mock, чтобы matplotlib.pyplot и проверить, что к нему сделаны соответствующие вызовы с соответствующими аргументами. Допустим, у вас есть plot_data(data) внутри module.py которую вы хотите протестировать и которая выглядит следующим образом:

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 module as my_module


@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 mock_plt.title.call_args_list[0][0][0] == "my title"
    assert mock_plt.figure.called

Это проверяет, вызывается ли метод title с аргументом my title и вызывается plot_data метод figure внутри plot_data.