liveBook · Manning

Property-based testing

As explained in Functional Design and Architecture: Examples in Haskell

Property-based testing evaluates software by checking general behavioral properties across automatically generated inputs rather than relying only on hand-written example cases.

Property-based testing checks whether general invariants hold across generated inputs instead of asserting only a few hand-picked results. It is usually a black-box form of functional testing: define the behavior that must always hold, generate cases, and let failures challenge that claim. section 15.1 section 15.1.2

The technique fits small, pure algorithms especially well. A pure algorithm can be treated as a data transformation with inherent mathematical or behavioral properties; generated inputs cover many cases that would be tedious to enumerate manually. When a property fails, the framework should shrink the failing value toward a minimal counterexample. section 15.1.2 section 15.3

Checking a squaring property

Rather than list expected outputs, assert that a squaring function returns x * x for generated Integer values. This checks a class of values, but not every value: ordinary generation concentrates on reasonably small or edge-range inputs. A malformed implementation that fails only beyond 1,000,000 can still pass 100 tests, so generation limits and success counts must match the risk being tested. section 15.1.2 section 15.1.2

Generated data must also respect the domain. For a configurable labyrinth generator, choose arbitrary dimensions, discard values outside the permitted range, run the scenario, and assert that the resulting labyrinth has valid bounds. This tests the parameterized generator rather than merely repeating a random action several times. section 15.1.3 section 15.1.3

Listing 15.5 Adjusting the test data generator
...
$ monadicIO $ do
   x :: Int <- pick arbitrary                        #1
   y :: Int <- pick arbitrary
  
   pre (x >= 4 && x <= 10 && y >= 4 && y <= 10)      #2
  
   let action = generateLabyrinth (x, y) 3 3         #3
  
   eLab <- run (try (runLangL coreRuntime action))   #4
  
   case eLab of                                      #5
     Left (err :: SomeException) -> assert False
     Right lab -> do
       let (x, y) = getBounds lab
       assert (x * y >= 16 && x * y <= 100)

Shows arbitrary dimension selection, preconditions that constrain valid bounds, scenario execution, and an assertion over the generated labyrinth. section 15.1.3

Effectful scenarios require extra setup. A property can run IO actions, create a runtime before each test, interpret the scenario, capture exceptions, and assert conditions on the result. This is workable for a free-monadic scenario, though property-based testing remains less naturally suited to IO-bound code than to compact pure logic. section 15.1.3 section 15.3

Use property-based tests effectively by:

  • Stating an invariant or contract, not just a set of expected examples. section 15.1.2
  • Generating inputs broadly enough to expose meaningful failures, while recognizing that finite runs cannot prove the absence of subtle bugs. section 15.1.2
  • Constraining generated inputs to values accepted by the logic under test. section 15.1.3
  • Treating failures as counterexamples and using shrinking to make them easier to diagnose. section 15.1.2

How Functional Design and Architecture: Examples in Haskell covers it

How deep it goes
Explains invariant encoding, random generation, shrinking, thresholds, constrained inputs, and IO-backed scenario setup; does not guarantee freedom from subtle bugs.
Examples
Uses squaring and malformed-square functions, plus generated labyrinth bounds under a free-monadic runtime.
What it assumes
Assumes familiarity with functional testing and pure algorithms while demonstrating property definitions and generated-input checks.
What you can do after
Apply it via QuickCheck and Hspec to express properties, constrain generators, run scenarios, and inspect counterexamples.
How it connects
Connects functional testing, black-box testing, pure functions, QuickCheck, Hspec, free monads, and IO.

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 Functional Design and Architecture: Examples in Haskell. The section links point to the parts of the book it draws on.