This blog has relocated to https://coolbutuseless.github.ioand associated packages are now hosted at https://github.com/coolbutuseless.

29 April 2018

mikefc

Assert an expression is unchanged over a code block

Most (all?) testing packages in R are built around the idea of testing a value at a particular moment in time e.g. “Check that a == 2 right now”.

The prior post detailed a function to test an expected changed in value of a particular expression before & after some particular code is run. E.g. I might not know how long a data.frame is, but I do know that it should grow by 2 rows during the execution of some function.

This post shows a simplification of yesterday’s testing function which only tests that a value is unchanged over a block of code.

assert_unchanged()

The following code tests the validity of a statement over a code block.

The statement to test is of the form [after] [logical operator] [before].

E.g.

  • a == a + 1 – when we expect the value of a to be incremented by 1
  • nrow(df) == nrow(df) - 3 – when we expect 3 rows to be removed from the data.frame df
#-----------------------------------------------------------------------------
#' Check the evaluation of a statement is unchanged before/after a block of code
#' @param statement some expression to be evaludated e.g. `nrow(df)`
#' @param code code block to be evaluated
#' @return If statement is unchanged, return an `invisible(TRUE)`, otherwise
#'         raise an error
#-----------------------------------------------------------------------------
assert_unchanged <- function(statement, code) {

  # Capture the statement so we can manipulate it
  exp <- substitute(statement)

  # Evaluate in order
  #  - statement before the block
  #  - the actual code block
  #  - statement after the block
  before <- eval(exp)
  res    <- eval(code)
  after  <- eval(exp)

  # The statement passes if the `before` and `after` values are equal
  statement_passed <- identical(before, after)

  if (!statement_passed) {
    stop("The following statement has changed value: ", deparse(exp), call.=FALSE)
  }

  invisible(TRUE)
}

Test that we can write a code block which passes the assertion i.e. expect that the length of a remains unchanged, and it does.

a <- c(1, 2, 3)

assert_unchanged(
  length(a),  
  {
    a[1] <- 10
  }
)

Test that we can write a code block which causes an error i.e. expect b to remain unchanged, but it doesnt

b <- 2

assert_unchanged(
  b,  
  {
    b <- 3
  }
)

# Error: The following statement has changed value: b

Conclusion

  • Proof-of-concept seems plausible.
  • Extensions
    • Be able to test multiple statements are unchanged over a code block. The call signature of such a function is going to need thinking though i.e. is it more sensible to:
      1. Have a signature like assert_unchanged(code, ...) which shifts the code to be first followed by a varying number of statements, or
      2. Have a call signature of assert_unchanged(...) and automatically interpret the last item in the argument list be interpreted as the code block, and the preceding arguments are the tests? (This is similar to how ensurer does some argument unpacking)
    • More checks needed to see that this behaves nicely when calling functions.
    • More verbose error with the actual evaluated values of the before/after shown e.g. “The following statement has changed value: b. ‘2’ => ‘3’”