2026-03-05
Motivation
Automated Testing and Unit Tests
Testing Functions in R with testthat
Coverage
Package Checks
testing is part of function writing
sometimes we forget to test a special case
also: functions can create side effects and introduce new errors
Unit tests are tests that evaluate one feature of a function
run automatically after you make changes to the code
Unit tests are one part of the larger framework of Test Driven Development
Test Driven Development is an approach to programming where the unit tests are written before the actual code:
Requirements are decided in advance
Code must meet the requirements (and only the requirements)
For a bit of extra work, you get:
testthattestthat: unit testing for R packages
structured R package testing
provides functions to set up and tear down testing environments
runs each set of tests in a clean environment
reports whether each test passes or fails
usethisusethis: helper functions for R package development
set up testthat for your package: usethis::use_testthat()
create new test files: usethis::use_test("test1") creates a new test file
Note: testthat works with packages. This github issue has a good discussion of testing outside of the package framework
Modify your code or tests
Test your package (Ctrl/CMD - Shift - T)
Repeat until all tests pass
Install the testthat package for unit testing and the usethis package (helper functions to make package development easy)
Set the happyR package up to work with testthat
What does your file structure look like now?
A test file consists of
context - a description of the test blocks in the file
one or more test blocks:
test_that(description, {test statements})
testthat has a series of expectation functions:
| function | description |
|---|---|
expect_equal(obj, value) |
Is the object equal to a value? |
expect_error(expr) |
Does the expression produce an error? |
expect_gt(obj, value) |
Is the object greater than the value? |
expect_length(obj, value) |
Does the object have length value? |
See more with help(package = "testthat")
These functions are silent if the expectation is met, and throw an error otherwise.
Expectations are used to construct tests.
Create a test file with usethis::use_test("hello")
Write a set of expectations that test the hello function
Write out the specifications for your function
Create your test file usethis::use_test("foo")
Write tests for your function in tests/testthat/test_foo.R
Write your function and documentation in R/foo.R
Test your package: Ctrl/CMD - Shift - T or use the Build Menu
Function: mymean(x, na.rm)
Required Outcome:
na.rm is true, missing values in x should be removed before calculating the averageFunction: mymean(x, na.rm)
Specific Behavior:
NANA when x has NAs and na.rm = Fsum(x)/length(x) when x has no NA valuessum(x2)/length(x2) wherex2 = x[!is.na(x)] if na.rm = TImportant behaviors:
x <- letters[1:3]
expect_warning(mymean(x)) # This is the minimal test
expect_warning(mymean(x), # This tests for a specific warning
"argument is not numeric or logical: returning NA")
# This tests the value and that a warning is generated
expect_warning(
expect_true(is.na(mymean(x))) # Function returns NA
)NA when x has NAs and na.rm = Fsum(x)/length(x)Modular (by design)
Quick to run
Run in an clean environment
Independent - don’t require outside files (traditionally)
But, we’re statisticians. What about the data?
use tiny test data (dput() is helpful)
read in toy data from files in the test directory
use data included with the package
use data included in base R
download data from elsewhere with a set-up function, delete it with a tear-down function
covrHow many unit tests are enough? Is everything tested?
covr is a package that will:
Run a code coverage report locally for your happyR package using covr::report()
Get a codecov token
Run use_tidy_github_actions(). This will activate all three main workflows: checking the package, running a code coverage report and re-publishing the website
lintr https://lintr.r-lib.org/articles/lintr.htmlprovides stylistic code review: lintr::lint_dir("R")