Introduction to Shrinking

Get introduced to shrinking and the basic concepts of how to manipulate the process in PropEr.

We'll cover the following

Shrinking

A critical component of property-based testing is shrinking. Shrinking is the mechanism by which a property-based testing framework can be told how to simplify failure cases enough to let us figure out exactly what the minimal reproducible case is. While finding complex obtuse cases is worthwhile, being able to reduce failing inputs to a simple counterexample truly is the killer feature.

But there are some cases where PropEr can’t do what we need. Either it can’t shrink large data structures well enough to be understandable, or it’s not shrinking them the way we want it to. In this chapter, learn about two different macros to help us handle things:

  1. ?SHRINK
  2. LETSHRINK

These macros help us give the framework hints about what we want it to do. But first, we have to see how shrinking works at a high level.

How shrinking works

In general, we can think of shrinking as the framework attempting to bring the data generator closer to its own zero-point, and successfully doing so as long as the property fails. Zero for a generator is somewhat arbitrary, but if we play with the default generators a bit by calling proper_gen:sampleshrink on them in the shell, notice the following:

  • A number tends to shrink from floating-point values toward integers, and integers tend to shrink toward the number 0.
  • Binaries tend to shrink from things full of bytes toward the empty binary.
  • Lists tend to shrink toward the empty list.
  • Elements ([A,B,C]) will shrink toward the value A.

In short, data structures that contain other data tend to empty themselves, and other values try to find a neutral point. The nice aspect of this is that as custom generators are built from other generators, the shrinking is inherited, and a custom generator may get its own shrinking for free: a map full of people records made of strings and numbers will see the strings get shorter and simpler, the numbers will get closer to zero, and the map will get fewer and fewer elements until only the components essential to trigger a failure are left.

In short, data structures that contain other data tend to empty themselves, and other values try to find a neutral point. The nice aspect of this is that as custom generators are built from other generators, the shrinking is inherited, and a custom generator can get its own shrinking for free. For example, a map full of people’s records made of strings and numbers will see the strings get shorter and simpler, the numbers will get closer to zero, and the map will retain fewer and fewer elements until only the components essential to trigger a failure are left.

These cases, even if they are rare, require the ?SHRINK macro.

Get hands-on with 1200+ tech skills courses.