如何用testthat测试两个plotly对象是否等价?
问题场景
我想用testthat检查两个plotly对象是否等价,测试代码如下:
library(plotly) p1 <- plot_ly(economics, x = ~pop) p2 <- plot_ly(economics, x = ~pop)
当调用testthat::expect_equivalent(p1, p2)时,出现以下报错:
Error:
p1not equivalent top2.
Component “x”: Component “visdat”: Component 1: Component “id”: 1 string mismatch
Component “x”: Component “visdat”: Component 1: Component “p”: Component “cur_data”: 1 string mismatch
Component “x”: Component “cur_data”: 1 string mismatch
我自己实现了一种 workaround:先验证核心组件的一致性,再覆盖掉含随机ID的字段后再做等价性检查:
testthat::expect_equal( object = p2$x$visdat[[1]](), expected = p1$x$visdat[[1]]() ) p2$x$visdat[1] <- p1$x$visdat[1] names(p2$x$visdat) <- names(p1$x$visdat) p2$x$cur_data <- p1$x$cur_data testthat::expect_equivalent(p1, p2)
但这个方法比较繁琐,想知道有没有更简便的方式。
简便解决方案
方案1:转换为JSON后对比
plotly提供的toJSON()函数会将对象序列化为仅包含图表核心配置和数据的JSON字符串,自动忽略内部生成的随机ID、cur_data这类非核心字段,直接对比JSON即可验证等价性:
library(testthat) library(plotly) p1 <- plot_ly(economics, x = ~pop) p2 <- plot_ly(economics, x = ~pop) expect_equal(toJSON(p1), toJSON(p2))
方案2:自定义testthat期望函数
如果需要更灵活的字段控制,可以编写一个专门处理plotly对象的自定义期望函数,自动清理掉随机生成的无关字段后再做对比:
library(testthat) library(plotly) expect_plotly_equal <- function(object, expected, ...) { # 清理plotly对象中的随机标识字段 clean_plotly <- function(p) { # 清理visdat中的随机id和cur_data if (!is.null(p$x$visdat)) { p$x$visdat <- lapply(p$x$visdat, function(vis) { vis$id <- NULL if (!is.null(vis$p)) vis$p$cur_data <- NULL vis }) } # 清理顶层的cur_data p$x$cur_data <- NULL p } # 对比清理后的对象 expect_equal(clean_plotly(object), clean_plotly(expected), ...) } # 使用示例 p1 <- plot_ly(economics, x = ~pop) p2 <- plot_ly(economics, x = ~pop) expect_plotly_equal(p1, p2)
报错原因
每次创建plotly对象时,内部会自动生成随机的id和cur_data字段,这些是plotly用于内部状态跟踪的标识,不属于图表的核心配置或数据内容,因此直接使用expect_equivalent这类严格对比函数会因这些随机值不匹配而报错。
内容的提问来源于stack exchange,提问作者Clemsang

