unitizerWrites To Your Filesystem
all.equalStored Reference Values
unitizer simplifies creation, review, and debugging of
tests in R. It automatically stores R expressions and the values they
produce, so explicit expectations are unnecessary. Every test is easy to
unitizer because testing and using a function
are the same. This encourages non-trivial tests that better represent
Tests fail when the value associated with an expression changes. In interactive mode you are dropped directly into the failing test environment so you may debug it.
unitizer is on CRAN:
It bakes in a lot of contextual help so you can get started without reading all the documentation. Try the demo to get an idea:
Or check out the screencast
unitizer in action.
Are you tired of the
copy-paste R objects into test file dance, or do you use
testthat::expect_equal_to_reference or other snapshot
testing a lot?
unitizer you interactively review your code as you
would when typing it at the R prompt. Then, with a single keystroke, you
unitizer to store the code, and any values, warnings,
or errors it produced, thereby creating a formal regression test.
Do you wish the nature of a test failure was more immediately obvious?
When tests fail, you are shown a proper diff so you can clearly identify how the test failed:
Do you wish that you could start debugging your failed tests without additional set-up work?
unitizer drops you in the test environment so you can
debug why the test failed without further ado:
Do you avoid improvements to your functions because that would require painstakingly updating many tests?
The diffs for the failed tests let you immediately confirm only what you intended changed. Then you can update each test with a single keystroke.
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
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")
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
In favor of
unitizeryou still need to
unitizeand review the tests.
unitizerstores 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.
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
The simplest way to use
unitizer as part of your package
development process is to create a
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
library(unitizer) unitize("unitizer/fastlm.R") unitize("unitizer/cornerCases.R")
The path specification for test files should be relative to the
tests directory as that is what
R CMD check
unitize is run by
R CMD check it
will run in a non-interactive mode that will succeed only if all tests
You can use any folder name for your tests, but if you use
unitize will look for files automatically,
so the following work assuming your working directory is a folder within
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.
unitizerWrites To Your Filesystem
unitized 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.equalStored Reference Values
Once you have created your first
unitize, subsequent calls to
compare the old stored value to the new one using
all.equal. You can change the comparison function by using
unitizer_sect (see tests
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:
<- 14523.2342520 # assignments are not considered tests num.var test_me(num.var) # safe
test_me(14523.2342520) # could be deparsed differently
Similarly issues may arise with non-ASCII characters, so use:
<- "hello\u044F" # assignments are not considered tests chr fun_to_test(chr) # safe
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,
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
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.
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
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
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
In 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
.Last.valuewill not work
stderrduring test evaluation to capture those streams (see details on tests vignette), though we take care to do so responsibly
unitizerinteractions do not pollute it
In particular, you should avoid evaluating tests that invoke
debugged functions, or introducing interactivity by using
readline, or some such. Tests will work, but the
interaction will be challenging because you will have to do it with
Doing so will cause
unitize to quit if any test
expressions throw conditions. See discussion in error
Some base functions are masked at the
quitare masked to give the user an opportunity to cancel the quit action in case they meant to quit from
unitizerinstead of R. Use Q to quit from
unitizer, as you would from
lsis masked with a specialized version for use in
tracebackis masked to report the most recent error in the order presented by the