Building tests for your functions

Building tests for your functions

Another requirement of developing functions for the package is that they have some testing built in. Testing is the process of specifying the expected resuts of your function given a fixed input. Once a proper set of tests for a function has been built you can modify the function and the tests will inform you if the behaviour has changed. This can be extremely helpful when developing a colleciton of functions which are interdependant, as it will alert you to when the behavior of a function changes during development even if you were not currently changing that function.

The standard for test frameworks in R is testthat which is an R package that comes with a set of functions for constructing tests and integrates with devtools so that the tests are run every time the package is built. Tests must be written into files with names beginning with test_ which are then placed in the tests/testthat folder relative to the package root. A typical convention is to have one test file per function (as with function writing).

This document will not cover the principles of unit-testing here, there are many resources for this - (see here) - a simple example test is shown below.

context("Must calculate the cumulative hazard over [0, tmax] for piecewise constant model")

test_that("`pwe_H`", {
  # Generate output - function vectorises with 'time' arg, so we use a vector
  # of length > 1 to test this functionality
  output <- pwe_H(time = 1:10, cut.pts = c(0, 3, 10, Inf), haz.rates = 1:5)

  # check class
  expect_is(output, "numeric")
  # check values
  expect_equal(output, c(2, 4, 6, 9, 12, 15, 18, 21, 24, 27.0001144483108))
})

The call to context()lets us preface the following test with a statement of the functional requirement. This is helpful when the automated testing happens as this title will help direct us to any failing test in the console output. It’s also good practice to explicitly state the functional requirement so any other developers reading these tests are clear on what you intended the function to do (and not do). After context() there is a test_that("", {}) chunk where we lay out our expectations of the function. Schematically we generate a fixed output for a fixed input passed through the function we are testing, and we then check our expectations on that output such that they satisfy our requirement laid out above in context().

Tests can be run interactively using the devtools::test() function.