2015-05-14 14 views
31

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 
+0

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

risposta

12

Mango Solutions hanno pubblicato un pacchetto open source, visualTest, che fa la corrispondenza fuzzy di trame, di affrontare questo caso d'uso.

Il pacchetto è in github, in modo da installare utilizzando:

devtools::install_github("MangoTheCat/visualTest") 
library(visualTest) 

quindi utilizzare la funzione getFingerprint() per estrarre un'impronta digitale per ogni trama, e confrontare utilizzando la funzione di isSimilar(), specificando una soglia adeguata.

In primo luogo, creare alcuni appezzamenti in archivio:

png(filename = "test1.png") 
img() 
dev.off() 

png(filename = "test2.png") 
plot(1:11, col="red") 
dev.off() 

L'impronta digitale è un vettore numerico:

> 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 

Confronta utilizzando isSimilar():

> isSimilar(file = "test2.png", 
+   fingerprint = getFingerprint(file = "test1.png"), 
+   threshold = 0.1 
+) 
[1] FALSE 

Puoi leggi di più sul pacchetto a http://www.mango-solutions.com/wp/products-services/r-services/r-packages/visualtest/

+0

Ottimo! Questo è esattamente quello che stavo cercando! Sfortunatamente il pacchetto non è ancora su CRAN ma per la maggior parte dei miei casi d'uso va bene. – sgibb

0

Vale la pena notare che il pacchetto vdiffr supporta anche il confronto dei grafici. Una buona caratteristica è che si integra con il pacchetto testthat - è effettivamente utilizzato per i test in ggplot2 - e ha un add-in per RStudio per aiutare a gestire la tua suite di test.

Problemi correlati