Dividing with let_shrink

Learn the let_shrink macro and where it can come in handy to understand the test cases that fail.

We'll cover the following

The let_shrink macro

As we use PropEr, we sometimes get stuck with generators creating huge data structures that take a long time to shrink and often don’t give very interesting results back. This often happens when some very low-probability failure is triggered, meaning that the framework had to generate a lot of data to find it, and has limited chances of shrinking things in a significant manner.

Whenever that happens, the let_shrink([Pattern, ...], [Generator, ...], Expression) is what we need. Let’s take a look at how we can use the generators in practice.

let_shrink([
    a <- list(number()),
    b <- list(number()),
    c <- list(number())
]) do
    a ++ b ++ c
end

The macro looks a lot like a regular let macro, but with a few constraints. The first two arguments must always be lists, and the third argument is an operation where all list elements get combined into one. Here, A, B, and C are three lists filled with integers, and A ++ B ++ C is just a bigger list of integers. The important part is that the program can use any of A, B, or C instead of A ++ B ++ C.

The reason for that is that once a property fails and PropEr tries to shrink the data set, it will instead pick just one of A, B, or C without applying the transformation and return that directly. The let_shrink macro is particularly appropriate for recursive structures, data made through branching, and all kinds of pieces of data that are generated by smashing others together and applying transformations, since taking a part of it is a legitimate way to get a simpler version.

Basically, we’re giving PropEr a way to divide the data up to isolate a failing subset more efficiently.

Example

Let’s take a look at an example of how the macro can be used in properties.

The most common form of let_shrink is the one we’d use on tree data structures. Let’s take a look at the binary tree generator of size N.

The set of commands is:

  1. ExUnit.start()
  2. c "test/pbt_test.exs"
  3. PropCheck.sample_shrink(PbtTest.tree(4)) for the tree generator and PropCheck.sample_shrink(PbtTest.tree_shrink(16)) for the tree_shrink generator.

Get hands-on with 1200+ tech skills courses.