如何使用 testthat 以未知顺序测试多个警告?

How to test for multiple warnings in an unknown order using testthat?

我想测试一个函数是否会生成多个警告(4 个或更多),而警告的顺序可能会有所不同。我对此的最佳尝试是基于前瞻性 RegExp 匹配。简化为只有 2 个警告,我知道我的 RegExp 在单个字符串输出上工作,因为以下两个都是真的:

grepl("(?s)(?=.*2)(?=.*1)", "* warn 1.\n* warn 2.", perl=TRUE)
grepl("(?s)(?=.*2)(?=.*1)", "* warn 2.\n* warn 1.", perl=TRUE)

但是,当使用 testhat::expect_warning

测试多个警告时,这不起作用
# The function generating warnings:
foo <- function() { warning("warn 1."); warning("warn 2.") }
foo()
Warning messages:
1: In foo() : warn 1.
2: In foo() : warn 2.

# Testing it
expect_warning( foo(), "(?s)(?=.*1)(?=.*2)", perl=TRUE)

Error: foo() does not match '(?s)(?=.*1)(?=.*2)'. Actual values:
* warn 1.
* warn 2.

我怀疑这是因为 expect_warning 的内部正在做一些事情,比如针对每个警告分别测试给定的正则表达式——为什么 expect_warning( ... all=TRUE ) 参数可能有意义。

不幸的是,我不能像 "1 | 2" 这样的正则表达式使用它;如果只给出一个警告,则成功。

我还想避免 运行 多次使用该函数并每次都测试不同的警告。测试真正的功能需要大量的设置和拆卸代码。它与文件系统有很大的交互,因为它是我正在测试的文件系统警告,所以我不能嘲笑它。此外,我想在多种情况下测试警告,每种情况都需要不同的设置和拆卸代码,所以这很快就会使我的测试膨胀。

关于如何一次简单地测试多个警告有什么建议吗?

您也许可以使用 [12],例如:

expect_warning( foo(),'(?s)(?=.*[12])' , all=T, perl=TRUE)

所以我拼凑了一个解决方案,其中涉及使用我自己的警告捕获循环并将警告检查为字符串。不是 "all at once" 解决方案,但在复杂的函数调用情况下它至少是紧凑的。我的代码适用于示例函数 foo() 如下:

gotWarnings= character(0)
withCallingHandlers({
   got <- foo()
   }, warning= function(e) {
      # Push warning onto vector in parent frame.
      gotWarnings <<- c(gotWarnings, conditionMessage(e))
      invokeRestart("muffleWarning")
})

# Ensure no unexpected warnings, 
expect_equal(length(gotWarnings), 2)

# Test that each warning I want is there
expect_true( any( grepl( "warn 1\.", gotWarnings )))
expect_true( any( grepl( "warn 2\.", gotWarnings )))

弄清楚如何在收到警告后继续处理是困难的部分; tryCatch 捕捉到第一个警告后退出。 Here and here 帮我解决了这个问题。也许这可以转换成一个 expect_all_warnings 测试,它需要一个 vector 的 RegEx 来匹配,也许 all= TRUE 意味着没有警告可以不匹配。我会推迟接受这个作为答案,以防万一有人发布了更好的答案,或者至少是一个很好地打包了这样的解决方案的答案。

要捕获警告并分析它们 "by hand",您还可以使用 testthat::capture_warnings:

# The function generating warnings:
foo <- function() { warning("warn 1."); warning("warn 2.") }

w <- capture_warnings(foo())
expect_match(w, ".*1", all = FALSE)
expect_match(w, ".*2", all = FALSE)
expect_match(w, ".*3", all = FALSE)

(最后一行引发错误。)