2015-05-14 16 views
31

Tôi đang tự hỏi làm thế nào để kiểm tra chức năng sản xuất đồ họa. Tôi có một chức năng đơn giản âm mưu img:Làm thế nào để kiểm tra đầu ra đồ họa của các chức năng?

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

Trong gói của tôi, tôi muốn tạo một thử nghiệm đơn vị cho chức năng này sử dụng testthat. Vì plot và bạn bè của mình trong đồ họa cơ sở chỉ trả NULL một đơn giản expect_identical không đang làm việc:

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) 

Đầu tiên tôi nghĩ về âm mưu vào một tập tin và so sánh checksums md5 để đảm bảo rằng đầu ra của chức năng là như nhau:

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) 

Điều đó hoạt động tốt trên Linux nhưng không hoạt động trên Windows. Đáng ngạc nhiên md5plot(plot(1:10)) kết quả trong một md5sum mới tại mỗi cuộc gọi. Bên cạnh vấn đề này, tôi cần tạo nhiều tệp tạm thời.

Tiếp theo, tôi đã sử dụng recordPlot (trước tiên tạo thiết bị trống, hãy gọi hàm và ghi lại đầu ra của nó). Điều này hoạt động như mong đợi:

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) 

Có ai biết cách tốt hơn để kiểm tra đầu ra đồ họa của hàm không?

EDIT: liên quan đến các điểm @josilber hỏi trong nhận xét của anh ấy.

Trong khi cách tiếp cận recordPlot hoạt động tốt bạn cần phải viết lại các chức năng toàn bộ âm mưu trong các thử nghiệm đơn vị. Điều đó trở nên phức tạp đối với các chức năng âm mưu phức tạp. Nó sẽ được tốt đẹp để có một phương pháp cho phép để lưu trữ một tập tin (*.RData hoặc *.pdf, ...), trong đó có một hình ảnh chống lại bạn có thể so sánh trong các thử nghiệm trong tương lai. Cách tiếp cận md5sum không hoạt động vì md5sums khác nhau trên nền tảng khác nhau. Via recordPlot bạn có thể tạo một tập tin *.RData nhưng bạn không thể dựa vào định dạng của nó (từ recordPlot trang thủ công):

Định dạng của lô ghi có thể thay đổi giữa các phiên bản R. lô ghi có thể không được sử dụng như một định dạng lưu trữ vĩnh viễn cho lô R.

Có lẽ nó sẽ có thể để lưu trữ một tập tin hình ảnh (*.png, *.bmp, vv), nhập nó và so sánh nó từng pixel một ...

EDIT2: Đoạn mã sau minh họa cho tài liệu tham khảo mong muốn cách tiếp cận tập tin bằng cách sử dụng svg như đầu ra. Đầu tiên là chức năng helper cần thiết:

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

Một thử nghiệm sẽ là:

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) 

Thật đáng buồn này không hoạt động trên nhiều nền tảng khác nhau.Thứ tự (và tên) của các yếu tố svg hoàn toàn khác nhau trên Linux và Windows.

Sự cố tương tự tồn tại cho png, jpegrecordPlot. Các tệp kết quả khác nhau trên tất cả các nền tảng.

Hiện tại, giải pháp làm việc duy nhất là cách tiếp cận recPlot ở trên. Nhưng do đó Tôi cần phải viết lại toàn bộ các chức năng âm mưu trong các bài kiểm tra đơn vị của mình.


P.S .: Tôi hoàn toàn nhầm lẫn về các md5sums khác nhau trên Windows. Có vẻ như họ tùy thuộc vào thời gian tạo ra các tập tin tạm thời:

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

Có vẻ như giải pháp 'recordPlot' của bạn hoạt động tốt cho trường hợp sử dụng của bạn, nhưng sau đó bạn hỏi ở cuối câu hỏi nếu có ai biết cách tốt hơn để kiểm tra. Bạn có thể giải thích về những gì bạn đang tìm kiếm, hay không, tại sao cách tiếp cận hiện tại của bạn với 'recordPlot' là không đủ? – josliber

Trả lời

12

Solutions Mango đã công bố một gói phần mềm mã nguồn mở, visualTest, mà không kết hợp mờ của lô, để giải quyết trường hợp sử dụng này.

Các gói phần mềm là trên github, vì vậy cài đặt sử dụng:

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

Sau đó sử dụng chức năng getFingerprint() để trích xuất một bản in ngón tay cho từng lô, và so sánh bằng cách sử dụng chức năng isSimilar(), xác định một ngưỡng thích hợp.

Đầu tiên, tạo một số lô trong hồ sơ:

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

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

Việc in ấn ngón tay là một vector số:

> 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 

Hãy so sánh sử dụng isSimilar():

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

Bạn có thể đọc thêm về gói tại http://www.mango-solutions.com/wp/products-services/r-services/r-packages/visualtest/

+0

Tuyệt vời! Đó là excatly những gì tôi đang tìm kiếm! Thật không may là các gói phần mềm không phải là trên CRAN được nêu ra nhưng đối với hầu hết các trường hợp sử dụng của tôi là tốt. – sgibb

0

Cần lưu ý rằng gói vdiffr cũng hỗ trợ so sánh các ô. Một tính năng tốt đẹp là nó tích hợp với gói testthat - nó thực sự được sử dụng để thử nghiệm trong ggplot2 - và nó có một bổ trợ cho RStudio để giúp quản lý các testsuite của bạn.

Các vấn đề liên quan