liveBook · Manning

Property-based testing

As explained in Learn Haskell by Example

Property testing checks whether program behavior satisfies general properties across automatically generated or varied inputs.

Property-based testing checks general, computable characteristics of program behavior rather than only predetermined examples. A property is commonly a Boolean-valued function: it can compare values, enforce rules on data, or combine other properties. Randomly generated inputs exercise the property repeatedly, increasing the chance of finding errors without handcrafting every test case. section 9.1 section 9.1.1

A useful property states what must remain true, not the exact answer for one input. For a sorting function, every output should be ordered; a predicate can recursively check adjacent list elements and then test any candidate sorting function against it. section 9.1.1

Listing 9.1 Predicates on the sortedness of lists
sorted :: Ord a => [a] -> Bool
sorted [] = True      #1
sorted [_] = True     #1
sorted (x : y : xs) = x <= y && sorted (y : xs)  #2

sorts :: Ord a => ([a] -> [a]) -> [a] -> Bool
sorts f input = sorted $ f input  #3

The predicates separate the rule that defines a sorted list from the function being tested, making the same property reusable across implementations. section 9.1.1

A basic property test combines four parts:

  • A function under test and a predicate that evaluates its result. section 9.1.5
  • A random input action and a chosen number of test runs. section 9.1.5
  • A counterexample report that prints the failing input instead of continuing silently. section 9.1.5

ROT13 symmetry and alphabet closure

ROT13 can be tested with the property that applying it twice returns the original string, using generated ASCII strings. Alphabet rotation can be tested for closure: given a nonempty alphabet and a character selected from it, the rotated character must still belong to that alphabet. Tailored generators are needed when inputs have relationships or preconditions. section 9.2 section 9.2

A small custom framework reveals practical limits: generators must be written and wired up for each type, inputs cannot easily have preconditions, and failures may not provide a conventional result. QuickCheck addresses these issues with property combinators, debugging support, type-directed random generation, preconditions, and shrinking of failing values. Its Arbitrary type class supplies generation and simplification behavior, while generators vary value complexity with an internal size. section 9.1.5 section 9.3 section 9.6

In a Stack project, QuickCheck can be added as a dependency and used to collect and run module properties automatically with quickCheckAll. Naming property tests with a prop prefix helps distinguish them from example-based unit tests. section 9.3 section 9.2 section 9.6

How Learn Haskell by Example covers it

How deep it goes
Builds a generic random-test action, then explains QuickCheck generators, properties, shrinking, and automatic collection.
Examples
Uses sorted lists, ROT13 symmetry, and alphabet-rotation closure with a constrained generator.
What it assumes
Introduces property testing from basic Boolean predicates while working with Haskell functions and IO-based randomness.
What you can do after
Apply it via QuickCheck to write properties, generate inputs, shrink failures, and run collected checks.
How it connects
Connects properties to random generation, IO actions, type classes, QuickCheck, Arbitrary, shrinking, and unit tests.

Also explained in

Compare how 8 books explain property-based testing →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Haskell by Example. The section links point to the parts of the book it draws on.