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

9

我希望测试一个函数是否会生成多个警告(4个或更多),而警告的顺序可能会变化。我的最佳尝试是基于前瞻正则表达式匹配。为了简化,只考虑2个警告,我知道我的正则表达式适用于单个字符串输出,因为以下两个条件都成立:

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"这样的正则表达式;这种情况只有一个警告时才会成功。

我还想避免多次运行函数并测试每个警告。测试真实函数需要大量的设置和拆卸代码。它与文件系统密切相关,由于我正在测试文件系统警告,所以我无法模拟它。此外,我想在多种情况下测试警告,每种情况都需要不同的设置和拆卸代码,因此这很快使我的测试膨胀。

有没有建议如何简单地一次性测试多个警告?


似乎expect_warning没有像R中的其他函数一样的perl参数,但为什么不简单地使用模式1.*\\n.*21(.*\\n)*.*2 *(因为(?s).*和前瞻是无用的)*? - Casimir et Hippolyte
@Casimir et Hippolyte - expect_warning 接受 ...,并将参数传递给底层的 grepl 调用,因此它 使用 perl=TRUE 参数。如果没有它,包含 RegExp 的 "lookahead" 是无效的。我需要它,因为顺序是未知的,并且在我的实际应用程序中有超过 2 个。lookaheads 是一种匹配未知顺序的元素的方法。 - Stuart R. Jefferys
3个回答

7

要捕获警告并手动分析它们,您还可以使用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)

(最后一行引发一个错误。)

2

所以我想出一个解决方案,涉及使用自己的警告捕获循环并将警告作为字符串检查。虽然不是“一次全部”解决方案,但在处理复杂函数调用时至少更加紧凑。对于示例函数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 在捕获第一个警告后退出。这里和这里帮助我解决了这个问题。也许这可以转化为一个expect_all_warnings测试,它接受一个正则表达式向量进行匹配,也许还可以加上all=TRUE,表示没有警告可以不匹配。如果有人发布更好或者至少将解决方案封装得更好的答案,我会暂时不接受这个答案。

感觉有点奇怪,接受自己的答案作为解决方案,但已经过了一段时间,而且有人给问题点了赞。 - Stuart R. Jefferys
当我测试完整脚本时,我遇到了类似的问题。我预计会出现几个错误,但我不想为每个测试执行脚本。有没有一种方法可以捕获所有警告,然后进行测试?我已经写了一个SO问题 - drmariod

0

您可以尝试使用类似于 [12] 的方式:

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

不行。和 1 | 2 一样的问题。只有一个警告时为真,例如对于函数 bar <- function() { warning("warn 1.") } - Stuart R. Jefferys

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接