Property-based testing

Example-based testing asserts one input → one expected output. Property-based testing (PBT) inverts it: you state an invariant that must hold for all inputs — “LRU cache size never exceeds capacity”, “serialize(parse(x)) == x”, “sort(l) is a permutation of l” — and the framework generates hundreds of random cases trying to break it, shrinking any failure down to the minimal reproducing input. One property replaces a wall of example tests, and the adversarial generator finds edge cases humans forget (empty input, unicode, off-by-one boundaries, negative zero).

The canonical lineage:

  • QuickCheck (Claessen & Hughes, 2000, Haskell) — originated the idea: properties as executable specs, random generation, shrinking. Every later tool copies this shape.
  • Hypothesis (Python, MacIver) — the production workhorse: strategy-based generation, database of failing examples replayed across runs, deterministic replay of shrunk failures. Also the engine under pytest’s ecosystem and the reference implementation others now build on.
  • Hegel (hegel.dev, 2026) — the “universal PBT engine” claim: one protocol (hegel-core), language bindings for Rust/Go/TypeScript/OCaml, all built on Hypothesis — same generators and shrinking across languages, so a protocol fuzzer and its implementations in different languages share test cases. @GeoffreyHuntley’s timeline reminder is the capture that opened this trail.
  • Also in the family: fast-check (JS/TS), scalacheck (Scala), proptest (Rust, pre-Hegel), falsum… the pattern is portable everywhere.

Why it matters for agent-era code, tying into Agent tests: a tautological test restates the implementation and never fails; a property cannot be tautological by construction — it’s a claim about the input space, not a replay of the code’s output. For LLM-written code the highest-value properties are the cheap ones to state: roundtrips, invariants, idempotence, metamorphic relations. And failure-mode-first thinking (from the same AGENTS.md rule) is exactly “list the invariants, then generate against them.”

Practical notes:

  • Properties complement, not replace, E2E (Agent tests): PBT nails pure logic and parsers/serializers; E2E nails the wiring. The Golden repo test section wants both.
  • Shrinking is the feature that pays the bills — a 3-element minimal counterexample beats a random 10k-element one.
  • Keep generation realistic or you drown in “not-a-bug” failures; constrain generators to the valid input domain (Hypothesis strategies compose for exactly this).

Sources (captured 2026-09-26): clippings/x-2103800381415551009.md (tweet) · clippings/hegel-dev.md (hegel.dev) · hegel-rust (OSS).

Growing

  • Try Hypothesis on a real ocli/cldb function (a parser or date util) — feel the shrink cycle once before theorizing.
  • Hegel’s cross-language protocol angle: relevant to testing the same wire format consumed by Python + Go services at work?