Property-based testing

Testing backends Mid-level Python 3.13+ PostgreSQL 17+ Reviewed By Best Latest Apps

Predict the output

Does the round-trip property hold for 500 random strings of a and b?

Python 3.13
import random


def encode(s: str) -> str:
    out, i = [], 0
    while i < len(s):
        j = i
        while j < len(s) and s[j] == s[i]:
            j += 1
        out.append(f"{j - i}{s[i]}")
        i = j
    return "".join(out)


def decode(s: str) -> str:
    out, num = [], ""
    for ch in s:
        if ch.isdigit():
            num += ch
        else:
            out.append(ch * int(num))
            num = ""
    return "".join(out)


rng = random.Random(1)
samples = [
    "".join(rng.choice("ab") for _ in range(rng.randrange(30)))
    for _ in range(500)
]
ok = all(decode(encode(t)) == t for t in samples)
print(ok)

Options
Hint 1 of 1

What does a seeded Random do?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A True

Correct. Run-length encoding then decoding returns the original for these inputs.

A property test checks a rule over many generated inputs instead of a few examples. decode(encode(s)) == s holds for all 500 seeded strings, including the empty one.

Why not the others?
  • B False. Every run is encoded with its count and decoded back.
  • C A ValueError on the empty string. encode('') returns '' and decode('') returns ''.
  • D It depends on the run. The generator is seeded with 1.

30-second answer

Instead of hand-picking examples, you state a property that should hold for all inputs, like 'decode(encode(x)) == x' or 'a sorted list is ordered and has the same items', and a tool generates hundreds of inputs, shrinking any failure to a minimal example. It pays off for parsers, serialisers, money and date arithmetic, idempotency ('applying twice equals applying once') and state machines.

Practise saying it · 25 s. No recording, just a timer

Property-based tests.

Say your answer out loud, then check which of these points you covered:

  • Properties over examples
  • Generated inputs, shrinking
  • Round-trip, invariants
  • Parsers, money, idempotency

Deep dive

Hypothesis is the standard Python library; it shrinks failures automatically.

Round-trip, invariant and 'same as a simple reference implementation' are common property shapes.

Keep a seed or example database so failures replay.

Combine with a few readable example tests.

A round-trip property checked on 500 seeded inputs Ran on Python 3.13.5 · mypy --strict
Python 3.13
import random


def encode(s: str) -> str:
    out, i = [], 0
    while i < len(s):
        j = i
        while j < len(s) and s[j] == s[i]:
            j += 1
        out.append(f"{j - i}{s[i]}")
        i = j
    return "".join(out)


def decode(s: str) -> str:
    out, num = [], ""
    for ch in s:
        if ch.isdigit():
            num += ch
        else:
            out.append(ch * int(num))
            num = ""
    return "".join(out)


rng = random.Random(1)
samples = [
    "".join(rng.choice("ab") for _ in range(rng.randrange(30)))
    for _ in range(500)
]
ok = all(decode(encode(t)) == t for t in samples)
print(ok)

Common mistakes

  • Properties that restate the implementation.
  • Generating inputs no real caller can send, then 'fixing' them.
  • Only example tests for parsers.

Senior take & follow-ups

Define it, give property shapes, say where it pays off and how failures replay.

Follow-up questions

Think of your answer first, then open the hint.

  1. Give an idempotency property.

    Show hint

    f(f(x)) == f(x) for a normaliser or upsert.

  2. What is shrinking?

    Show hint

    Reducing a failing input to the smallest that still fails.

Suggest a fix if something here is wrong or out of date.