【问题标题】:How to test for multiple warnings in an unknown order using testthat?如何使用 testthat 以未知顺序测试多个警告?
【发布时间】:2016-06-13 06:13:02
【问题描述】:

我想测试一个函数是否会生成多个警告(4 个或更多),而警告的顺序可能会有所不同。我最好的尝试是基于前瞻正则表达式匹配。简化为只有 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 的内部正在做一些事情,比如分别针对每个警告测试给定的 RegExp ——为什么 expect_warning( ... all=TRUE ) 参数可能有意义。

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

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

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

【问题讨论】:

  • 看来expect_warning 没有perl 参数(与R 中的其他函数一样),但为什么不简单地使用模式1.*\\n.*21(.*\\n)*.*2 (因为(?s) , .* 和前瞻都没用)
  • @Casimir et Hippolyte - expect_warning 接受...,并将参数传递给底层的grepl 调用,因此它使用perl= TRUE 参数。没有它,包含 RegExp 的“前瞻”是无效的。我需要它,因为顺序未知,并且在我的实际应用程序中有超过 2 个。前瞻是match elements in an unknown order 的一种方式。

标签: regex r unit-testing testthat


【解决方案1】:

要捕获警告并“手动”分析它们,您还可以使用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 在收到第一个警告后退出。 Herehere 帮助我解决了这个问题。也许这可以转换成一个expect_all_warnings 测试,它需要一个正则表达式的vector 来匹配,也许all= TRUE 意味着没有警告可以不匹配。我不会接受这个作为答案,以防有人发布更好的答案,或者至少发布一个很好地打包了这样的解决方案的答案。

    【讨论】:

    • 接受我自己的答案作为解决方案感觉有点奇怪,但已经有一段时间了,有人对这个问题投了赞成票。
    • 我在测试完整脚本时遇到了类似的问题。我预计会有几个错误,但我不想为每个测试执行脚本。有没有办法捕获所有警告然后测试它们?我已经写了SO question
    【解决方案3】:

    您也许可以使用[12] 喜欢:

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

    【讨论】:

    • 不。与1 | 2 相同的问题。如果只给出一个警告则为真,例如函数bar &lt;- function() { warning("warn 1.") } 为真
    猜你喜欢
    • 2016-01-01
    • 1970-01-01
    • 2016-12-26
    • 2012-09-12
    • 2017-05-01
    • 2022-01-06
    • 2018-01-04
    • 1970-01-01
    相关资源
    最近更新 更多