E: Test Which Expectations are Met

Description

Performs a unit test and summarises the results.

Usage

E(
  expr,
  ...,
  value_comparer = getOption("realtest_value_comparer", identical),
  sides_comparer = getOption("realtest_sides_comparer", sides_similar),
  postprocessor = getOption("realtest_postprocessor", failstop)
)

Arguments

expr

an expression to be recorded (via R) and and compared with the prototypes

...

a sequence of 1 or more (possibly named) prototypes constructed via R or P (objects which are not of class realtest_descriptor will be passed to P); arguments whose names start with a dot (like .label=value) can be used to introduce metadata (e.g., additional details in natural language)

value_comparer

a two-argument function used (unless overridden by the prototype) to compare the values with each other, e.g., identical or all.equal

sides_comparer

a two-argument function used (unless overridden by the prototype) to compare the side effects (essentially: two lists) with each other, e.g., sides_similar or ignore_differences

postprocessor

a function to call on the generated realtest_result, e.g., failstop

Details

Each R expression has a range of possible effects. The direct effect corresponds to the value generated by evaluating the expression. Side effects may include errors, warnings, text printed out on the console, etc., see P and R.

Arguments passed via ... whose names do not start with a dot should be objects of class realtest_descriptor (otherwise they are passed to P). They define the prototypes against which the object generated by expr will be tested.

value_comparer and sides_comparer are 2-ary functions that return TRUE if two objects/side effect lists are equivalent and a character string summarising the differences (or any other kind or object) otherwise.

The test case is considered as met, whenever value_comparer(prototype[["value"]], object[["value"]]) and sides_comparer(prototype[["sides"]], object[["sides"]]) are both TRUE for some prototype. The comparers may be overridden on a per-prototype basis, though. If prototype[["value_comparer"]] or prototype[["sides_comparer"]] are defined, these are used instead.

Value

The function creates an object of class realtest_result, which is a named list with at least the following components:

  • object – an object of class realtest_descriptor, ultimately R(expr),

  • prototypes – a (possibly named) list of objects of class realtest_descriptor that were passed via ...,

  • matches – a (possibly empty) numeric vector of the indexes of the prototypes matching the object (can be named),

  • .dotted.names – copied as-is from the arguments of the same name.

This object is then passed to the postprocessor which itself becomes responsible for generating the output value to be returned by the current function (and, e.g., throwing an error if the test fails).

Author(s)

Marek Gagolewski

See Also

The official online manual of realtest at https://realtest.gagolewski.com/

Related functions: P, R, test_dir

Examples

# the default result postprocessor throws an error on a failed test:
E(E(sqrt(4), P(7)), P(error=TRUE, stdout=TRUE))
E(sqrt(4), 2.0)  # the same as E(sqrt(4), P(2.0))
E(sin(pi), 0.0, value_comparer=all.equal)  # almost-equal
E(
  sample(c("head", "tail"), 1),
  .description="this call has two possible outcomes",
  "head",  # first prototype
  "tail"   # second prototype
)
E(sqrt(-1), P(NaN, warning=TRUE))  # a warning is expected
E(sqrt(-1), NaN, sides_comparer=ignore_differences) # do not test side effects
E(sqrt(-1), P(NaN, warning=NA))  # ignore warnings
E(
  paste0(1:2, 1:3),                  # expression to test - concatenation
  .description="partial recycling",  # info - what behaviour are we testing?
  best=P(                            # what we yearn for (ideally)
    c("11", "22", "13"),
    warning="longer object length is not a multiple of shorter object length"
  ),
  pass=c("11", "22", "13"),          # this is the behaviour we have now
  bad=P(error=TRUE)                  # avoid regression
)
e <- E(sin(pi), best=0.0, pass=P(0.0, value_comparer=all.equal),
  .comment="well, this is not a symbolic language after all...")
print(e)
## $object
## $value
## [1] 1.224647e-16
## 
## $expr
## sin(pi)
## 
## attr(,"class")
## [1] "realtest_descriptor" "realtest"           
## 
## $prototypes
## $prototypes$best
## $value
## [1] 0
## 
## $differences
## [1] "objects are different"
## 
## attr(,"class")
## [1] "realtest_descriptor" "realtest"           
## 
## $prototypes$pass
## $value
## [1] 0
## 
## $value_comparer
## function (target, current, ...) 
## UseMethod("all.equal")
## <bytecode: 0x5609a1b8ca60>
## <environment: namespace:base>
## 
## attr(,"class")
## [1] "realtest_descriptor" "realtest"           
## 
## 
## $matches
## pass 
##    2 
## 
## $.comment
## [1] "well, this is not a symbolic language after all..."
## 
## attr(,"class")
## [1] "realtest_result" "realtest"