Discovering the new Digital world

18 May 2017

Software Quality - TDD and unit testing in R using the 'testthat' package

“Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: requirements are turned into very specific test cases, then the software is improved to pass the new tests, only. This is opposed to software development that allows software to be added that is not proven to meet requirements.”, from TDD - Wikipedia

Why TDD and testing?

Few things you should think about when writing code:

TDD and tests are important because allow you to be sure that the code is working as expected when you write it for the first time, and/ or when you change it. TDD and tests allow you:

Setting up a battery of tests around your code and functionality can play a big role in maintaining and keeping smooth operations.

The TDD mantra

The testthat package

testthat is a testing framework for R implemented by Hadley Wickham, as the author says “an R package to make test fun”.

The main goal of this package is to make the initial effort around testing as small as possible, and then gradually and incrementally increase the formality and complexity of your tests. And it can be used to use to introduce Test Driven Development into your way of working.

How to install the testthat

#The package is available on CRAN
#Run the following line to install the package locally in your machine
install.packages("testthat")

Test structures

testthat has an hierarchical structure that is made up of expectations, tests and contexts.

Expectations

An expectation is a binary assertion about whether of not a returned value is as expected. If an expectation is not true then testthat will raise an error. The format of an expectation is very simple to read I expect that a will be equal to b.

An expectation, actually the same expectation, can be be expressed using one of the following formats

#An old-style expectation
#still supported but sift deprecated
testthat::expect_that(object = 10, condition = testthat::equals(10))

#New style expectation
testthat::expect_equal(object = 10, expected = 10)

Information on available expectations can be found in the help page for the package (see help(package="testthat")).

Tests

Each test should test a single item of functionality and have an informative name. When a test fails it should be straight forward to know where to look for the problem in the code.

A test is created using testthat::test_that with a test name and code block as arguments. The test name should complete the sentence “Test that ….” while the code block should be a collection of expectations.

Each test is run in its own environment and is self-contained but if you change the R landscape testthat does not know how to clean up. So if you change

• the filesystem, creating and deleting files, changing the working directory, etc. • the search path, library() or attach() • global options, like options() and par()

then it is your responsibility to clean up (CLEAN UP AFTER YOURSELF).

Let’s say to have a simple function make_filename(), given the year as argument it creates a file name as a string with the following format accident_<year>.csv.bz2. An example of a test checking if the function behave correctly is given below

testthat::test_that("make_filename returns expected filename when passing year as integer",{
  testthat::expect_match("accident_2017.csv.bz2", make_filename(2017))
})

Contexts

Tests are grouped together using a context, the context describes the functionality under test. Normally there is one context per file.

An example below

#File: test_make_filename.R
context("make_filename tests")

test_that("make_filename returns expected filename when passing year as integer",{
  expect_match("accident_2017.csv.bz2", make_filename(2017))
})

test_that("make_filename returns expected filename when passing year as character",{
  expect_match("accident_2013.csv.bz2", make_filename("2013"))
})

test_that("make_filename returns a warning when passing an invalid character",{
  invalid_year = "a_string"
  expect_warning(make_filename(invalid_year), "NAs introduced by coercion")
})

How to use the testthat package

TDD in practice

A step-by-step example of how to apply TDD to solve a simple exercise can be found in the following repository.

References

  1. ‘testthat’ package, by Hadley Wickham
  2. ‘testthat: Get Started with Testing’, by Hadley Wickham
  3. ‘R Packages’, by Hadley Wickham, O’Reilly April 2015.