开发者

How can we test functions that aren't exposed when building R packages?

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.

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 <- 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.

Several ideas/solutions oc开发者_JAVA技巧curred 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. Hadley Wickham's testthat package wiki says it supports testing of "non-exported functions" using an R CMD check workflow.
  3. If all else fails, we could somehow manually break our Outer() functions for testing purposes

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.

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 :-(

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.

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.

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.

Thanks!


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.

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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜