Мне интересно, как тестировать функции, которые производят графику. У меня есть простая функция построения графиков 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
Кажется, что ваше решение 'recordPlot' хорошо работает для вашего случая использования, но затем вы спрашиваете в конце вопроса, знает ли кто-нибудь лучший способ тестирования. Не могли бы вы рассказать о том, что вы ищете, а почему ваш текущий подход с 'recordPlot' недостаточен? – josliber