【问题标题】:How to write unit tests for suggested packages?如何为建议的包编写单元测试?
【发布时间】:2015-03-10 13:25:33
【问题描述】:

R 中的包可以对其他包有不同类型的依赖。其中一些类型表示硬要求,即DependsImportsLinkingTo

但是,还有第二个类别表示更软的依赖关系,即SuggestsEnhances。在这两种情况下,如果建议的/增强的包可用,包会提供额外的功能。

这是一个具体的例子:包checkpoint 导入knitr 因为knitr 帮助checkpoint 解析rmarkdown 文件。

但现在我正在考虑将 knitr 更改为 Suggests 依赖项,即仅在实际安装了 knitr 时才提供此功能。

为了进行正确的单元测试,这意味着我必须同时测试两种情况:

  1. 如果knitr 可用,则执行操作。
  2. 如果knitr可用,则发出警告并且什么都不做。

实际的R代码很简单:

if(require(knitr)) {
  do_stuff()
} else {
  message("blah")
}

问题

但是如何为这两种场景设置单元测试呢?

在我看来,检查require(knitr) 的简单事实将加载knitr 包(如果它在本地库中可用)。

所以,要测试案例 1,我必须在本地安装 knitr,这意味着我无法测试案例 2。

有没有办法为此用例配置testthat(或任何其他单元测试框架)?

【问题讨论】:

  • 临时重命名 knitr 文件夹是否可以接受?
  • @Dason 也许,这可能适用于我的本地机器。但是如何在我的持续构建集成机器(Jenkins 或 Travis)上管理这个过程,以及如何编写这个测试以在 CRAN 上工作?
  • 我想如果您在这些机器上安装 knitr,它会进入您具有读/写权限的文件夹中。我想它可以归结为通过在.libPaths() 中的条目上使用dir 然后使用file.rename 将 knitr 文件夹重命名为类似 knitr_BALEETED (或任何你想要的)然后测试完成后将其重命名为 knitr 。我不确定这将如何符合 CRAN...
  • 这是一个有趣的想法。我会尝试使用system.file() 告诉我位置。老实说,我很乐意禁用 CRAN 上的测试(使用 testthat 很容易做到),条件是测试在本地和 Travis 上工作。
  • 我没有意识到 testthat 可以选择 skip_on_cran。那...实际上使我一直在做的一些事情变得更容易了。

标签: r testthat


【解决方案1】:

tl;博士

要测试使用require(knitr) 失败时遵循的分支,请使用trace() 临时修改require(),使其找不到knitr,即使它存在于.libPaths() .具体来说,在require() 的主体中,将lib.loc= 的值重置为指向R.home()——一个不包含knitr 包的现有目录。

这似乎在包中和在运行以下命令的交互式会话中一样有效:

find.package("knitr")

trace("require", quote(lib.loc <- R.home()), at=1)
isTRUE(suppressMessages(suppressWarnings(require(knitr))))

untrace("require")
isTRUE(suppressMessages(suppressWarnings(require(knitr))))

据我了解,您有一个具有两个分支的函数,一个在 require(knitr) 成功的 R 会话中执行,另一个在失败的会话中执行。然后,您希望从 knitr 实际上位于 .libPaths() 的单个 R 实例“双向”测试此功能。

所以基本上你需要一些方法来暂时将require(knitr) 的呼叫蒙蔽到 knitr 的实际存在。完全暂时重置.libPaths() 返回的值看起来很有希望,但似乎不可能。

另一个有希望的途径是以某种方式将require() 调用中的lib.loc 的默认值从NULL (这意味着“使用.libPaths() 的值”)重置到knitr 不可用。您不能通过覆盖base::require() 来实现此目的,也不能(在包中)通过使用所需值lib.loc 定义require() 的本地掩码版本来实现此目的。

不过,看起来您可以使用trace() 临时修改require() (通过设置lib.loc=R.home() 使其不知道knitr 的可用性)。然后执行untrace()require() 恢复为原版版本,然后继续查找knitr

这是我测试过的虚拟包中的样子。首先是一个 R 函数,它允许我们沿两个分支测试是否成功

## $PKG_SRC/R/hello.R

hello <- function(x=1) {
    if(require(knitr)) {
        x==2
    } else {
        x==3
    }
}

然后是几个测试,每个分支一个:

## $PKG_SRC/inst/tests/testme.R

## Test the second branch, run when require(knitr) fails
trace("require", quote(lib.loc <- R.home()), at=1)
stopifnot(hello(3))
untrace("require")

## Test the first branch, run when require(knitr) succeeds
stopifnot(hello(2))

为了测试这一点,我使用pkgKitten::kitten("dummy")设置了一个源目录,复制到这两个文件中,将Suggests: knitr添加到DESCRIPTION文件中,然后从相应目录运行devtools::install()devtools::check() .软件包安装得很好,并通过了所有检查。

【讨论】:

  • 最好设置trace("require", quote(value &lt;- FALSE), at=5)。即使 knitr 已经加载,这将导致 require() 返回 FALSE 的值,从而让您不太注意执行测试的顺序。
  • 这很好。我一直在做类似的事情,使用testthat::with_mock() 功能。我怀疑在后台 with_mock 做了类似的事情。
  • @Andrie 很酷,我以前没见过。 trace() 当您只想更改一个或几行函数时会更好,但with_mock() 确实可以很容易地简单地强制某个返回值,这就是您所需要的。
猜你喜欢
  • 2017-05-29
  • 1970-01-01
  • 2018-01-17
  • 2012-01-06
  • 2019-03-17
  • 2016-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多