From Examples to Properties
From Examples to Properties
Example tests assert one input; property tests assert a rule across a generated space:
from hypothesis import given, strategies as st
def canonical(s): # the function under test
return "-".join(s.lower().split())
@given(st.text())
def test_idempotent(s):
once = canonical(s)
assert canonical(once) == once # normalizing twice = normalizing once
@given(st.lists(st.integers(), min_size=1))
def test_sort_preserves(xs):
assert sorted(xs) == sorted(reversed(xs))
Hypothesis runs many generated examples per call, and on failure it shrinks to a minimal counterexample (often a short string or small list) — the main advantage over hand-picked cases. Regressions auto-save: any failing example is replayed on every future run until the bug is fixed.
Building Strategies
Building Strategies
from hypothesis import given, strategies as st, assume
@st.composite
def user(draw):
name = draw(st.text(min_size=1, max_size=20))
age = draw(st.integers(min_value=0, max_value=130))
return {"name": name, "age": age}
@given(user())
def test_roundtrip(u):
parsed = parse(serialize(u))
assume(parsed is not None) # discard invalid draws, don't assert-pass
assert parsed == u
Composable builders cover most needs: st.one_of for unions, st.builds(Class, ...) for objects, st.sampled_from for enums, st.recursive for nested structures. Constrain with min_size/max_size rather than slicing results, and prefer assume for rare preconditions — but keep rejection rates low or Hypothesis reports a filtered_too_much health check.
Pitfalls and Interview Angles
Pitfalls and Interview Angles
- Tests must be pure. Reading the clock, network, or a shared DB makes properties flaky; inject fakes or seed the dependency.
- The database directory
.hypothesis/holds passing examples and failing regressions — keep it out of VCS or commit it deliberately so CI replays the same failures. - Health checks.
failed_health_check,too_slow,filter_too_muchall mean the strategy or assumption logic is misdesigned, not that your code is correct. - Stateful testing.
RuleBasedStateMachinegenerates command sequences with@rule/@preconditionfor caches, queues, and DB layers where single-call properties are not enough.
Interview angle: name three cache properties (hit returns original value, size never exceeds bound, eviction keeps LRU order) and explain why a two-character shrunk counterexample teaches more than a 4 KB one.
Quiz
1. What is the main benefit of Hypothesis shrinking?
2. When should you use assume() instead of asserting a condition?
3. What does a filtered_too_much health check indicate?
Flashcards
Question
Property-based test
Click to reveal answer
Answer
Asserts an invariant over generated inputs; Hypothesis runs many examples and shrinks failures.
Question
@st.composite
Click to reveal answer
Answer
Builds custom strategies by drawing sub-values step by step.
Question
assume()
Click to reveal answer
Answer
Discards draws that violate preconditions instead of failing the test.
Question
Hypothesis database
Click to reveal answer
Answer
Local .hypothesis dir replaying failing examples and shrinking history on every run.
Revision Notes
Key Takeaways
- 1. Properties (idempotence, round-trip) find bugs examples never think to try
- 2. Strategies compose; keep rejection rates low to satisfy health checks
- 3. Lock fixed regressions with @example and decide deliberately whether the database ships in CI
Interview Tips
- • Offer concrete properties for a real component, not the abstract definition of PBT
- • Explain shrinking with a mini counterexample story and mention stateful testing for sequence bugs
Cheat Sheet
@given(st.integers()) generates+shrinks; @example pins regressions; assume() for preconditions; @st.composite custom draws; .hypothesis/ replays failures.