如何测试函数的图形输出?
How to test graphical output of functions?
我想知道如何测试生成图形的函数。我有一个简单的
绘图函数 img
:
img <- function() {
plot(1:10)
}
在我的包中,我喜欢使用 testthat
为此功能创建一个单元测试。
因为 plot
和它在 base graphics 中的朋友只是 return 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
,...),其中包含您可以在未来测试中比较的图像。 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)
遗憾的是,这不适用于不同的平台。顺序(和名称)
Linux 和 Windows.
上的 svg 元素完全不同
png
、jpeg
和 recordPlot
也存在类似问题。结果文件
在所有平台上都不同。
目前唯一可行的解决方案是上面的 recPlot
方法。但因此
我需要在我的单元测试中重写整个绘图函数。
P.S.:
我对 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/
阅读有关该软件包的更多信息
值得注意的是,vdiffr 包也支持比较绘图。一个不错的功能是它与 testthat 包集成——它实际上用于在 ggplot2 中进行测试——并且它有一个用于 RStudio 的插件来帮助管理你的测试套件。
我想知道如何测试生成图形的函数。我有一个简单的
绘图函数 img
:
img <- function() {
plot(1:10)
}
在我的包中,我喜欢使用 testthat
为此功能创建一个单元测试。
因为 plot
和它在 base graphics 中的朋友只是 return 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
,...),其中包含您可以在未来测试中比较的图像。 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)
遗憾的是,这不适用于不同的平台。顺序(和名称) Linux 和 Windows.
上的 svg 元素完全不同png
、jpeg
和 recordPlot
也存在类似问题。结果文件
在所有平台上都不同。
目前唯一可行的解决方案是上面的 recPlot
方法。但因此
我需要在我的单元测试中重写整个绘图函数。
P.S.: 我对 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/
阅读有关该软件包的更多信息值得注意的是,vdiffr 包也支持比较绘图。一个不错的功能是它与 testthat 包集成——它实际上用于在 ggplot2 中进行测试——并且它有一个用于 RStudio 的插件来帮助管理你的测试套件。