2015-05-14 4 views
31

Ich frage mich, wie Funktionen getestet werden, die Grafiken erzeugen. Ich habe eine einfache Plotten Funktion img:So testen Sie die grafische Ausgabe von Funktionen?

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

In meinem Paket Ich mag einen Unit-Test für diese Funktion erstellen mit testthat. Da plot und seine Freunde in Basis Grafiken nur NULL zurückgeben eine einfache expect_identical funktioniert nicht:

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) 

Zuerst dachte ich über in eine Datei Plotten und die MD5-Prüfsummen zu vergleichen sicherzustellen, dass der Ausgang des Funktionen ist gleich:

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) 

Das funktioniert gut unter Linux, aber nicht unter Windows. Überraschenderweise md5plot(plot(1:10)) ergibt bei jedem Anruf eine neue md5sum. Abgesehen von diesem Problem muss ich eine Menge temporärer Dateien erstellen.

Als nächstes habe ich (zunächst ein Null-Gerät erstellen, rufen Sie die Plotfunktion Funktion und notieren Sie die Ausgabe). Dies funktioniert wie erwartet:

Kennt jemand eine bessere Möglichkeit, die grafische Ausgabe von Funktionen zu testen?

EDIT: bezüglich der Punkte @josilber fragt in seinen Kommentaren.

Während der Ansatz gut funktioniert, müssen Sie die gesamte Plotfunktion im Komponententest neu schreiben. Das wird kompliziert für komplexe Plotfunktionen. Es wäre nett, einen Ansatz zu haben, der es erlaubt, eine Datei (*.RData oder *.pdf, ...) zu speichern, die ein Bild enthält, das Sie in zukünftigen Tests vergleichen könnten. Der Ansatz md5sum funktioniert nicht, da sich die md5sums auf verschiedenen Plattformen unterscheiden. Via recordPlot könnten Sie eine *.RData-Datei erstellen, aber sie konnte sich nicht auf das Format verlassen (von der recordPlot Handbuch Seite):

Das Format der aufgezeichneten Plots kann zwischen R-Versionen ändern. Aufgezeichnete Diagramme können nicht als permanentes Speicherformat für R-Plots verwendet werden.

Vielleicht wäre es möglich, eine Bilddatei zu speichern (*.png, *.bmp, usw.), importiert und Pixel für Pixel vergleichen ...

EDIT2: Der folgende Code veranschaulicht die gewünschte Referenz Dateiansatz mit Svg als Ausgabe. Zunächst werden die benötigten Hilfsfunktionen:

## 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() 
} 

Ein Test wäre:

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) 

Leider ist dies nicht auf verschiedenen Plattformen arbeiten.Die Reihenfolge (und die Namen) der Svg-Elemente unterscheidet sich vollständig unter Linux und Windows.

Ähnliche Probleme bestehen für png, jpeg und . Die resultierenden Dateien unterscheiden sich auf allen Plattformen.

Derzeit ist die einzige funktionierende Lösung der obige Ansatz recPlot. Aber deshalb Ich muss die gesamten Zeichenfunktionen in meinen Unit Tests neu schreiben.


S.S .: Ich bin komplett verwirrt über die verschiedenen md5sums unter Windows. Es scheint, sie auf die Erstellungszeit der temporären Dateien abhängig:

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

Es scheint, als ob Ihre 'recordPlot'-Lösung gut für Ihren Anwendungsfall funktioniert, aber Sie fragen dann am Ende der Frage, ob jemand eine bessere Testmethode kennt. Können Sie näher erläutern, wonach Sie suchen, oder warum Ihr aktueller Ansatz mit "recordPlot" nicht ausreicht? – josliber

Antwort

12

Mango-Lösungen haben eine Open-Source-Paket veröffentlicht, visualTest, die Fuzzy Matching von Plots der Fall ist, diesen Anwendungsfall zu lösen.

Das Paket ist auf github, indem so installieren:

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

Dann getFingerprint() die Funktion verwenden, um einen Fingerabdruck für jeden Plot zu extrahieren und vergleichen, um die Funktion isSimilar() Verwendung eine geeignete Schwelle angibt.

Zuerst einige Parzellen auf Datei erstellen:

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

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

Der Fingerabdruck ist ein numerischer Vektor:

> 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 

Vergleichen mit isSimilar():

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

können Sie Lesen Sie mehr über das Paket bei http://www.mango-solutions.com/wp/products-services/r-services/r-packages/visualtest/

+0

Großartig! Das ist genau das, wonach ich gesucht habe! Leider ist das Paket noch nicht auf CRAN, aber für die meisten meiner Anwendungsfälle ist das in Ordnung. – sgibb

0

Es ist erwähnenswert, dass das vdiffr Paket auch das Vergleichen von Plots unterstützt. Ein nettes Feature ist, dass es in das Testpaket integriert ist - es wird tatsächlich zum Testen in ggplot2 verwendet - und es hat ein Add-In für RStudio, um die Testsuite zu verwalten.