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 1nrow(df) == nrow(df) - 3
– when we expect 3 rows to be removed from the data.framedf
#-----------------------------------------------------------------------------
#' 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:
- Have a signature like
assert_unchanged(code, ...)
which shifts thecode
to be first followed by a varying number of statements, or - 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 howensurer
does some argument unpacking)
- Have a signature like
- 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’”
- 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:
Share this post
Twitter
Google+
Facebook
Reddit
LinkedIn
StumbleUpon
Pinterest
Email