如何测试依赖于正在安装或未安装的包的行为

how to test behavior that depends on a package being installed or not

我有这样的功能:

func <- function(x) {
  if (requireNamespace("broom", quietly = TRUE)) {

    print(1)

  } else {

    print(2)

  }

我想使用 testthat 编写测试来触发 两种 情况。但当然 broom 要么安装在我的电脑上,要么没有安装。怎么办?

编辑:自 testthat-2.0.0 起不再有效。根据 change in Oct 2017:

  • "Can't mock functions in base packages": You can no longer use with_mock() to mock functions in base packages, because this no longer works in R-devel due to changes with the byte code compiler. I recommend using mockery or mockr instead.

其余答案仅适用于 testthat 的旧版本。


testthat::with_mock应该做你想做的。

library(testthat)
somefunc <- function() if (requireNamespace("base", quietly=TRUE)) 1L else 2L

一些简单的测试:

expect_equal( somefunc(), 1L )

成功。

expect_equal( somefunc(), 2L )
# Error: somefunc() not equal to 2.
# 1/1 mismatches
# [1] 1 - 2 == -1

预期。

让我们创建一个覆盖基本函数的 "mock" 函数:

with_mock(
  `base::requireNamespace` = function(package, ..., quietly=FALSE) FALSE,
  expect_equal( somefunc(), 1L )
)
# Error: somefunc() not equal to 1.
# 1/1 mismatches
# [1] 2 - 1 == 1

with_mock(
  `base::requireNamespace` = function(package, ..., quietly=FALSE) FALSE,
  expect_equal( somefunc(), 2L )
)
# [1] 2

注意:成功时,expect_equal 不可见 return 是 return 值,因此您在第一个示例中看不到 [1] 1with_mock,成功时,return 是 return 值,但不是不可见的。在这两种情况下,失败都会 return 修改后的 return 值。这个微小的差异应该不会影响任何测试。

根据您要覆盖的函数,(对我而言)小心使用相同的形式定义模拟函数是有意义的。如果你知道 exactly 它是如何在 all 从属函数中调用的 always在你的测试期间,但我认为对形式的额外仔细关注将排除真正难以解决的测试失败。

注意:帮助说明这个

... is still experimental, so use with care.

    old_fn <- base::requireNamespace
    myrequireNamespace <- function(...) FALSE
    unlockBinding("requireNamespace", as.environment("package:base"))
    assign("requireNamespace",myrequireNamespace, "package:base")
    expect_error(read_data("meta",data_table,  frame=NULL),"Package \"readxl\" 
    needed for this function to load excel files")
    assign("requireNamespace",old_fn, "package:base")
    lockBinding("requireNamespace", as.environment("package:base"))
    rm(old_fn, myrequireNamespace)

对我来说是一个在 testthat 内部工作的解决方法。