liveBook · Manning

QuickCheck

As explained in Functional Design and Architecture: Examples in Haskell

QuickCheck is a library for property-based testing that generates test inputs and checks specified properties against them.

QuickCheck turns a testing requirement into a property and checks that property against automatically generated inputs. It suits small, pure algorithms whose input space is too broad for hand-written cases. A failing run produces a counterexample, and shrinking aims to reduce that failure to a minimal case. section 15.1.2

Testing a square property

The property x * x == square x checks a whole class of Integer values rather than a few expected results. QuickCheck commonly tries values near zero and type-range edges, so a malformed implementation can fail quickly when it mishandles ordinary inputs. section 15.1.2

spec :: Spec
spec = prop "square" $                   #1
  \x -> (malformedSquare x == x * x)     #2
  
-- Test output:
> Falsified (after 2 tests): 1

Inspect the compact Hspec property and its falsification output, then compare it with the malformed implementation that passes after hiding a defect beyond the generated range. section 15.1.2

Use QuickCheck effectively by matching generation and execution to the system under test:

  • Increase the accepted run count with withMaxSuccess when more generated cases are useful, while recognizing that finite testing cannot cover every Integer. section 15.1.2
  • Run IO-based properties with monadicIO and run, including scenarios executed through real interpreters and a prepared runtime. section 15.1.3
  • Generate arbitrary parameters with pick arbitrary, then use pre to discard values outside the generator’s valid bounds. 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)

Follow the constrained generator example: choose dimensions, reject invalid values, run the interpreted action, and assert the resulting labyrinth bounds. section 15.1.3

How Functional Design and Architecture: Examples in Haskell covers it

How deep it goes
Explains generation, shrinking, bounded success counts, IO execution, and constrained inputs, but stops short of exhaustive verification.
Examples
Uses malformed square functions and labyrinth generation with bounded dimensions, exits, wormholes, and runtime interpretation.
What it assumes
Assumes familiarity with Haskell functions and properties while demonstrating Hspec, IO actions, monadic execution, and runtime setup.
What you can do after
Diagnoses logical defects through falsified properties and tests interpreted scenarios with generated or deliberately constrained inputs.
How it connects
Connects property-based testing with Hspec, pure functions, IO actions, free monads, interpreters, and test-data generation.

Also explained in

Compare how 3 books explain QuickCheck →

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.