5 Unit Tests
Write unit tests for your custom functions using testthat.
5.1 Set up testhat
Use the following code to set up testing for your package. this makes a directory called tests
that contains a file called testthat.R
and a directory called testthat
where you will keep your testing scripts. It will also add some lines to the DESCRIPTION
file, so make sure that file doesnโt have unsaved changes before you run this.
5.2 Set up test
Next, weโll follow the directions from the output text and call use_test()
to initialise a basic test file and open it for editing.
A new file (tests/testthat/apa_t_pair.R
) will open with the following text. It you run it, you should get a message saying it passed.
Test passed ๐ธ
Change the default label for this test. You can label these whatever you want (it really doesnโt matter); I usually start with โdefaultsโ and test the most basic example to make sure the function defaults work as expected. Delete the demo code between the curly brackets.
Set x
and y
as two small static vectors (donโt use rnorm()
or anything that makes random numbers). Then assign the value of apa_t_pair(x, y)
to result
and the expected result to expected
. Finally, use the function expect_equal()
to compare the obtained and expected result.
tests/testthat/apa_t_pair.R
test_that("defaults", {
x <- c(1,2,3,4,5)
y <- c(2,3,2,5,6)
result <- apa_t_pair(x, y)
expected <- "A paired-samples t-test was conducted to compare the DV between level 1 (M = 3.0, SD = 1.6) and level 2 (M = 3.6, SD = 1.8). There was a non-significant difference; t(4) = -1.50, p = 0.208."
expect_equal(result, expected)
})
Test passed ๐
That particular example was significance. Letโs add a version that does show a significant difference and make sure the text changes appropriately.
tests/testthat/apa_t_pair.R
test_that("defaults-sig", {
x <- c(1,2,1,3,1)
y <- c(5,3,2,5,6)
result <- apa_t_pair(x, y)
expected <- "A paired-samples t-test was conducted to compare the DV between level 1 (M = 1.6, SD = 0.9) and level 2 (M = 4.2, SD = 1.6). There was a significant difference; t(4) = -3.20, p = 0.033."
expect_equal(result, expected)
})
Test passed ๐ฅ
No letโs change the default values for the DV and labels.
tests/testthat/apa_t_pair.R
test_that("non-defaults", {
x <- c(1,2,1,3,1)
y <- c(5,3,2,5,6)
result <- apa_t_pair(x, y, dv = "the score", "Group A", "Group B")
expected <- "A paired-samples t-test was conducted to compare the score between Group A (M = 1.6, SD = 0.9) and Group B (M = 4.2, SD = 1.6). There was a significant difference; t(4) = -3.20, p = 0.033."
expect_equal(result, expected)
})
Test passed ๐ธ
5.3 Test-driven development
You can use tests to help you develop your package. First, think of something you want to add or change. Then write a test that checks if your function is doing that new thing. It will, of course, fail, but then your task is to alter the function code until the test passes.
For example, it would be nice to give a custom error message if x and y are identical, since this is almost always a mistake and you canโt calculate any test statistics.
A paired-samples t-test was conducted to compare the DV between level 1 (M = 1.8, SD = 1.3) and level 2 (M = 1.8, SD = 1.3). There was a NAsignificant difference; t(4) = NaN, p = NaN.
You may get an error above instead of โThere was a NAsignificant difference; t(4) = NaN, p = NaN.โ, this means that youโve changed a default setting, but Iโm having trouble figuring out what (one of my computers returns an error and two return the text above). Iโll update this when I figure it out. Regardless, we want to replace that opaque error with an intelligible error.
The first step is to write a test that fails. Wrap the code apa_t_pair(x, x)
inside the function expect_error()
, which will pass if the code produces the specified error message and fail if it doesnโt.
tests/testthat/apa_t_pair.R
Error in `reporter$stop_if_needed()`:
! Test failed
โโ Failure ('<text>:5'): same x and y โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
`apa_t_pair(x, x)` did not throw an error.
The function expect_error()
can use regular expressions to test for patterns. For example, "is not allowed$"
will match any string that ends with โis not allowedโ. Regex is powerful, but can be tricky to use. If you want to test if the returned error text is exactly the same as the regexp
string, set fixed = TRUE
.
Now we can edit the function apa_t_pair()
until the test passes. Add a few lines of code at the start of the function to check if all of the values in x
are the same as all of the values in y
, and display an error message if they are.
R/apa_t_pair.R
apa_t_pair <- function(x, y,
dv = "the DV",
level1 = "level 1",
level2 = "level 2") {
# warn about identical values
if (all(x == y)) {
stop("x and y cannot be identical")
}
t_results <- t.test(x, y, paired = TRUE)
template <- "A paired-samples t-test was conducted to compare {dv} between {level1} (M = {mean1}, SD = {sd1}) and {level2} (M = {mean2}, SD = {sd2}). There was a {non}significant difference; t({df}) = {t_value}, p = {p_value}."
glue::glue(
template,
mean1 = round0(mean(x), 1),
sd1 = round0(sd(x), 1),
mean2 = round0(mean(y), 1),
sd2 = round0(sd(y), 1),
non = ifelse(t_results$p.value < .05, "", "non-"),
df = round0(t_results$parameter, 0),
t_value = round0(t_results$statistic,2),
p_value = round0(t_results$p.value, 3)
)
}
Now the test should pass.
tests/testthat/apa_t_pair.R
Test passed ๐ธ
Now that we have 4 tests for this function, we can run them all by clicking on Run Tests
in the upper right corner of the source pane. If all goes well, youโll see the following in the Build tab:
==> Testing R file using 'testthat'
โน Loading demopkg
โโ Testing test-apa_t_pair.R โโโโโ
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 4 ] Done!
Test complete
5.4 Testing data
You can also set up tests for a data object.
Letโs check that the data()
function can load this data set and that it has the expected dimensions. We can also check if the column sex
is a factor.
5.5 Multiple tests
You can run all of the tests for all of the functions in your package by clicking on Test
in the Build Tab or typing devtools::test()
in the console. Here, youโll get one row for each test file (usually one test file per function) and a summary of how many tests Failed (F), gave unexpected warnings (W), were skipped (S), or passed (OK).
==> devtools::test()
โน Testing demopkg
โ | F W S OK | Context
โ | 4 | apa_t_pair
โ | 3 | self_res_att
โโ Results โโโโโโโโโโโโโโโโโโโโโโโ
Duration: 0.1 s
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 7 ]
Sometimes you want to set tests to skip because they take too long or cause problems when you submit a package to CRAN. Testthat has a number of functions that start with skip
that allow you to skip any following tests under different circumstances.
You can also set up tests for a data object.
Letโs check that the data()
function can load this data set and that it has the expected dimensions.
5.6 Glossary
term | definition |
---|---|
default-values | |
non-significant | |
panes | RStudio is arranged with four window "panes". |
vector | A type of data structure that collects values with the same data type, like T/F values, numbers, or strings. |
5.7 Further Resources
- Testing Basics from Wickham (2015)
- testthat
5.8 Further Practice
Set up tests for
round0
. Make sure that this function gives the expected result for different values ofdigits
and rounds values appropriately. Remember that the results youโre trying to match is a character data type, not a numeric value.Use test-driven development to add a custom error message to
apa_t_pair()
if the length ofx
andy
are not the same.Add further sense checks that the column types are as expected for
self_res_att
.Add unit tests for any other functions youโve created.