Pertanyaan Bagaimana cara menguji output fungsi grafis?


Saya bertanya-tanya bagaimana cara menguji fungsi yang menghasilkan grafik. Saya punya yang sederhana merencanakan fungsi img:

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

Dalam paket saya, saya ingin membuat tes unit untuk fungsi ini menggunakan testthat. Karena plot dan teman-temannya di grafik dasar baru kembali NULL sederhana expect_identical tidak bekerja:

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)

Pertama saya berpikir tentang merencanakan ke dalam file dan membandingkan checksum md5 untuk memastikan bahwa output dari fungsi adalah sama:

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)

Itu berfungsi dengan baik di Linux tetapi tidak di Windows. Heran md5plot(plot(1:10)) menghasilkan md5sum baru di setiap panggilan. Selain masalah ini saya perlu membuat banyak file sementara.

Berikutnya saya gunakan recordPlot (pertama-tama membuat perangkat null, panggil plotnya berfungsi dan merekam outputnya). Ini berfungsi seperti yang diharapkan:

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)

Apakah ada yang tahu cara yang lebih baik untuk menguji output fungsi grafis?

EDIT: mengenai poin @josilber bertanya dalam komentarnya.

Selagi recordPlot pendekatan bekerja dengan baik Anda harus menulis ulang seluruh fungsi merencanakan dalam tes unit. Itu menjadi rumit untuk fungsi perencanaan kompleks. Akan menyenangkan untuk memiliki pendekatan yang memungkinkan untuk menyimpan file (*.RData atau *.pdf, ...) yang berisi gambar melawan Anda dapat membandingkannya dalam pengujian di masa mendatang. Itu md5sum pendekatan tidak berfungsi karena md5sums berbeda pada platform yang berbeda. Melalui recordPlot Anda bisa membuat *.RData file tetapi Anda tidak bisa mengandalkan formatnya (dari recordPlot halaman manual):

Format plot yang direkam dapat berubah di antara versi R.        Plot rekaman bisa tidak digunakan sebagai format penyimpanan permanen untuk        R plot.

Mungkin ada kemungkinan untuk menyimpan file gambar (*.png, *.bmp, dll), impor dan bandingkan dengan pixel ...

EDIT2: Kode berikut mengilustrasikan pendekatan file referensi yang diinginkan menggunakan svg sebagai output. Pertama fungsi pembantu yang dibutuhkan:

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

Tes akan menjadi:

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)

Sayangnya ini tidak bekerja di berbagai platform. Urutan (dan nama) dari elemen svg benar-benar berbeda di Linux dan Windows.

Masalah serupa terjadi png, jpeg dan recordPlot. File yang dihasilkan berbeda di semua platform.

Saat ini satu-satunya solusi yang berfungsi adalah recPlot pendekatan di atas. Tetapi karena itu Saya perlu menulis ulang seluruh fungsi perencanaan dalam pengujian unit saya.


P.S .: Saya bingung tentang md5sums yang berbeda di Windows. Sepertinya mereka bergantung pada waktu pembuatan file sementara:

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

32
2018-05-14 20:31


asal


Jawaban:


Solusi Mangga telah menerbitkan paket open source, visualTest, yang melakukan pencocokan fuzzy dari plot, untuk mengatasi kasus penggunaan ini.

Paketnya aktif github, jadi instal menggunakan:

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

Kemudian gunakan fungsi getFingerprint() untuk mengekstrak sidik jari untuk setiap plot, dan bandingkan menggunakan fungsi isSimilar(), menetapkan ambang batas yang sesuai.

Pertama, buat beberapa plot di file:

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

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

Sidik jari adalah vektor numerik:

> 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

Bandingkan menggunakan isSimilar():

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

Anda dapat membaca lebih lanjut tentang paket di http://www.mango-solutions.com/wp/products-services/r-services/r-packages/visualtest/


13
2018-05-17 11:51



Perlu dicatat bahwa vdiffr paket juga mendukung membandingkan plot. Sebuah fitur yang bagus adalah ia terintegrasi dengan paket testthat - itu benar-benar digunakan untuk pengujian di ggplot2 - dan itu memiliki add-in untuk RStudio untuk membantu mengelola testsuite Anda.


2
2017-07-08 21:02