Everyday computational science
01 Sep 2020 - adolgert
How to call testthat under RStudio in order to debug a unit test.
When I see an error in a unit test, and I need to figure out how to fix it, I’d like to have all of my tools available. There is no reason to be stuck with print-statement debugging.
This entry shows two techniques: how to add convenience functions for calling debuggers and how to use editor breakpoints to trace unit tests.
My projects always have a directory structure that places
tests for the source file R/filename.R
under tests/testthat/test-filename.R
. I make it easier to test a single file
by making functions that take filename.R
as the argument.
# Place in .Rprofile
test_file_path <- function(filepath) {
test_file <- paste("test", filepath, sep = "-")
rprojroot::find_package_root_file("tests", "testthat", test_file)
}
test_progress <- function(filepath, reporter) {
testthat::test_file(
test_file_path(filepath),
reporter = reporter
)
}
# This turns off Hadley's weird unit test messages.
testf <- function(filepath) {
test_progress(
filepath, testthat::ProgressReporter$new(show_praise = FALSE)
)
}
# Drop into debug if the test fails.
testd <- function(filepath) {
test_progress(filepath, testthat::DebugReporter$new())
}
They also turn off the infantilizing “praise” option.
The code above also shows one way to look at stack frames
when a test fails. It calls testthat::test_file
with
the DebugReporter
. This will stop at the failure and
show variables that are defined in a given stack frame.
That’s helpful, but it isn’t as helpful as being able to
use an interactive debugger.
There are two ways to invoke an interactive debugger from
a unit test. One is to insert browser()
commands into the
source code. You will need to execute the function again, or
rebuild the source, in order for the browser()
command to
be invoked. The trouble with this is that you don’t want to
leave browser()
commands in the code by accident, and
accidents will happen.
I’d like to use editor breakpoints instead.
The testthat
library won’t be able to use editor
breakpoints because it usually starts a separate session,
outside RStudio, to run its tests.
If we’re willing to relax our requirement that unit tests
run in a pristine environment, then we can run those tests
ourselves.
In order to run the unit tests ourselves, we need to read the file with unit tests and run those tests in the local environment. A quick way to do this
test_file_trace <- function(filename) {
env <- new.env()
test_that <- function(desc, code) {
eval(substitute(code), env)
}
env$test_that <- test_that
source(filename, env)
}
When we want to debug a particular function, we can
set a breakpoint by left-clicking to the left of
the line numbers in RStudio. Then mark the function
for tracing
with debugonce. Finally run unit tests in that file
using the code above, test_file_trace(filename).
That gives interactive debugging using the browser.
It’s possible to modify this function further so that it picks out tests that match a regular expression.