---
title: "unitizer - Interactive R Unit Tests"
author: "Brodie Gaslam"
output:
    rmarkdown::html_vignette:
        toc: true
        css: styles.css

vignette: >
  %\VignetteIndexEntry{1 - Introduction}
  %\VignetteEngine{knitr::rmarkdown}
  %\usepackage[utf8]{inputenc}
---

```{r child='./rmdhunks/intro.Rmd'}
```

## How Does `unitizer` Differ from `testthat`?

### Testing Style

`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`:

* Tests are easy to write.
* Tests with non-trivial outputs are easy to write, which encourages more
  realistic testing of functionality.
* Conditions are captured automatically, with no need for special handling.
* You can immediately review failing tests in an interactive environment.
* Updating tests when function output legitimately changes is easy.

In favor of `testthat`:

* The tests are self documenting; expected results are obvious.
* Once you write the test you are done; with `unitizer` you still need to
  `unitize` and review the tests.
* Tests are usually all-plain text, whereas `unitizer` stores reference values
  in binary RDSes (see [Collaborating with Unitizer](u5_miscellaneous.html#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.

### Converting `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 Packages

The 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.

## Things You Should Know About `unitizer`

### `unitizer` Writes To Your Filesystem

The `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](u5_miscellaneous.html#storing-unitized-tests) for implications
and alternatives.

### Tests Pass If They `all.equal` Stored Reference Values

Once 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](u2_tests.html)).

### Test Expressions Are Stored Deparsed

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](u2_tests.html#matching-tests) their previously
stored evaluations.

For example, in order to avoid round issues with numerics, it is better to use:

```{r, eval=FALSE}
num.var <- 14523.2342520  # assignments are not considered tests
test_me(num.var)          # safe
```

Instead of:

```{r, eval=FALSE}
test_me(14523.2342520)    # could be deparsed differently
```

Similarly issues may arise with non-ASCII characters, so use:

```{r eval=FALSE}
chr <- "hello\u044F"      # assignments are not considered tests
fun_to_test(chr)          # safe
```

Instead of:

```{r eval=FALSE}
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.

### Increase Reproducibility with Advanced State Management

`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:

* Search path (including removing the global environment from search path)
* Random seed
* Working directory
* Options
* Loaded namespaces

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](u4_reproducible-tests.html).

### Beware of `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.

### Reference Objects

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 Complex

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 do:

* Each tests is evaluated in its own environment, a child of the previous test's
  environment; because R looks up objects in parent environments it appears that
  all tests are evaluated in one environment (see [interactive environment
  vignette](u3_interactive-env.html))
* We [mask some base functions](#masked-functions).
* `.Last.value` will not work
* We sink `stdout` and `stderr` during test evaluation to capture those streams
  (see [details on tests vignette](u2_tests.html)), though we take care to
  do so responsibly
* We parse the test file and extract comments so that they can be attached to
  the correct test for review
* The history file is temporary replaced so that your `unitizer` interactions do
  not pollute it

### Avoid Tests That Require User Input

In 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...

### Avoid running `unitize` within `try` / `tryCatch` Blocks

Doing so will cause `unitize` to quit if any test expressions throw conditions.  See discussion in [error handling](u5_miscellaneous.html#running-unitize-within-error-handling-blocks).

### Masked Functions

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](u5_miscellaneous.html#overriden-functions).

