Mi chiedo come testare le funzioni che producono grafica. Ho una semplice funzione di tracciato img
:Come testare l'output grafico delle funzioni?
img <- function() {
plot(1:10)
}
Nel mio pacchetto mi piace creare uno unit test per questa funzione utilizzando testthat
. Perché plot
e dei suoi amici in grafica di base solo tornare NULL
un semplice expect_identical
non funziona:
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)
Per prima cosa ho pensato di tracciare in un file e confrontare il checksum MD5 per assicurare che l'uscita del le funzioni sono uguali:
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)
Che funziona bene su Linux ma non su Windows. Sorprendentemente md5plot(plot(1:10))
restituisce un nuovo md5sum ad ogni chiamata. A parte questo problema ho bisogno di creare molti file temporanei.
Successivamente ho utilizzato recordPlot
(prima di creare un dispositivo null, chiamare la funzione di plottaggio e registrare l'output). Funziona come previsto:
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)
Qualcuno conosce un modo migliore per testare l'output grafico delle funzioni?
MODIFICA: per quanto riguarda i punti @josilber chiede nei suoi commenti.
Mentre l'approccio recordPlot
funziona correttamente, è necessario riscrivere l'intera funzione di stampa nel test dell'unità. Ciò diventa complicato per complesse funzioni di tracciamento. Sarebbe bello avere un approccio che permetta di memorizzare un file (*.RData
o *.pdf
, ...) che contenga un'immagine che potresti confrontare nei test futuri. L'approccio md5sum
non funziona perché i md5sum differiscono su piattaforme diverse. Via recordPlot
è possibile creare un file di *.RData
ma non si poteva fare affidamento su suo formato (dalla pagina di manuale recordPlot
):
Il formato di trame registrata potrebbe cambiare tra le versioni R. I lotti registrati possono essere non da utilizzare come formato di archiviazione permanente per i grafici R .
Forse sarebbe possibile memorizzare un file immagine (*.png
, *.bmp
, ecc), importarlo e confrontarlo pixel per pixel ...
EDIT2: Il seguente codice illustrano il riferimento desiderato approccio al file usando svg come output. In primo luogo le funzioni di supporto necessarie:
## 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()
}
Un test sarebbero:
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)
Purtroppo questo non funziona su diverse piattaforme.L'ordine (e i nomi) degli elementi svg è completamente diverso su Linux e Windows.
Problemi simili esistono per png
, jpeg
e recordPlot
. I file risultanti differiscono su tutte le piattaforme.
Attualmente l'unica soluzione funzionante è l'approccio recPlot
in alto. Ma quindi Ho bisogno di riscrivere tutte le funzioni di tracciamento nei miei test di unità.
P.S .: Sono completamente confuso circa i diversi md5sum su Windows. Sembra che a seconda del tempo di creazione dei file temporanei:
# on Windows
table(sapply(1:100, function(x)md5plot(plot(1:10))))
#4693c8bcf6b6cb78ce1fc7ca41831353 51e8845fead596c86a3f0ca36495eacb
# 40 60
Sembra che la tua soluzione 'recordPlot' funzioni bene per il tuo caso d'uso, ma alla fine della domanda chiedi se qualcuno conosce un modo migliore di testare. Potresti approfondire ciò che stai cercando, ovvero perché il tuo attuale approccio con 'recordPlot' non è sufficiente? – josliber