r - 如何测试函数的图形输出?

标签 r testing graphics plot testthat

我想知道如何测试生成图形的函数。我有一个简单的 绘图函数 img:

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

在我的包中,我喜欢使用 testthat 为此功能创建一个单元测试。 因为 plot 及其在 base graphics 中的 friend 只返回 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)

有人知道测试函数图形输出的更好方法吗?

编辑:关于@josilber 在他的评论中提出的要点。

虽然 recordPlot 方法效果很好,但您必须在单元测试中重写整个绘图函数。对于复杂的绘图函数,这变得很复杂。最好有一种方法可以存储一个文件(*.RData*.pdf, ...),其中包含您可以比较的图像 future 的测试。 md5sum 方法不起作用,因为 md5sums 在不同平台上不同。通过 recordPlot,您可以创建一个 *.RData 文件,但您不能依赖其格式(来自 recordPlot 手册页):

The format of recorded plots may change between R versions. Recorded plots can not be used as a permanent storage format for R plots.

也许可以存储图像文件(*.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 上完全不同。

pngjpegrecordPlot 也存在类似问题。结果文件 在所有平台上都不同。

目前唯一可行的解​​决方案是上面的recPlot 方法。但因此 我需要在我的单元测试中重写整个绘图函数。


附言: 我对 Windows 上的不同 md5sums 感到很困惑。似乎它们取决于临时文件的创建时间:

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

最佳答案

Mango 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/ 阅读有关该软件包的更多信息

关于r - 如何测试函数的图形输出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30246789/

相关文章:

r - 取消列出两列,同时保留列中的值对

ios - 单元目标测试构建失败 xcode objective-c

python - Tensorflow:Tensordot 可重现的结果

opengl - 以编程方式确定硬件是否支持 OpenGL 函数

r - R中的h2o实现

RMarkdown 引导导航栏

r - Mongolite - 明显太大,16mb 上限

javascript - 如何在预请求脚本中更改 Postman 环境?

javascript - "saturation"globalCompositeOperation 不改变透明度?

Java : VolatileImage slower than BufferedImage