r - How to test graphical output of functions? -
i wondering how test functions produce graphics. have simple plotting function img
:
img <- function() { plot(1:10) }
in package create unit test function using testthat
. because plot
, friends in base graphics return null
simple expect_identical
not working:
library("testthat") ## example successful test expect_identical(plot(1:10), img()) ## equal (as expected) ## example test failure expect_identical(plot(1:10, col="red"), img()) ## not fail! # (because both return null)
first thought plotting file , compare md5 checksums ensure output of functions equal:
md5plot <- function(expr) { file <- tempfile(fileext=".pdf") on.exit(unlink(file)) pdf(file) expr dev.off() unname(tools::md5sum(file)) } ## example successful test expect_identical(md5plot(img()), md5plot(plot(1:10))) ## equal (as expected) ## example test failure expect_identical(md5plot(img()), md5plot(plot(1:10, col="red"))) ## not equal (as expected)
that works on linux not on windows. surprisingly md5plot(plot(1:10))
results in new md5sum @ each call. aside problem need create lot of temporary files.
next used recordplot
(first creating null-device, call plotting function , record output). works expected:
recplot <- function(expr) { pdf(null) on.exit(dev.off()) dev.control(displaylist="enable") expr recordplot() } ## example successful test expect_identical(recplot(plot(1:10)), recplot(img())) ## equal (as expected) ## example test failure expect_identical(recplot(plot(1:10, col="red")), recplot(img())) ## not equal (as expected)
does know better way test graphical output of functions?
edit: regarding points @josilber asks in comments.
while recordplot
approach works have rewrite whole plotting function in unit test. becomes complicated complex plotting functions. nice have approach allows store file (*.rdata
or *.pdf
, ...) contains image against compare in future tests. md5sum
approach isn't working because md5sums differ on different platforms. via recordplot
create *.rdata
file not rely on format (from recordplot
manual page):
the format of recorded plots may change between r versions. recorded plots can not used permanent storage format r plots.
maybe possible store image file (*.png
, *.bmp
, etc), import , compare pixel pixel ...
edit2: following code illustrate desired reference file approach using svg output. first needed helper functions:
## plot svg , return file contant character plot_image <- function(expr) { file <- tempfile(fileext=".svg") on.exit(unlink(file)) svg(file) expr dev.off() readlines(file) } ## ids differ @ each `svg` call, that's why 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() }
a test be:
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)
sadly not working across different platforms. order (and names) of svg elements differs on linux , windows.
similar problems exist png
, jpeg
, recordplot
. resulting files differ on platforms.
currently working solution recplot
approach above. therefore need rewrite whole plotting functions in unit tests.
p.s.: completley confused different md5sums on windows. seems depending on creation time of temporary files:
# on windows table(sapply(1:100, function(x)md5plot(plot(1:10)))) #4693c8bcf6b6cb78ce1fc7ca41831353 51e8845fead596c86a3f0ca36495eacb # 40 60
mango solutions have published open source package, visualtest
, fuzzy matching of plots, address use case.
the package on github, install using:
devtools::install_github("mangothecat/visualtest") library(visualtest)
then use function getfingerprint()
extract finger print each plot, , compare using function issimilar()
, specifying suitable threshold.
first, create plots on file:
png(filename = "test1.png") img() dev.off() png(filename = "test2.png") plot(1:11, col="red") dev.off()
the finger print numeric vector:
> 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
compare using issimilar()
:
> issimilar(file = "test2.png", + fingerprint = getfingerprint(file = "test1.png"), + threshold = 0.1 + ) [1] false
you can read more package @ http://www.mango-solutions.com/wp/products-services/r-services/r-packages/visualtest/
Comments
Post a Comment