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