Automatic 'testthat' test skeletons with new R package 'roxytest' extending 'roxygen2'

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:

  1. Install it: devtools::install_github('mikldk/roxytest')
  2. Specify in your package’s DESCRIPTION file that you will use it: see below
  3. Use it: see below
  4. Run roxygen2 to generate the documentation and testthat tests: 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 of devtools::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
  • 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; roxygen2 uses roxygen2::roc_proc_text; it would be nice to be able to use multiple roclets
    • Both addressed in issue 891 for roxygen2
Avatar
Mikkel Meyer Andersen
Assoc. Professor of Applied Statistics

My research interests include applied statistics and computational statistics.

Related