Pbt hypothesis
Hypothesis-specific patterns for property-based testing in Python — strategies, RuleBasedStateMachine, settings, and Hypothesis ecosystem gotchas. Use this skill whenever the task involves Hypothesis tests, the `hypothesis` package, `@given`, `st.` strategies, `RuleBasedStateMachine`, `@example`, or any Python property-based testing work. This skill pairs with the core `property-based-testing` skill, which handles property discovery and design — load both together for any Hypothesis task. This skill does not re-derive the workflow; it assumes the core skill is loaded and only covers Hypothesis syntax, idioms, and library-specific patterns.From its SKILL.md
npx -y skills add alialavia/pbt-skills --skill pbt-hypothesisAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
9.2 KB, ~2.0k tokens by cl100k_base, as published. Nobody here has run it
Property-Based Testing with Hypothesis (Python)
This skill covers Hypothesis-specific patterns. It assumes the core property-based-testing skill is loaded and you have already worked through the discovery workflow (understand the function → brainstorm properties → name oracles → reject traps). This file picks up at Step 5 of that workflow: turning a chosen property into a Hypothesis test.
If you have not done the discovery work yet, stop and do it first. Hypothesis syntax is easy; finding the right property is hard, and skipping that step produces the tautologies and weak tests this skill exists to prevent.
Reference files
references/strategies.md— Strategy patterns: composite, recursive, FK-aware, shrinking-friendly designsreferences/stateful.md—RuleBasedStateMachine, rules, invariants, preconditions, bundlesreferences/settings.md— Profiles, deadlines, example database, CI tuning
Quickstart by category
A minimal Hypothesis test:
from hypothesis import given, strategies as st
@given(st.lists(st.integers()))
def test_sort_permutation(xs):
"""sort returns a permutation of its input.
Oracle: collections.Counter (independent of my_sort).
"""
from collections import Counter
assert Counter(my_sort(xs)) == Counter(xs)
Key elements every Hypothesis test should have:
@given(...)with a strategy that matches the input domain precisely- A docstring stating the property in English and naming the oracle
- An assertion that fails meaningfully — not just
assert result - Where applicable,
@example(...)decorators pinning known-tricky cases
Strategies: the 30-second tour
For full coverage see references/strategies.md. The essentials:
| Need | Strategy |
|---|---|
| Integer in a range | st.integers(min_value=0, max_value=100) |
| Finite float | st.floats(allow_nan=False, allow_infinity=False) |
| ASCII text | st.text(alphabet=string.printable) |
| List with constraints | st.lists(elem, min_size=1, unique=True) |
| Dict | st.dictionaries(key_strategy, value_strategy) |
| Dataclass / class | st.builds(MyClass, field1=strategy1, ...) |
| Custom structure | @st.composite (see below) |
| One of several | st.one_of(strat1, strat2, ...) |
| Recursive (trees, JSON) | st.recursive(base, lambda c: extend(c), max_leaves=N) |
| From a regex | st.from_regex(r"...", fullmatch=True) |
@st.composite — the workhorse
For any input with internal structure or cross-field constraints:
@st.composite
def valid_dates(draw):
year = draw(st.integers(min_value=1900, max_value=2100))
month = draw(st.integers(min_value=1, max_value=12))
max_day = calendar.monthrange(year, month)[1]
day = draw(st.integers(min_value=1, max_value=max_day))
return datetime(year, month, day)
@given(valid_dates())
def test_date_arithmetic(date):
...
This is dramatically better than st.dates().filter(lambda d: ...) — composite generators shrink well and don't waste budget.
FK-aware generation (relevant for SqlProof-style work)
Generate parent rows first, then draw children from the parent keys:
@st.composite
def order_with_customer(draw):
customers = draw(st.lists(
st.fixed_dictionaries({
"id": st.integers(min_value=1, max_value=1000),
"name": st.text(min_size=1, max_size=50),
}),
unique_by=lambda c: c["id"],
min_size=1, max_size=20,
))
customer_ids = [c["id"] for c in customers]
orders = draw(st.lists(
st.fixed_dictionaries({
"id": st.integers(min_value=1),
"customer_id": st.sampled_from(customer_ids),
"amount": st.decimals(min_value=0, max_value=10000, places=2),
}),
max_size=50,
))
return {"customers": customers, "orders": orders}
The st.sampled_from(parent_keys) step guarantees referential integrity without any filtering.
Stateful testing: RuleBasedStateMachine
For systems with state (databases, caches, classes with mutable instances), flat @given won't find sequence-dependent bugs. Use RuleBasedStateMachine. Quick sketch:
from hypothesis.stateful import RuleBasedStateMachine, rule, invariant, precondition
class StackMachine(RuleBasedStateMachine):
def __init__(self):
super().__init__()
self.stack = MyStack()
self.model = [] # reference model
@rule(x=st.integers())
def push(self, x):
self.stack.push(x)
self.model.append(x)
@precondition(lambda self: self.model)
@rule()
def pop(self):
assert self.stack.pop() == self.model.pop()
@invariant()
def size_matches(self):
assert self.stack.size() == len(self.model)
TestStack = StackMachine.TestCase
For full coverage of Bundle, consumes(), multi-actor patterns, and lifecycle handling: read references/stateful.md.
Hypothesis-specific gotchas
These are mistakes that are syntactically valid Hypothesis but produce bad tests. The general PBT anti-patterns are in the core skill's references/anti-patterns.md; the items below are Hypothesis-specific traps not covered there.
Don't mutate strategy outputs in place
# BAD
@given(st.lists(st.integers()))
def test_mutating(xs):
xs.sort() # mutates the input Hypothesis gave you
# ...
Hypothesis may reuse strategy outputs across runs (especially during shrinking). Mutating them produces nondeterministic test failures. Always copy:
@given(st.lists(st.integers()))
def test_no_mutation(xs):
xs_copy = list(xs)
xs_copy.sort()
# ...
assume() is not free
Each assume(cond) that fails costs Hypothesis an example. If your assumption fails often, you get warned with HealthCheck.filter_too_much — but the warning is the symptom, not the cause. The cause is a strategy that doesn't match the input domain. Fix the strategy, don't suppress the health check.
st.floats() defaults include NaN and infinity
assert a + b == b + a fails for NaN because NaN is not equal to itself. Always use st.floats(allow_nan=False, allow_infinity=False) unless you're explicitly testing NaN behavior.
@example should pin known edge cases
Always layer @example(...) decorators on @given for known-tricky inputs:
@given(st.lists(st.integers()))
@example(xs=[])
@example(xs=[0])
@example(xs=[1, 1, 1])
def test_dedupe(xs):
...
These run in addition to the random search, guaranteeing those cases are always tested.
Deadline issues with slow tests
Hypothesis's default 200ms per-example deadline trips often on tests that hit a database, filesystem, or network. Either disable it for those tests or set a generous deadline:
from hypothesis import settings
from datetime import timedelta
@settings(deadline=None) # or deadline=timedelta(seconds=5)
@given(...)
def test_slow_thing(...): ...
Reproduce failures cleanly
When Hypothesis reports a failure, it prints a @reproduce_failure(...) decorator. Paste it temporarily onto the test to re-run the same counterexample without searching. Don't try to manually reproduce — use the blob.
Commit the example database
Hypothesis records failures to .hypothesis/examples. Commit this directory (or share it via CI cache) so found bugs reproduce immediately on every run, building a free regression suite over time.
Workflow integration
Recall the core workflow:
- Understand the function under test
- Brainstorm ≥5 candidate properties
- Name the oracle for each
- Reject the obvious traps
- Design the strategy ← Hypothesis specifics start here
- Write the test, then critique it ← and continue here
- Use stateful testing when appropriate ←
RuleBasedStateMachineif so
When the property is identified and the oracle is clear, the Hypothesis-specific work is: pick the right strategy (often @st.composite), write the @given and assertion with a docstring stating the property, pin known edge cases with @example, and tune settings if needed. The library is small once you know what you're testing.
What ships with it: 3 files
19.5 KB alongside SKILL.md
references/
- settings.md3.9 KB
- stateful.md8.3 KB
- strategies.md7.2 KB
Gives 0 of the 12 instructions most test skills give in ~2.0k tokens
Counted across 1,201 of the 2,096 authors here whose files we hold, read 2026-09-06
- Write a failing test before writing codein 43 of 1201, across 36 files
- Run the full test suitein 36 of 1201, across 35 files
- Test only one variable per experimentin 34 of 1201, across 17 files
- Read product marketing context before asking questionsin 34 of 1201, across 14 files
- Mock external dependenciesin 34 of 1201, across 30 files
- Define primary, secondary, and guardrail metricsin 33 of 1201, across 16 files
- Pre-determine sample size before startingin 31 of 1201, across 14 files
- Test behavior rather than implementationin 31 of 1201, across 29 files
- Formulate a hypothesis before designing a testin 30 of 1201, across 13 files
- Document every test hypothesis, variant, and resultin 29 of 1201, across 11 files
- Use descriptive test function namesin 25 of 1201, across 21 files
- Commit to the methodology without stopping earlyin 24 of 1201, across 8 files
Said here and by no other author read
- perform discovery workflow before writing tests
- use st.composite for complex input structures
- copy strategy outputs before mutating them
- use st.floats with allow_nan=False and allow_infinity=False
- pin known edge cases using @example decorators
- use RuleBasedStateMachine for stateful systems
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.