Unit testing is the technique of writing tests that assess low-level functionality against requirements.
In reports, databases, and code we typically encode business rules, conventions, and our own conventions. Bringing these into reusable functions means less code reproduction, no variances between team members, and lower time to change.
All of these are verifiably correct and so can be tested. Therefore, why test them manually, or why risk someone “tweaking” the code and changing the rules without people knowing? Your unit tests save you time over the long run, and protect against unexpected behaviour changes.
Reconciliations can also be done inside your unit tests. If you have “canonical answers”, you can test new transformations against these to ensure you're consistent.
I'm not a developer so this may be the wrong approach but here are the scenarios I write tests for:
Also, if you're struggling to test because it's got really complex inputs, outputs, or intermediate calculations, consider breaking up the code and rewriting. It's more likely that things are going to go wrong and you won't know if something is particularly complex.
Let's build a sample function:
myfunc<-function(a=1,b=2,c="blah"){
stopifnot(is.numeric(a), is.numeric(b),
is.character(c))
d<-if(a<0){
a*b
}else{
a/b
}
e<-paste0(c,d)
return(e)
}
Let's write some tests (in a file tests/testthat/test-myfunc.r
)
# Add a high-level name for group of tests, typically the function name
context("myfunc")
# Simplest test
test_that("Defaults return expected result",{
result<-myfunc()
check<-"blah0.5"
expect_equal(result,check)
})
# Vector test
test_that("Basic vectorisation works",{
result<-myfunc(a=c(1,1),b=c(2,2), c=c("blah","blah"))
check<-c("blah0.5","blah0.5")
expect_equal(result,check)
})
# Non-uniform vectorisation test
test_that("Complex vectorisation works",{
result<-myfunc(a=c(1,1),b=c(2,2))
check<-c("blah0.5","blah0.5")
expect_equal(result,check)
})
# Test a different condition
test_that("Negative a values result in multiplication",{
result<-myfunc(a=c(-1,-2),b=c(2,2))
check<-c("blah-2","blah-4")
expect_equal(result,check)
})
# Test a different condition
test_that("a=0 values result in 0",{
result<-myfunc(a=0)
check<-c("blah0")
expect_equal(result,check)
})
# Test some duff inputs
test_that("errors expectedly",{
expect_error(myfunc(a="0"))
expect_error(myfunc(b="0"))
expect_error(myfunc(c=0))
})
There are a lot more expectation functions you can use and you can make your own.