liveBook · Manning

Property-based testing

As explained in Functional Programming in Scala, Second Edition

Property-based testing checks general behavioral properties against automatically generated input values rather than relying only on hand-written examples.

Property-based testing specifies behavior separately from individual test cases. Instead of collecting only examples, state an invariant—a relationship that should hold for arbitrary inputs—and let generators produce inputs that satisfy the stated constraints. A property remains valuable because it describes what an API must preserve, not merely what happened to work for selected values. section 8.0 section 8.6

A generator such as Gen[List[Int]] is not a list; it is a value that can create many lists. Prop.forAll combines a generator with an A => Boolean predicate. Running the resulting property searches generated cases for a falsification, reports a counterexample when one is found, and otherwise records that the tested cases passed. section 8.1 section 8.6

Listing 8.1 ScalaCheck properties
import org.scalacheck.{Gen, Prop}
val intList = Gen.listOf(Gen.choose(0, 100))     #1
val prop =                                       #2
  Prop.forAll(intList)(ns =>
    ns.reverse.reverse == ns) &&                 #3
  Prop.forAll(intList)(ns =>
    ns.headOption == ns.reverse.lastOption)      #4
val failingProp =
  Prop.forAll(intList)(ns => ns.reverse == ns)   #5

This listing contrasts two useful claims about reverse: reversing twice restores a list, and a list’s first optional element matches the reversed list’s last. The deliberately false claim that every list equals its reverse shows why generated counterexamples matter. section 8.1

Properties form a partial specification. For List.reverse, double reversal and the headOption/lastOption relationship constrain behavior without describing an implementation. For List.sum, useful properties can relate a list to its reversal or characterize special inputs such as lists whose elements are all zero. section 8.1

Refining a maximum property

A first attempt says that a list’s maximum is at least every element. Generated empty lists expose a hidden precondition: List.max crashes on them. Rather than weakening the check after the fact, express the domain explicitly with a nonempty-list generator, then test that no generated element exceeds max. section 8.2.2 section 8.6

Generated testing does not replace ordinary examples. A fixed Boolean claim should use a direct verification primitive rather than repeatedly generating an irrelevant unit value. This distinction also sharpens results: a generated property may be unfalsified after some cases, whereas a finite domain tested completely can be proved. section 8.2.3 section 8.6

A practical property-testing design needs composable representations for both data and claims:

  • Use Gen and sized SGen values to generate arbitrary inputs, including lists whose size varies with the test size. section 8.6 section 8.6
  • Combine properties with && and ||, preserving useful failure context through tags. section 8.1 section 8.6
  • Minimize failing inputs when possible so a counterexample is easier to investigate. section 8.6
  • Use exhaustive generation only for finite, practically small domains; otherwise random sampling can establish only that no failure was found. section 8.6 section 8.6

The same approach checks algebraic laws. Parallel computations can be tested against laws such as mapping identity or mapping over unit, while generators vary both input values and executor services. If the generic expression obscures intent, introduce helpers such as forAllPar or verifyPar rather than accepting a technically expressive but unreadable test. section 8.2.3 section 8.0

How Functional Programming in Scala, Second Edition covers it

How deep it goes
Develops a testing library through generators, properties, combinators, result statuses, and exhaustive generation.
Examples
Uses list reversal, sum, maximum, sorting, Boolean conjunction, and parallel-computation laws as properties.
What it assumes
Introduces property-based testing from unfamiliarity while expressing implementations and examples in Scala.
What you can do after
Build it from scratch with generators and properties that report falsifications, passes, or proofs.
How it connects
Connects API algebra and laws with generators, combinators, parallel computations, and map and flatMap.

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 Programming in Scala, Second Edition. The section links point to the parts of the book it draws on.