hypothesis review
Hypothesis 6.165.10 is a property-based testing library for Python. A strategy describes valid inputs, `@given` runs the test over generated cases, and the shrinker searches for a smaller counterexample after a failure. Its local example database replays useful failures before exploring new inputs, and state machines cover sequences of operations. The current patch corrects `from_regex` for mixed negative classes, `re.ASCII` complements and case folding, plus optional subpatterns excluded by a custom alphabet. Our installed distribution included py.typed and compiled extensions.
Hypothesis 6.165.10 installed in 0.3 seconds and used 5 MB across two packages in our sandbox, with typed APIs, compiled extensions, and no audit findings. Add it where you can state a property clearly; use plain pytest cases when the behavior is already best described by a short example table.
We installed it
| Install | ✓ · 0.3s | 2 packages on disk · 5 MB |
| Import | ✓ | import hypothesis in 0.60s · compiled extensions · py.typed · requires Python >=3.10 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does hypothesis install cleanly?
Yes. In a fresh container with an empty cache, pip install hypothesis finished in 0.3s, leaving 2 packages and 5 MB on disk. pip-audit reported no known vulnerabilities.
What does hypothesis need to run?
Python >=3.10, and a platform wheel with compiled extensions. In our run import hypothesis succeeded in 0.60s, and the package ships py.typed for type checkers.
hypothesis or pytest: which should you use?
pytest: Use parametrization when a small named table of cases is the clearest specification. Hypothesis 6.165.10 installed in 0.3 seconds and used 5 MB across two packages in our sandbox, with typed APIs, compiled extensions, and no audit findings.
When should you not use hypothesis?
The specification is a short list of named input-output pairs. pytest.mark.parametrize expresses those cases more directly.
Use it if
- The requirement is an invariant or round trip that should hold over many integers, strings, collections, or structured records.
- Parser, serializer, Unicode, or floating-point edge cases are broader than a maintained table of examples.
- A queue, cache, or protocol can be tested as generated operations with an invariant checked after each step.
- A failing generated input should be reduced to a small counterexample that can become a readable regression test.
- The specification is a short list of named input-output pairs. `pytest.mark.parametrize` expresses those cases more directly.
- One test invocation performs costly remote or database work. A single `@given` test calls its body many times, while function-scoped pytest fixtures are shared across those calls.
- CI policy forbids a green test from finding a new input later. Ongoing input exploration is intentional, even though saved failures are replayed.
- You need plausible people, addresses, and demo fixtures. Faker targets realistic-looking data; Hypothesis searches a validity space and then minimizes failures.
- The project is stuck on Python 3.9 or older. Hypothesis 6.165.10 requires Python 3.10 or later.
Setup reality
We installed Hypothesis 6.165.10 in a fresh Python 3.12 Bookworm sandbox. pip took 0.3 seconds and left two packages using 5 MB. The package metadata declares 37 direct dependencies and Python 3.10 or later. import hypothesis worked in 0.60 seconds. The distribution ships py.typed and compiled .so files. pip-audit found zero known vulnerabilities, while the measured package metadata did not provide a license value.
Installing it registers a pytest plugin and introduces local test state. Hypothesis normally stores interesting and failing cases under .hypothesis; it replays those before generating fresh data. That database helps locally but should not be the only record of a repaired bug. Preserve the printed reproduction details from CI, then add a readable @example once the cause is understood. Disposable runners need an explicit database policy if replay across jobs matters.
One decorated test runs the body repeatedly. The default deadline can expose accidental slowness, and heavy filtering can trigger health checks after too many rejected values. Put constraints into strategies instead of relying on assume() for most candidates. A function-scoped pytest fixture is constructed once around the generated test, not once per example, so reset mutable state inside the body or keep external integration work in ordinary tests.
Profiles control example counts, deadlines, phases, and reproducibility for different environments. Optional extras cover NumPy, pandas, Django, and the ghostwriter command. Version 6.165.10 is particularly relevant to regex strategies: from_regex(r'[\W_]'), complements under re.ASCII, and patterns with a zero-repeat branch plus restricted alphabet now follow Python's regex rules. Pin this patch if those generators protect a parser or validator.
Patterns
Check a binary round trip round-trip
from hypothesis import given, strategies as st
@given(st.binary())
def test_round_trip(payload: bytes) -> None:
assert decode(encode(payload)) == payloadA round trip has an expected result for every generated byte string. Also test format requirements so matching encoder and decoder bugs cannot cancel out.
Build valid dataclass instances structured-records
from dataclasses import dataclass
from hypothesis import given, strategies as st
@dataclass
class LineItem:
sku: str
quantity: int
line_items = st.builds(
LineItem,
sku=st.text(min_size=1, max_size=20),
quantity=st.integers(min_value=1, max_value=100),
)
@given(line_items)
def test_total_is_nonnegative(item: LineItem) -> None:
assert price(item) >= 0`st.builds` invokes the real constructor. Express domain limits in its field strategies so generation does not discard mostly invalid objects.
Draw a valid slice range dependent-values
from hypothesis import given, strategies as st
@st.composite
def slices(draw):
values = draw(st.lists(st.integers(), min_size=1, max_size=20))
start = draw(st.integers(0, len(values) - 1))
stop = draw(st.integers(start, len(values)))
return values, start, stop
@given(slices())
def test_slice_size(case) -> None:
values, start, stop = case
assert len(values[start:stop]) == stop - startA composite strategy fits because the index bounds depend on the generated list. Simple draw order also gives the shrinker fewer dependencies to untangle.
Load explicit CI settings ci-profile
import os
from hypothesis import settings
settings.register_profile(
'ci',
max_examples=500,
deadline=None,
derandomize=True,
)
if os.environ.get('CI'):
settings.load_profile('ci')Disabling the deadline accepts variable test time. `derandomize=True` aids repeatability but stops each run from exploring a fresh random sequence.
Keep known edge cases in source regression-example
from hypothesis import example, given, strategies as st
@given(st.text())
@example('')
@example('e\u0301')
def test_slug_has_no_spaces(value: str) -> None:
assert ' ' not in slugify(value)`@example` runs readable cases before generated ones. Keep it after a fix even if the local example database can replay the same input.
Generate complete regex matches regex-fullmatch
import re
from hypothesis import given, strategies as st
slugs = st.from_regex(
re.compile(r'[a-z][a-z0-9-]{0,31}', re.ASCII),
fullmatch=True,
)
@given(slugs)
def test_slug_parser(value: str) -> None:
assert parse_slug(value) == value`fullmatch=True` prevents unrelated prefix or suffix text. Version 6.165.10 fixes several ASCII and negative-category generation errors.
Constrain values before filtering avoid-filtering
from hypothesis import assume, given, strategies as st
@given(st.integers(min_value=1))
def test_reciprocal(value: int) -> None:
assert 0 < 1 / value <= 1
@given(st.integers(), st.integers())
def test_difference(left: int, right: int) -> None:
assume(left != right)
assert left - right != 0A bounded integer strategy wastes no draws. Use `assume` when direct construction of the valid relationship would make the test harder to read.
Explore cache operation sequences state-machine
from hypothesis import strategies as st
from hypothesis.stateful import RuleBasedStateMachine, invariant, rule
class CacheMachine(RuleBasedStateMachine):
def __init__(self):
super().__init__()
self.cache = Cache(capacity=3)
@rule(key=st.text(min_size=1), value=st.integers())
def put(self, key, value):
self.cache.put(key, value)
@invariant()
def stays_within_capacity(self):
assert len(self.cache) <= 3
TestCache = CacheMachine.TestCaseExpose `TestCase` at module scope for pytest collection. The invariant runs throughout generated operation sequences, not only after one fixed scenario.
Replay a printed reproduction blob replay-ci-failure
from hypothesis import given, reproduce_failure, strategies as st
@reproduce_failure('6.165.10', b'PASTE_BLOB_FROM_CI')
@given(st.lists(st.integers()))
def test_parser(values) -> None:
assert parse(dump(values)) == valuesA reproduction blob is tied to a Hypothesis version. Use it for diagnosis, then replace it with a readable `@example` before merging.
Generate NumPy arrays and shapes numpy-arrays
import numpy as np
from hypothesis import given
from hypothesis.extra.numpy import array_shapes, arrays
@given(arrays(
dtype=np.float64,
shape=array_shapes(min_dims=1, max_dims=2, max_side=8),
))
def test_normalize_keeps_shape(values) -> None:
assert normalize(values).shape == values.shapeFloat generation may include NaN and infinity. Exclude them only if the function's stated input contract rejects them.
Search toward larger cases target-extremes
from hypothesis import given, strategies as st, target
@given(st.lists(st.integers(), max_size=200))
def test_deduplicate(values) -> None:
target(len(values), label='input length')
result = deduplicate(values)
assert len(result) <= len(values)`target` biases exploration toward higher observed scores while the property still controls pass or fail. Labels separate multiple optimization goals.
Draw an index after its list interactive-draws
from hypothesis import given, note, strategies as st
@given(st.data())
def test_lookup(data) -> None:
values = data.draw(st.lists(st.integers(), min_size=1), label='values')
index = data.draw(st.integers(0, len(values) - 1), label='index')
note(f'index={index}, size={len(values)}')
assert lookup(values, index) == values[index]`st.data()` permits draws whose shape depends on values already seen in the test body. Labels and notes appear in useful failure output.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| pytest | PyPI | Use parametrization when a small named table of cases is the clearest specification. |
| Faker | PyPI | Use it to produce plausible fixture and demo records rather than minimized adversarial cases. |
| schemathesis | PyPI | Use it when OpenAPI or GraphQL should drive generated requests and response checks. |
| crosshair-tool | PyPI | Try it when contracts and symbolic path exploration fit the function better than generated sampling. |
More testing guides
pytest · chai · vitest · jsdom · playwright · coverage · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

