我们如何测试构建R包时未暴露的函数?

时间:2022-06-06 23:09:08

I'm currently developing a graphical analysis package for R. We're trying to use principles from both Clean Code and Test-Driven Development (TDD). But, we've run into a conceptual problem.

我正在为R开发一个图形分析包。我们正在尝试使用清洁代码和测试驱动开发(TDD)中的原则。但是,我们遇到了一个概念问题。

How can you test unexposed functions?

Consider the following simplified example. Outer() is a function we're building in our package, and we expose it to users by listing it in the NAMESPACE file. Inner() is a short (~5 line) utility function:

请考虑以下简化示例。 Outer()是我们在包中构建的函数,我们通过在NAMESPACE文件中列出它来向用户公开它。 Inner()是一个短(~5行)效用函数:

Outer <- function(...) {

  Inner <- function(...) {
    return(x)
  }

  return( Inner() )
}

Most of the user-exposed functions in our package are collections of short, single-behavior Inner() style functions that lie under the umbrella of a single Outer() function the user can call. Granova.ds.ggplot() is one example. Such a modular design is strongly advocated by Clean Code, and we're quite happy with the results. But we don't know how to build unit tests for it, since the functions we want to test aren't accessible outside the scope of the Granova.ds.ggplot() function given how we designed it.

我们程序包中的大多数用户公开函数都是简短的单行为Inner()样式函数的集合,这些函数位于用户可以调用的单个Outer()函数的保护之下。 Granova.ds.ggplot()就是一个例子。 Clean Code强烈倡导这种模块化设计,我们对结果非常满意。但是我们不知道如何为它构建单元测试,因为我们想要测试的函数不能在Granova.ds.ggplot()函数的范围之外访问,因为我们设计了它。

Several ideas/solutions occurred to us:

我们遇到了几个想法/解决方案:

  1. Tests should only have access to public APIs. Since functions like Inner() are by design not public (they're not exported in NAMESPACE) we shouldn't even be trying to test them.
  2. 测试应该只能访问公共API。由于像Inner()这样的函数不是公开的(它们不是在NAMESPACE中导出的),我们甚至不应该尝试测试它们。
  3. Hadley Wickham's testthat package wiki says it supports testing of "non-exported functions" using an R CMD check workflow.
  4. Hadley Wickham测试包wiki说它支持使用R CMD检查工作流测试“非导出功能”。
  5. If all else fails, we could somehow manually break our Outer() functions for testing purposes
  6. 如果所有其他方法都失败了,我们可以以某种方式手动破坏我们的Outer()函数以进行测试

None of these solutions has worked so far

Solution 1 seems like a cop-out. We believe that especially in R, it's sensible to have a top-level function that calls various short, single-responsibility utility methods. Not being able to test such methods seems silly, and like the type of thing there's a solution for but we just haven't found yet.

解决方案1似乎是一个警察。我们认为,特别是在R中,拥有一个称为各种简短,单一责任的实用方法的*函数是明智的。无法测试这样的方法似乎很愚蠢,就像那种类型的东西有解决方案,但我们还没有找到。

Solution 2 might work, but we haven't gotten it to work so far. If you try cloning our repository, then sourcing inst/dev.R I believe you'll find in the test output that it cannot find the function InitializeGgplot(), even though said function is defined in lines 613-615 of granova.1w.ggplot(). Similarly, running R CMD check at the terminal doesn't seem to execute any of our tests at all. It does take a great deal of time and throw insulting errors at us, though :-(

解决方案2可能有效,但到目前为止我们尚未开始工作。如果您尝试克隆我们的存储库,那么获取inst / dev.RI相信您会在测试输出中找到它无法找到函数InitializeGgplot(),即使所述函数在granova.1w.ggplot的第613-615行中定义()。同样,在终端上运行R CMD检查似乎根本不执行任何测试。它需要花费大量时间并向我们投掷侮辱性错误,但:-(

Solution 3 is in a sense pragmatic, but counter to the aim of always moving toward a goal state of the project. It doesn't make sense to us.

解决方案3在某种意义上是务实的,但与总是朝着项目的目标状态前进的目标相反。这对我们没有意义。

What would be the ideal solution

Ideally, we're looking for a way to leverage a package like testthat to rapidly provide feedback as we code, and to allow us to test functions like Inner() that exist within functions like Outer(). Even better would be a way to do it without resorting to R CMD check, which due to the 3-d complexity of some of our functions takes almost a full minute to run each time.

理想情况下,我们正在寻找一种方法来利用像testthat这样的包在我们编码时快速提供反馈,并允许我们测试像Outer()这样的函数中存在的Inner()等函数。更好的方法是在不使用R CMD检查的情况下执行此操作,由于我们的某些功能的三维复杂性每次运行几乎需要一分钟。

So, what are we missing? Should TDD practices allow for testing Outer/Inner style setups in R? If they do, how can we do so in developing our package? Any feedback is welcome, and I'll try to respond to anything that's unclear.

那么,我们缺少什么? TDD实践是否允许在R中测试外部/内部样式设置?如果他们这样做,我们怎样才能开发我们的包装?任何反馈都是受欢迎的,我会尽力回应任何不清楚的事情。

Thanks!

谢谢!

2 个解决方案

#1


6  

If Inner implements non-trivial functionality that you want to test, I'd suggest moving Inner to the top-level, but not exporting it. Generally, I avoid nesting functions within other functions for exactly this reason - they're hard to test.

如果Inner实现了你要测试的非平凡功能,我建议将Inner移到顶层,但不要导出它。一般来说,出于这个原因,我避免在其他函数中嵌套函数 - 它们很难测试。

You can test during development with the usual testthat functions because you're probably just sourcing in all your R code and not worrying about namespaces (at least that's how I develop). You then use R CMD check in conjunction with test_package to ensure the tests still work at build time - test_packages runs the tests in the package namespace so they can test non-exported functions.

您可以在开发过程中使用通常的测试函数进行测试,因为您可能只是在使用所有R代码而不用担心命名空间(至少我是如何开发的)。然后将R CMD检查与test_package结合使用以确保测试在构建时仍然有效 - test_packages在包命名空间中运行测试,以便它们可以测试未导出的函数。

#2


6  

IMO there is no problem here -- Inner is just a nondetachable part of Outer, so testing Outer tests Inner. Would you be willing to test anonymous functions? Same here.

IMO在这里没有问题 - Inner只是Outer的一个不可拆卸的部分,所以测试外部测试内部。你愿意测试匿名功能吗?同样在这里。

#1


6  

If Inner implements non-trivial functionality that you want to test, I'd suggest moving Inner to the top-level, but not exporting it. Generally, I avoid nesting functions within other functions for exactly this reason - they're hard to test.

如果Inner实现了你要测试的非平凡功能,我建议将Inner移到顶层,但不要导出它。一般来说,出于这个原因,我避免在其他函数中嵌套函数 - 它们很难测试。

You can test during development with the usual testthat functions because you're probably just sourcing in all your R code and not worrying about namespaces (at least that's how I develop). You then use R CMD check in conjunction with test_package to ensure the tests still work at build time - test_packages runs the tests in the package namespace so they can test non-exported functions.

您可以在开发过程中使用通常的测试函数进行测试,因为您可能只是在使用所有R代码而不用担心命名空间(至少我是如何开发的)。然后将R CMD检查与test_package结合使用以确保测试在构建时仍然有效 - test_packages在包命名空间中运行测试,以便它们可以测试未导出的函数。

#2


6  

IMO there is no problem here -- Inner is just a nondetachable part of Outer, so testing Outer tests Inner. Would you be willing to test anonymous functions? Same here.

IMO在这里没有问题 - Inner只是Outer的一个不可拆卸的部分,所以测试外部测试内部。你愿意测试匿名功能吗?同样在这里。