Property-Based Testing with PropEr, Erlang, and Elixir by Fred Hebert

Property-Based Testing with PropEr, Erlang, and Elixir by Fred Hebert

Author:Fred Hebert
Language: eng
Format: epub, pdf
Tags: Pragmatic Bookshelf
Publisher: Pragmatic Bookshelf


Dividing with ?LETSHRINK

As you use PropEr, you may find yourself 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 ?LETSHRINK([Pattern, ...], [Generator, ...], Expression) is what you need. In practice, we use the generator like this:

​ Erlang

​ ?LETSHRINK([A,B,C], [​list​(​number​()), ​list​(​number​()), ​list​(​number​())],

​ A ++ B ++ C)

​ Elixir

​ 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 any of A, B or C can be used by the program 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. ?LETSHRINK 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.

The most common form of ?LETSHRINK is the one you’d use on tree data structures. For a binary tree generator of size N, we’d write something like this:

​ Erlang code/Shrinking/erlang/pbt/test/prop_shrink.erl

​ ​tree​(N) ​when​ N =< 1 ->

​ {leaf, ​number​()};

​ ​tree​(N) ->

​ PerBranch = N ​div​ 2,

​ {branch, ​tree​(PerBranch), ​tree​(PerBranch)}.

​ Elixir code/Shrinking/elixir/pbt/test/pbt_test.exs

​ ​def​ tree(n) ​when​ n <= 1 ​do​

​ {​:leaf​, number()}

​ ​end​

​ ​def​ tree(n) ​do​

​ per_branch = div(n, 2)

​ {​:branch​, tree(per_branch), tree(per_branch)}

​ ​end​

If you run sampleshrink/1 on it, you’ll find out that the elements within the tree shrink, but the tree itself stays the same size:

​ ​1>​​ ​​proper_gen:sampleshrink(prop_shrink:tree(4)).​

​ {branch,{branch,{leaf,13},{leaf,0.6154862580810709}},

​ {branch,{leaf,8},{leaf,-3}}}

​ {branch,{branch,{leaf,0},{leaf,0.6154862580810709}},

​ {branch,{leaf,8},{leaf,-3}}}

​ {branch,{branch,{leaf,0},{leaf,-6}},{branch,{leaf,8},{leaf,-3}}}

​ {branch,{branch,{leaf,0},{leaf,0}},{branch,{leaf,8},{leaf,-3}}}

​ {branch,{branch,{leaf,0},{leaf,0}},{branch,{leaf,0},{leaf,-3}}}

​ {branch,{branch,{leaf,0},{leaf,0}},{branch,{leaf,0},{leaf,0}}}

Each of the trees in the sample contains exactly four entries: all the integers tend toward zero, but the structure itself has a fixed size. The obvious way to get the tree to shrink is through parameterizing it with the Size variable obtained from the ?SIZED(Var, Exp) macro. But if the failure requires internal tree elements to remain large while the tree structure itself is small, then the chances are fewer that shrinking by size only would work well.

Instead, if we use ?LETSHRINK, we can get a more shrink-friendly version:

​ Erlang code/Shrinking/erlang/pbt/test/prop_shrink.erl

​ ​tree_shrink​(N) ​when​ N =< 1 ->

​ {leaf, ​number​()};

​ ​tree_shrink​(N)



Download



Copyright Disclaimer:
This site does not store any files on its server. We only index and link to content provided by other sites. Please contact the content providers to delete copyright contents if any and email us, we'll remove relevant links or contents immediately.