devtools and testthat R packages, definitely worth using.

Tools that facilitate R package development

devtools is a R package developed by Hadley Wickham, which aims to make our life as package developer easier by providing R functions that simplify many common tasks. There are two functions that I find very useful in the early developments of my personal packages, they are load_all("pkg") and test("pkg").

  • load_all("pkg") function

load_all("pkg") simulates installing and reloading your package, by loading R code in R/, compiled shared objects in src/ and data files in data/. During development you usually want to access all functions so load_all ignores the package NAMESPACE. It works efficiently by only reloading files that have changed.

Suppose you have your wannabe package in the folder ~/my_package. Then if you type load_all(~/my_package) it will load all your functions, compiled shared objects and data. This is specially useful when you are in the first stages of development, when you still don’t have a fully functioning package, and want to ignore the NAMESPACE file.

  • test("pkg") function

test("pkg") reloads your code, then runs all testthat tests (see below). testthat is a R package that allow us to perform unit testing in R. If you have never used testthat before, I suggest you to start, since it is a very efficient and easy way to make sure all your functions are working properly and to detect bugs in your code as soon as possible.

testthat: unit testing in R

Next I summarize my notes about testthat. I have been using this for my own reference. They were extracted from [1].

Important: The test function in devtools expects you to keep all your test files (see below) inside pkg/inst/tests and also that you create a file with the following code in pkg/tests:

library(testthat)
load_all("pkg") # or library(pkg) in case your package is
                # already built and installed
test_package("pkg")

tip: After you read this post, my advice is that the best way to learn how to use testthat is to see how it is done in packages that uses it. For example, check the folders inst/test and tests in the ggplot2 repository at Github.

testthat has a hierarchical structure made up of expectations, tests and contexts.

  • An expectation describes what the result of a computation should be. Does it have the right value and right class? Does it produce error messages when you expect it to? There are 11 types of built-in expectations. See Table 1.
  • A test groups together multiple expectations to test one function, or tightly related functionality across multiple functions. A test is created with the test_that function.
  • A context groups together multiple tests that test related functionality.

Expectations

expect_true(x) checks that an expression is true.
expect_false(x) checks that an expression is false.
expect_is(x, y) checks that an object inherit()s from a specified class
expect_equal(x, y) check for equality with numerical tolerance
expect_equivalent(x, y) a more relaxed version of equals() that ignores attributes
expect_identical(x, y) check for exact equality
expect_matches(x, y) matches a character vector against a regular expression.
expect_output(x, y) matches the printed output from an expression against a regular expression
expect_message(x, y) checks that an expression shows a message
expect_warning(x, y) expects that you get a warning
expect_error(x, y) verifies that the expression throws an error.

Running a sequence of expectations is useful because it ensures that your code behaves as expected.

Tests

Each test should test a single item of functionality and have an informative name. The idea is that when a test fails, you should know exactly where to look for the problem in your code. You create a new test with test_that, with parameters name and code block. The test name should complete the sentence “Test that . . . ” and the code block should be a collection of expectations. When there’s a failure, it’s the test name that will help you figure out what’s gone wrong. For example,

test_that("str_length is number of characters", {
 expect_that(str_length("a"), equals(1))
 expect_that(str_length("ab"), equals(2))
 expect_that(str_length("abc"), equals(3))
})

Each test is run in its own environment so it is self-contained. The exceptions are actions which have effects outside the local environment. These include things that affect:

  • The filesystem: creating and deleting files, changing the working directory, etc.
  • The search path: package loading & detaching, attach.
  • Global options, like options() and par().

When you use these actions in tests, you’ll need to clean up after yourself.

Contexts

Contexts group tests together into blocks that test related functionality and are established with the code: context("My context"). Normally there is one context per file, but you can have more if you want, or you can use the same context in multiple files.

Example

The following example summarize well the roles of expectation, test and context:

context("String length")

test_that("str_length is number of characters", {
 expect_that(str_length("a"), equals(1))
 expect_that(str_length("ab"), equals(2))
 expect_that(str_length("abc"), equals(3))
})

test_that("str_length of missing is missing", {
 expect_that(str_length(NA), equals(NA_integer_))
 expect_that(str_length(c(NA, 1)), equals(c(NA, 1)))
 expect_that(str_length("NA"), equals(2))
})

test_that("str_length of factor is length of level", {
 expect_that(str_length(factor("a")), equals(1))
 expect_that(str_length(factor("ab")), equals(2))
 expect_that(str_length(factor("abc")), equals(3))
})

References:

[1] Wickham, H. (2011). testthat: Get Started with Testing. Springer. The R Journal, volume 3.

[2] The README.md at the Github repository for devtools.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s