// Testing · 08
Property-Based Testing
Assert a rule that must always hold, across many generated inputs.
How it fits together
- Random generator uses / calls Property (1 generate x)
- Property uses / calls Holds for all cases (2 all hold)
- Property fails / error path Counterexample (2 violated)
Spot it
- Edge cases keep appearing after release
- Hand-picked examples miss empty or huge inputs
- Round-trip bugs in parsing or serialising
It costs
Good properties take thought, and failures must be read as data, not as examples.
In code
using FsCheck.Xunit;using Shouldly;public sealed class MoneyFormatTests{ [Property(MaxTest = 500)] public void Parse_AfterFormat_ReturnsSameCents(long cents) { // Arrange var text = Money.Format(cents); // Act var parsed = Money.Parse(text); // Assert parsed.ShouldBe(cents); }}Go deeperThe full explanation and what to ask for · 1 min readExpandCollapse
An example test says: for this input, expect this output. A property says: for every input in this class, this relation holds. The framework generates hundreds of inputs and tries to falsify the property, so you state the rule rather than guess the cases. Good properties are invariants (sorting keeps the length), round trips (decode of encode returns the original) and oracles (a fast version agrees with a slow, obvious one).
When a case fails, shrinking reduces it to a smaller counterexample: a 40-element list with a bug becomes the two-element list that still fails. That minimal case is usually the bug itself. Keep the seed so a failure can be reproduced, and save important shrunk cases as plain example tests so they never regress.
The trap is a property that restates the implementation, so the test and the code are wrong together. Generators must cover the real domain: an unconstrained text generator tests your parser on garbage, not on the input you receive. When NOT to: a rule with one or two meaningful cases, or one whose expected output is hard to state without rebuilding the code, is clearer as an example.
Ask for it
- property
- generator
- invariant
- shrinking
- round-trip property
- test oracle
Add property tests for the Money parser: for any generated amount of cents, Format then Parse returns the same cents, and Parse never throws on arbitrary text. Keep the five example tests as regression cases. Report the shrunk input for any failure.