Automatic ‘testthat’ test skeletons with new R package ‘roxytest’ extending ‘roxygen2’
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
It is important to test software.
One approach is unit-testing, and for R packages this can e.g. be done using testthat.
It is also important to document software. For R packages roxygen2 is really helpful:
It enables you to write documentation in the code file in the R/ folder
where the function is
implemented. And then roxygen2 takes care of handling the Rd files in the man/ folder.
I have made a new R package that combines these approaches:
roxytest.
The idea is to write tests in the documentation near the implementing code,
and then roxytest takes care of handling tests/testthat/*.R files just
as roxygen2 handles the man/*.Rd files.
roxytest is still experimental, but please have a look at https://github.com/mikldk/roxytest.
There is also a demonstration package that shows how to use it at https://github.com/mikldk/roxytest-demo.
Not much is needed to use roxytest:
- Install it:
devtools::install_github('mikldk/roxytest') - Specify in your package’s
DESCRIPTIONfile that you will use it: see below - Use it: see below
- Run
roxygen2to generate the documentation andtestthattests:roxygen2::roxygenise()
The DESCRIPTION file
Add the following lines to your package’s DESCRIPTION file:
(Or make appropriate changes to obtain similar results.) The reason that roxytest is in Depends rather than in Imports is to attach the package immediately so that roxygen2 can find the roclet.
Imports:
roxygen2,
testthat
Depends:
R (>= 2.10),
roxytest
Roxygen: list(roclets = c("namespace", "rd", "roxytest::testthat_roclet"))
Use it
For example, if the file R/functions.R contains this code (from roxytest-demo):
#' A function to do x
#'
#' @param x A number
#'
#' @tests
#' expect_equal(foo(2), sqrt(2))
#' expect_error(foo("a string"))
#'
#' @return something
foo <- function(x) {
return(sqrt(x))
}
#' A function to do y
#'
#' @param x Character vector
#' @param y Character vector
#'
#' @tests
#' expect_equal(bar("A", "B"), paste("A", "B", sep = "/"))
#'
#' @export
bar <- function(x, y) {
paste0(x, "/", y)
}
Then roxygen2::roxygenise() will generate (with the roxytest::testthat_roclet roclet)
the file tests/testthat/test-roxytest-functions.R with this content:
# Generated by roxytest: Do not edit by hand!
context("File R/functions.R")
test_that("Function foo()", {
expect_equal(foo(2), sqrt(2))
expect_error(foo("a string"))
})
test_that("Function bar()", {
expect_equal(bar("A", "B"), paste("A", "B", sep = "/"))
})
Wish-list
In order to make the usage a bit smoother, I have a small wish-list that I am working on, but
it requires changes to roxygen2 and Rstudio:
- Rstudio: CTRL+SHIFT+D would run
roxygen2::roxygenise()instead ofdevtools::document(roclets=c('rd', 'collate', 'namespace'))- Project options -> Build tools -> If all check marks are removed, nothing happens with CTRL+SHIFT+D. If instead
devtools::document()would be ran it would work. - Feature request submitted
- Project options -> Build tools -> If all check marks are removed, nothing happens with CTRL+SHIFT+D. If instead
roxygen2:- In
DESCRIPTION,Roxygen: list(roclets = c("namespace", "rd", "roxytest::testthat_roclet"))must be added. It would be more consistent to omit_roclet. - Easier test;
roxygen2usesroxygen2::roc_proc_text; it would be nice to be able to use multiple roclets - Both addressed in issue 891 for
roxygen2
- In
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.