【发布时间】:2011-05-18 07:08:44
【问题描述】:
我目前正在为 R 开发一个 graphical analysis package。我们正在尝试使用来自 Clean Code 和 Test-Driven Development (TDD) 的原则。但是,我们遇到了一个概念问题。
如何测试未公开的函数?
考虑以下简化示例。 Outer() 是我们在package 中构建的一个函数,我们通过在NAMESPACE 文件中列出它来向用户公开它。 Inner() 是一个简短的(~5 行)实用函数:
Outer <- function(...) {
Inner <- function(...) {
return(x)
}
return( Inner() )
}
package 中的大多数用户公开函数都是短的、单一行为的 Inner() 样式函数的集合,它们位于用户可以调用的单个 Outer() 函数的保护伞下。 Granova.ds.ggplot() 就是一个例子。 Clean Code 强烈提倡这样的模块化设计,我们对结果非常满意。但是我们不知道如何为它构建单元测试,因为我们想要测试的函数在我们设计的Granova.ds.ggplot() 函数的范围之外是无法访问的。
我们想到了几个想法/解决方案:
- 测试应该只能访问公共 API。由于像
Inner()这样的函数在设计上是不公开的(它们不会在NAMESPACE中导出),我们甚至不应该尝试测试它们。 - Hadley Wickham 的
testthatpackage wiki 表示它支持使用R CMD check工作流测试“非导出函数”。 - 如果一切都失败了,我们可以以某种方式手动破坏我们的
Outer()函数以进行测试
到目前为止,这些解决方案都没有奏效
解决方案 1 似乎是一种逃避。我们相信,特别是在 R 中,拥有一个调用各种简短、单一职责实用程序方法的顶级函数是明智的。无法测试这样的方法似乎很愚蠢,就像有解决方案但我们还没有找到的东西类型。
解决方案 2 可能有效,但到目前为止我们还没有让它发挥作用。如果您尝试克隆our repository,然后采购inst/dev.R,我相信您会在测试输出中发现它找不到函数InitializeGgplot(),即使said function 在granova.1w.ggplot() 的第613-615 行中定义。同样,在终端运行R CMD check 似乎根本不会执行我们的任何测试。不过,这确实需要大量时间并向我们抛出侮辱性错误:-(
解决方案 3 在某种意义上是务实的,但与始终朝着项目的目标状态前进的目标背道而驰。这对我们来说没有意义。
理想的解决方案是什么
理想情况下,我们正在寻找一种方法来利用像 testthat 这样的包在我们编码时快速提供反馈,并允许我们测试像 Inner() 这样存在于函数中的函数喜欢Outer()。更好的方法是不用R CMD check,因为我们的一些函数的 3-d 复杂性,每次运行几乎需要整整一分钟。
那么,我们缺少什么? TDD 实践是否应该允许在 R 中测试外部/内部样式设置?如果他们这样做,我们如何在开发我们的包时这样做?欢迎任何反馈,我会尽力回复任何不清楚的地方。
谢谢!
【问题讨论】:
-
如果你的 Inner 函数实际上只定义在 within Outer 的代码中,我相信这些只是在调用 Outer 时才实际创建的。所以我看不到测试这些的方法。对于其他非嵌套但非导出的函数,应该可以通过 getAnywhere 等进行测试。
-
您能否解释一下选项 3 中“项目的目标状态”的含义?我会将这些内部函数分解出来,但不会在 API 中公开它们(即不将函数导出到 NAMESPACE)。因此,您可以记录和测试它们,并且仍然拥有干净的 API。
-
@Andrie - 好问题;我不清楚。我们的解决方案之一涉及您建议的伪版本。我们会故意破坏
Outer()(比如通过注释掉它的函数声明)来强制Inner()函数是顶级的。稍后,我们将取消注释掉这些行。对我来说,这感觉就像远离目标状态,因为这意味着在现在故意破坏暴露的函数,并且必须记住在我们构建之前稍后再次解除它。
标签: unit-testing r tdd