unitizer
Differ from testthat
?unitizer
requires you to review test outputs and confirm they are as expected.
testthat
requires you to assert what the test outputs should be beforehand.
There are trade-offs between these strategies that we illustrate here, first
with testthat
:
vec <- c(10, -10, 0, .1, Inf, NA) expect_error( log10(letters), "Error in log10\\(letters\\) : non-numeric argument to mathematical function\n" ) expect_equal(log10(vec), c(1, NaN, -Inf, -1, Inf, NA)) expect_warning(log10(vec), "NaNs produced")
And with unitizer
:
vec <- c(10, -10, 0, .1, Inf, NA) log10(letters) # input error log10(vec) # succeed with warnings
These two unit test implementations are functionally equivalent. There are benefits to both approaches. In favor of unitizer
:
In favor of testthat
:
unitizer
you still need to
unitize
and review the tests.unitizer
stores reference values
in binary RDSes (see Collaborating with Unitizer).unitizer
is particularly convenient when the tests return complex objects (e.g
as lm
does) and/or produce conditions. There is no need for complicated
assertions involving deparsed objects, or different workflows for snapshots.
testthat
tests to unitizer
If you have a stable set of tests it is probably not worth trying to convert them to unitizer
unless you expect the code those tests cover to change substantially. If you do decide to convert tests you can use the provided testthat_translate*
functions (see ?testthat_translate_file
).
unitizer
and PackagesThe simplest way to use unitizer
as part of your package development process is to create a tests/unitizer
folder for all your unitizer
test scripts. Here is a sample test structure from the demo package:
unitizer.fastlm/ # top level package directory R/ tests/ run.R # <- calls `unitize` or `unitize_dir` unitizer/ fastlm.R cornerCases.R
And this is what the tests/run.R
file would look like
library(unitizer) unitize("unitizer/fastlm.R") unitize("unitizer/cornerCases.R")
or equivalently
library(unitizer) unitize_dir("unitizer")
The path specification for test files should be relative to the tests
directory as that is what R CMD check
uses. When unitize
is run by R CMD
check
it will run in a non-interactive mode that will succeed only if all tests
pass.
You can use any folder name for your tests, but if you use "tests/unitizer"
unitize
will look for files automatically, so the following work assuming your
working directory is a folder within the package:
unitize_dir() # same as `unitize_dir("unitizer")` unitize("fast") # same as `unitize("fastlm.R")` unitize() # Will prompt for a file to `unitize`
Remember to include unitizer
as a "suggests" package in your DESCRIPTION file.
unitizer
unitizer
Writes To Your FilesystemThe unitize
d tests need to be saved someplace, and the default action is to
save to the same directory as the test file. You will always be prompted by
unitizer
before it writes to your file system. See storing unitized
tests for implications
and alternatives.
all.equal
Stored Reference ValuesOnce you have created your first unitizer
with unitize
, subsequent calls to
unitize
will compare the old stored value to the new one using all.equal
.
You can change the comparison function by using unitizer_sect
(see tests
vignette).
This means you need to be careful with expressions that may deparse differently on different machines or with different settings. Unstable deparsing will prevent tests from matching their previously stored evaluations.
For example, in order to avoid round issues with numerics, it is better to use:
num.var <- 14523.2342520 # assignments are not considered tests test_me(num.var) # safe
Instead of:
test_me(14523.2342520) # could be deparsed differently
Similarly issues may arise with non-ASCII characters, so use:
chr <- "hello\u044F" # assignments are not considered tests fun_to_test(chr) # safe
Instead of:
fun_to_test("hello\u044F") # could be deparsed differently
This issue does not affect the result of running the test as that is never deparsed.
unitizer
can track and manage many aspects of state to make your tests more
reproducible. For example, unitizer
can reset your R package search path to
what is is found in a fresh R session prior to running tests to avoid conflicts
with whatever libraries you happen to have loaded at the time. Your session
state is restored when unitizer
exits. The following aspects of state can be
actively tracked and managed:
State management is turned off by default because it requires tracing some base
functions which is against CRAN policy, and generally affects session state in
uncommon ways. If you wish to enable this feature use unitize(...,
state='suggested')
or options(unitizer.state='suggested')
. For more details
including potential pitfalls see ?unitizerState
and the reproducible tests
vignette.
browser
/debug
/recover
If you enter the interactive browser as e.g. invoked by debug
you should
exit it by allowing evaluation to complete (e.g. by hitting "c" until control
returns to the unitizer
prompt). If you instead hit "Q" while in browser mode
you will completely exit the unitizer
session losing any modifications you
made to the tests under review.
Tests that modify objects by reference are not perfectly suited for use with unitizer
. The tests will work fine, but unitizer
will only be able to show you the most recent version of the reference object when you review a test, not what it was like when the test was evaluated. This is only an issue with reference objects that are modified (e.g. environments, RC objects, data.table
modified with :=
or set*
).
unitizer
Is ComplexIn order to re-create the feel of the R prompt within unitizer
we resorted to a fair bit of trickery. For the most part this should be transparent to the user, but you should be aware it exists in the event something unexpected happens that exposes it. Here is a non-exhaustive list of some of the tricky things we do:
.Last.value
will not workstdout
and stderr
during test evaluation to capture those streams
(see details on tests vignette), though we take care to
do so responsiblyunitizer
interactions do
not pollute itIn particular, you should avoid evaluating tests that invoke debug
ged
functions, or introducing interactivity by using something like
options(error=recover)
, or readline
, or some such. Tests will work, but the
interaction will be challenging because you will have to do it with stderr
and
stdout
captured...
unitize
within try
/ tryCatch
BlocksDoing so will cause unitize
to quit if any test expressions throw conditions. See discussion in error handling.
Some base functions are masked at the unitizer
prompt:
q
and quit
are masked to give the user an opportunity to cancel the quit
action in case they meant to quit from unitizer
instead of R. Use Q to
quit from unitizer
, as you would from browser
.ls
is masked with a specialized version for use in unitizer
.traceback
is masked to report the most recent error in the order presented
by the unitizer
prompt.See miscellaneous topics vignette.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.