Python Hypothesis Strategies for Numbers, Strings, and Lists
python hypothesis strategies for numbers strings and lists: Use Hypothesis strategies for numbers, strings, and lists to generate diverse test inputs and catch edge ca...
Why Hypothesis Strategies Matter for Test Data
When you write unit tests with fixed inputs, you only verify the behavior for the exact values you chose. Edge cases like negative numbers, empty strings, or very large lists often go unnoticed until they reach production. Hypothesis solves this by generating random inputs from declarative strategies. For python hypothesis strategies for numbers strings and lists, the library provides dedicated strategies that let you control the domain while still exploring a wide range of possibilities. The key is to define strategies that match the real input space of your code, then let Hypothesis find the inputs that break it.
Generating Numbers with Integers, Floats, and Decimals
The integers() strategy generates arbitrary Python integers. You can constrain the range with min_value and max_value:
from hypothesis import given, strategies as st @given(st.integers(min_value=1, max_value=100)) def test_positive_integer(n): assert n > 0
Without bounds, integers() can produce arbitrarily large values, which is useful for testing overflow or performance. For floating-point numbers, floats() offers more control. You can exclude nan and infinity by default, but you can allow them explicitly:
@given(st.floats(allow_nan=False, allow_infinity=False)) def test_finite_float(x): assert math.isfinite(x)
floats() also accepts min_value and max_value, but be careful: the bounds are inclusive, and the strategy may still generate values very close to zero. For decimal numbers, decimals() works similarly but uses Python's Decimal type, which is important for financial calculations where precision matters.
Creating Strings with Text and Binary Strategies
The text() strategy generates Unicode strings. By default, it includes a wide range of characters, but you can restrict the alphabet to a specific set:
@given(st.text(alphabet="abc123", min_size=1, max_size=10)) def test_alphanumeric_string(s): assert set(s).issubset(set("abc123"))
The min_size and max_size parameters control length. If you need to test ASCII-only strings, you can use st.characters(codec="ascii") as the alphabet. For binary data, st.binary() generates bytes objects. It supports min_size and max_size as well:
@given(st.binary(min_size=0, max_size=100)) def test_binary_data(data): assert len(data) <= 100
When you need strings that match a specific format, you can compose strategies using st.from_regex() or st.one_of(), but for most cases text() with a custom alphabet is sufficient.
Building Lists with the List Strategy
The lists() strategy generates lists of elements produced by another strategy. The elements parameter is required:
@given(st.lists(st.integers(), min_size=0, max_size=20)) def test_integer_list(lst): assert all(isinstance(x, int) for x in lst)
You can enforce uniqueness with unique=True or unique_by for more complex criteria. For example, to generate a list of unique strings:
@given(st.lists(st.text(min_size=1), unique=True, max_size=10)) def test_unique_strings(lst): assert len(lst) == len(set(lst))
Lists are often combined with other strategies. For instance, to generate a list of tuples representing coordinates:
@given(st.lists(st.tuples(st.integers(), st.integers()))) def test_coordinates(coords): for x, y in coords: assert isinstance(x, int) and isinstance(y, int)
Combining Strategies for Realistic Data
Real-world inputs are rarely a single number or string. Hypothesis allows you to build complex data structures by nesting strategies. The @given decorator can accept multiple strategies, each passed as a separate argument:
@given(st.integers(), st.text(), st.lists(st.booleans())) def test_mixed_inputs(num, text, flags): pass
You can also build dictionaries using st.dictionaries():
@given(st.dictionaries(st.text(), st.integers())) def test_dict_of_counts(d): assert all(isinstance(v, int) for v in d.values())
For a structured object like a user record, combine strategies into a single dictionary:
user_strategy = st.fixed_dictionaries({ "name": st.text(min_size=1, max_size=50), "age": st.integers(min_value=0, max_value=120), "email": st.emails(), })
Then use @given(user_strategy) to generate complete records. This approach keeps your test data realistic and reduces the chance of generating nonsensical combinations.
Controlling Shrinking and Test Performance
One of Hypothesis's most valuable features is shrinking. When a test fails, Hypothesis automatically reduces the input to a minimal failing example. This works best when your strategies are well-constrained. For example, if you generate an unbounded integer and the test fails only for very large values, shrinking will find the smallest integer that still fails. However, overly broad strategies can slow down both generation and shrinking. Use max_size on lists and strings to keep the search space manageable. Also, avoid using filter() excessively because it can cause Hypothesis to reject many generated examples, increasing the time to find valid ones. Instead, use assume() inside the test or design strategies that directly produce valid values.
Common Pitfalls and Maintainability
A frequent mistake is making strategies too restrictive, which defeats the purpose of property-based testing. If you always generate the same small set of values, you miss the edge cases. Conversely, overly permissive strategies can generate inputs that are irrelevant to your code's logic, leading to tests that fail for unrelated reasons. Keep strategies close to the actual domain. For example, if a function expects a positive integer, use min_value=1 rather than filtering after generation. Another pitfall is reusing a strategy across many tests without adjusting constraints; this can cause tests to become interdependent. Define strategies as module-level constants when they are shared, but override parameters when a test needs a specific variation. This makes your test suite easier to maintain because the input space is explicit and consistent.