“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
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.
testthat
packagetestthat
is a testing framework for R implemented by Hadley Wickham, as the author says “an R package to make test fun”.
The main goal is to make the initial effort around testing as small as possible, and then gradually and incrementally increase the formality and rigour of your tests. And it can be used to use to introduce Test Driven Development.
testthat
#The package is available on CRAN
#Run the following line to install the package locally in your machine
install.packages("testthat")
testthat
has an hierarchical structure that is made up of expectations, tests and contexts.
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)
Below you can find some examples on how to test specific expectations, more information can be found in the help page for the package (see help(package="testthat")
).
testthat::expect_equal
tests exact equality including attributes using a numerical tolerance.
#PASS
testthat::expect_equal(object = 10, expected = 10)
testthat::expect_equal(object = (10 + 1e-7), expected = 10)
testthat::expect_equal(
object = c("first" = 1, "second" = 2),
expected = c("first" = (1 + 1e-10), "second" = (2 + 1e-10)))
#FAIL
testthat::expect_equal(object = (10 + 1e-6), expected = 10)
testthat::expect_equal(object = 15, expected = 10)
testthat::expect_equal(
object = c("first" = 1, "second" = 2),
expected = c("first" = (1 + 1e-10), "third" = (2 + 1e-10)))
A more relax tests of equality can be done using testthat::expect_equivalent()
ignoring the attributes.
#PASS
testthat::expect_equivalent(object = (10 + 1e-7), expected = 10)
testthat::expect_equivalent(
object = c("first" = 1, "second" = 2),
expected = c("third" = (1 + 1e-10), "fourth" = (2 + 1e-10)))
#PASS
testthat::expect_identical(object = 10, expected = 10)
#FAIL
testthat::expect_identical(object = (10 + 1e-7), expected = 10)
String matching is performed using the testthat::expect_match()
function and regular expression.
actual <- "This is a string!"
#PASS
testthat::expect_match(object = actual, regexp = "This is a string!")
#FAIL
testthat::expect_match(object = actual, regexp = "This is a String!")
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))
})
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")
})
testthat
packagetestthat::test_file()
, or directory, using testthat::test_dir()
testthat::auto_test()
R CMD check