Adding Tests to Packages

Whilst you can use unittest with scripts and ad-hoc code, the main use-case is for adding tests to packages.

For the following example we’ll assume you’re developing a package called mypackage.

Adding unittest to your package

Add the following line to the package DESCRIPTION file, to declare that your package optionally depends on unittest:

Suggests: unittest

Create a directory called tests in your package source, alongside your R directory.

Testing functions exported by your package

Let’s say we want to test the following package function, in R/biggest.R:

biggest <- function(x,y) {max(c(x,y))}

Create a corresponding tests/test_biggest.R in your package source (the file name isn’t important, but it helps to be consistent):

#!/usr/bin/Rscript --vanilla

library(mypackage)
library(unittest, quietly = TRUE)
if (!interactive()) options(warn=2, error = function() { sink(stderr()) ; traceback(3) ; q(status = 1) })

ok(ut_cmp_equal( biggest(3,4), 4), "two numbers")
ok(ut_cmp_equal( biggest(c(5,3),c(3,4)), 5), "two vectors")

The if (!interactive()) ... line makes sure that, when run as a script, any warnings are errors, so they don’t go unnoticed. We also enable a traceback so you can see where any errors occured.

Finally, we use ok to test the output of the function works as we expect.

Running your tests

There are many ways you can then run your tests:

  • R CMD check will run everything it finds in the tests directory, and will fail if any of the tests fail.
  • source('tests/test_biggest.R') within R will run individual tests in your current R session.
  • Rscript --vanilla tests/test_biggest.R will run the test outside of R.
  • tests/test_biggest.R will also run the test outside of R, if the file is marked as executable (i.e. chmod a+x tests/test_biggest.R).

unittest detects when it’s been run as a script and if so will produce a summary of a results. The package will also throw an error if any tests fail; throwing an error will in turn cause CMD check to report the error and fail the check.

To run all your tests as part of a bash script, you can do so with:

for f in tests/*.R; do echo "=== $f"; Rscript --vanilla $f || break; done

Forcing test output color

unittest will output colored diff output if it thinks it will be supported. By default it assumes that they are not supported in R CMD check. If you would like to see a log in color you can do the following:

R_CLI_NUM_COLORS=256 R CMD check ...
less -R 00check.log

Testing functions not exported by your package

Sometimes it’s necessary to test functions that aren’t exported by your package. Because they aren’t exported they cannot be directly referenced in tests. To get around this, use local() as follows:

var <- 4

local({
    ok(ut_cmp_equal(internal_function(3), 3))
    ok(ut_cmp_equal(internal_function(var), 4))

    # NB: Regular assignment (<-) won't work here,
    # but using <<- to refer to variables outside local() will
    var <<- 5
    ok(ut_cmp_equal(internal_function(var), 5))
}, asNamespace('mypackage'))

Embedding tests in vignettes

At the start of your vignette, load unittest but customise ok() so it’s output goes to stderr:

```{r, message=FALSE, echo=FALSE}
library(unittest)
# Redirect ok() output to stderr
options(unittest.output = stderr())
library(mypackage)
```

Then, include hidden blocks for your tests, for example:

Our biggest function will return the highest number:

```{r}
out <- biggest(3,4)
out
```

```{r, message=FALSE, echo=FALSE}
ok(ut_cmp_equal(out, 4), "biggest(3,4) is 4")
```

Here, the reader sees the first block, and the output of biggest(), and the hidden block ensures the output is as we expect.

To run the tests, build the vignettes with, e.g. tools::buildVignettes(dir="."). Test output will be shown as part of the rebuild process.