Property-Based Testing

A track of P30 · Testing & Mocking.

Property-based testing from the inside: state an invariant, generate hundreds of inputs to hunt a counterexample, and shrink it to the minimal failing case - the hypothesis engine, built by hand.

Every example test is a bet that the input you chose is the one that matters. Property-based testing takes the other side of that bet. Instead of "for hello, encode-then-decode gives hello back", you assert the rule for all strings - decode(encode(s)) == s - and let a generator throw hundreds of inputs at it, including the empty string, the ten-character run, and the string that already contains a digit: precisely the cases you would never write by hand. hypothesis is the tool teams reach for; this track builds its engine so you can see there is no magic in it.

That engine is three moves. Generate: a strategy is just a function that returns a random input. Check: for_all draws inputs and returns the first one that breaks the property - the search stops the moment it finds a failure. Shrink: a raw counterexample is usually a huge, unreadable value, so the engine reduces it - halving, decrementing - to the smallest input that still fails. "It broke at 73912" becomes "it broke at 5", which is the difference between noise and a bug report. You build for_all and shrink in a few lines each.

Two cautions come with the power. A property test is only as good as its strategy: a generator that never produces long runs or unicode proves nothing while looking thorough. And it turns flaky over non-deterministic code unless you seed the randomness. So property tests do not replace example tests - they hunt the edges you did not imagine, while your example tests pin the specific behaviours you meant and keep a failure reproducible. Keep both.

Generatea strategy makes inputsCheckfor_all finds a failureShrinkreduce to the minimum
The property-based loop: invent inputs, catch a failure, shrink it to something readable.