2015-05-14 3 views
31

Мне интересно, как тестировать функции, которые производят графику. У меня есть простая функция построения графиков img:Как проверить графический вывод функций?

img <- function() { 
    plot(1:10) 
} 

В моем пакете я бы создать модульный тест для этой функции с помощью testthat. Поскольку plot и его друзья в базовой графики просто вернуть NULL простой expect_identical не работает:

library("testthat") 

## example for a successful test 
expect_identical(plot(1:10), img()) ## equal (as expected) 

## example for a test failure 
expect_identical(plot(1:10, col="red"), img()) ## DOES NOT FAIL! 
# (because both return NULL) 

Сначала я подумал о заговоре в файл и сравнить контрольные суммы MD5 для убедитесь, что выход из функции:

md5plot <- function(expr) { 
    file <- tempfile(fileext=".pdf") 
    on.exit(unlink(file)) 
    pdf(file) 
    expr 
    dev.off() 
    unname(tools::md5sum(file)) 
} 

## example for a successful test 
expect_identical(md5plot(img()), 
       md5plot(plot(1:10))) ## equal (as expected) 

## example for a test failure 
expect_identical(md5plot(img()), 
       md5plot(plot(1:10, col="red"))) ## not equal (as expected) 

Это хорошо работает в Linux, но не в Windows. Удивительно md5plot(plot(1:10)) приводит к появлению нового md5sum при каждом вызове. Помимо этой проблемы мне нужно создать много временных файлов.

Далее я использовал recordPlot (сначала создав нулевое устройство, вызовите функцию и запишите ее выход). Это работает так, как ожидалось:

recPlot <- function(expr) { 
    pdf(NULL) 
    on.exit(dev.off()) 
    dev.control(displaylist="enable") 
    expr 
    recordPlot() 
} 

## example for a successful test 
expect_identical(recPlot(plot(1:10)), 
       recPlot(img())) ## equal (as expected) 

## example for a test failure 
expect_identical(recPlot(plot(1:10, col="red")), 
       recPlot(img())) ## not equal (as expected) 

Кто-нибудь знает, лучший способ проверить графический выход функций?

EDIT: относительно пунктов @josilber запрашивает в его комментариях.

В то время как подход recordPlot работает хорошо, вы должны переписать всю функцию построения графика в модульном тесте. Это усложняется для сложных функций построения. Было бы неплохо иметь подход, который позволяет хранить файл (*.RData или *.pdf, ...), который содержит изображение против вас, которое можно сравнить в будущих тестах. Подход md5sum не работает, потому что md5sums отличаются на разных платформах. Через recordPlot вы можете создать *.RData файл, но вы не можете полагаться на его формат (от страницы руководства по recordPlot):

Формат записанных участков может изменяться между версиями R. Записанные земельные участки не использовать как постоянный формат хранения для R участков.

Может быть, это было бы возможно сохранить файл изображения (*.png, *.bmp и т.д.), импортировать его и сравнить его пиксель за пикселем ...

EDIT2: Следующий код иллюстрирует нужную ссылку файловый подход с использованием svg в качестве вывода. Первые необходимые вспомогательные функции:

## plot to svg and return file contant as character 
plot_image <- function(expr) { 
    file <- tempfile(fileext=".svg") 
    on.exit(unlink(file)) 
    svg(file) 
    expr 
    dev.off() 
    readLines(file) 
} 

## the IDs differ at each `svg` call, that's why we simple remove them 
ignore_svg_id <- function(lines) { 
    gsub(pattern = "(xlink:href|id)=\"#?([a-z0-9]+)-?(?<![0-9])[0-9]+\"", 
     replacement = "\\1=\"\\2\"", x = lines, perl = TRUE) 
} 

## compare svg character vs reference 
expect_image_equal <- function(object, expected, ...) { 
    stopifnot(is.character(expected) && file.exists(expected)) 
    expect_equal(ignore_svg_id(plot_image(object)), 
       ignore_svg_id(readLines(expected)), ...) 
} 

## create reference image 
create_reference_image <- function(expr, file) { 
    svg(file) 
    expr 
    dev.off() 
} 

тест будет:

create_reference_image(img(), "reference.svg") 

## create tests 
library("testthat") 

expect_image_equal(img(), "reference.svg") ## equal (as expected) 
expect_image_equal(plot(1:10, col="red"), "reference.svg") ## not equal (as expected) 

К сожалению, это не работает на различных платформах.Порядок (и имена) элементов svg полностью отличается от Linux и Windows.

Аналогичные проблемы существуют для png, jpeg и recordPlot. Полученные файлы отличаются на всех платформах.

В настоящее время единственным рабочим решением является подход recPlot. Но поэтому Мне нужно переписать все функции построения в моих модульных тестах.


P.S .: Я полностью смущен о разных md5sums на Windows. Кажется, что они в зависимости от времени создания временных файлов:

# on Windows 
table(sapply(1:100, function(x)md5plot(plot(1:10)))) 
#4693c8bcf6b6cb78ce1fc7ca41831353 51e8845fead596c86a3f0ca36495eacb 
#        40        60 
+0

Кажется, что ваше решение 'recordPlot' хорошо работает для вашего случая использования, но затем вы спрашиваете в конце вопроса, знает ли кто-нибудь лучший способ тестирования. Не могли бы вы рассказать о том, что вы ищете, а почему ваш текущий подход с 'recordPlot' недостаточен? – josliber

ответ

12

Манго Solutions опубликовала пакет с открытым исходным кодом, visualTest, что делает нечеткое соответствие участков, для решения подобной ситуации.

Пакет на github, поэтому установить с помощью:

devtools::install_github("MangoTheCat/visualTest") 
library(visualTest) 

Затем используйте функцию getFingerprint() извлечь отпечаток пальца для каждого участка, и сравните с помощью функции isSimilar(), указав соответствующий порог.

Во-первых, создать некоторые участки на файл:

png(filename = "test1.png") 
img() 
dev.off() 

png(filename = "test2.png") 
plot(1:11, col="red") 
dev.off() 

Палец печати является числовой вектор:

> getFingerprint(file = "test1.png") 
[1] 4 7 4 4 10 4 7 7 4 7 7 4 7 4 5 9 4 7 7 5 6 7 4 7 4 4 10 
[28] 4 7 7 4 7 7 4 7 4 3 7 4 4 3 4 4 5 5 4 7 4 7 4 7 7 7 4 
[55] 7 7 4 7 4 7 5 6 7 7 4 8 6 4 7 4 7 4 7 7 7 4 4 10 4 7 4 

> getFingerprint(file = "test2.png") 
[1] 7 7 4 4 17 4 7 4 7 4 7 7 4 5 9 4 7 7 5 6 7 4 7 7 11 4 7 
[28] 7 5 6 7 4 7 4 14 4 3 4 7 11 7 4 7 5 6 7 7 4 7 11 7 4 7 5 
[55] 6 7 7 4 8 6 4 7 7 4 4 7 7 4 10 11 4 7 7 

Сравнить с помощью isSimilar():

> isSimilar(file = "test2.png", 
+   fingerprint = getFingerprint(file = "test1.png"), 
+   threshold = 0.1 
+) 
[1] FALSE 

Вы можете подробнее о пакете на http://www.mango-solutions.com/wp/products-services/r-services/r-packages/visualtest/

+0

Отлично! Это то, что я искал! К сожалению, пакет еще не включен в CRAN, но для большинства моих случаев использования это нормально. – sgibb

0

Стоит отметить, что пакет vdiffr также поддерживает сравнение графиков. Хорошей особенностью является то, что он интегрируется с пакетом testthat - он фактически используется для тестирования в ggplot2 - и у него есть надстройка для RStudio, которая поможет управлять вашим testuite.

Смежные вопросы