Properties¶
Properties define the expected behavior of your code over a range of inputs. For decorator-based approaches, see the Decorators documentation.
Defining Properties with Property(...)¶
-
Property(predicate: Callable[..., bool | None]): Creates a property object explicitly. Thepredicatefunction receives arguments generated according to the generators passed tofor_all.- If the predicate returns
Falseor throws an error, the property fails. - If the predicate returns
TrueorNone(implicitly returnsNone), the property passes for that input.
from python_proptest import Property, Gen # Property: The sum of two non-negative numbers is non-negative def sum_property(a: float, b: float): assert a + b >= 0 # Using assertions # Or: return a + b >= 0 # Running the property prop = Property(sum_property) prop.set_num_runs(200).for_all(Gen.int(min_value=0, max_value=100), Gen.int(min_value=0, max_value=100)) - If the predicate returns
-
property.set_num_runs(n: int): Configures the number of random test cases to execute whenfor_allis called on aPropertyinstance. Returns thePropertyinstance for chaining. -
property.example(...args: Any): Runs the property's predicate once with the explicitly providedargs. Useful for debugging specific edge cases.def prop_func(a: int, b: int): return a > b prop = Property(prop_func) prop.example(5, 3) # Runs the predicate with a=5, b=3 prop.example(3, 5) # returns False
See Also: run_for_all(), @for_all decorator
Defining and Running Properties with run_for_all(...) (Lambda-Based)¶
-
run_for_all(predicate: Callable[..., bool | None], *generators: Generator[Any]): Suitable for simple property checks that can be expressed as lambdas. This is a concise way to define and immediately check a property. It implicitly creates and runs the property. You don't need to manually create aPropertyobject.from python_proptest import run_for_all, Gen # Property: Reversing a list twice yields the original list def test_double_reverse(): def property_func(arr: list): # Predicate using assertions assert list(reversed(list(reversed(arr)))) == arr return True run_for_all(property_func, Gen.list(Gen.str(min_length=0, max_length=5), min_length=0, max_length=10)) # Property: String concatenation length def test_string_concatenation_length(): def property_func(s1: str, s2: str): # Predicate returning a boolean return len(s1 + s2) == len(s1) + len(s2) run_for_all(property_func, Gen.str(min_length=0, max_length=20), Gen.str(min_length=0, max_length=20)) # Property: Absolute value is non-negative def test_absolute_value_non_negative(): def property_func(num: float): assert abs(num) >= 0 return True run_for_all(property_func, Gen.float()) # Use float generator # Property: Tuple elements follow constraints def test_tuple_constraints(): def property_func(tup: tuple): num, bool_val = tup assert num >= 0 assert num <= 10 assert isinstance(bool_val, bool) return True run_for_all(property_func, Gen.tuple(Gen.int(min_value=0, max_value=10), Gen.bool()))
See Also: Property(), @run_for_all decorator, Generators
Decorator-based Properties¶
python-proptest also supports a decorator-based approach for defining properties, similar to Hypothesis. This is suitable for complex assertions that benefit from explicit parameter signatures. For full documentation, see Decorators.
from python_proptest import for_all, integers, text
@for_all(integers(), integers())
def test_addition_commutativity(x: int, y: int):
"""Test that addition is commutative."""
assert x + y == y + x
@for_all(text(), text())
def test_string_concatenation(s1: str, s2: str):
"""Test string concatenation properties."""
result = s1 + s2
assert len(result) == len(s1) + len(s2)
assert result.startswith(s1)
assert result.endswith(s2)
# Run the tests
test_addition_commutativity()
test_string_concatenation()
Choosing the Right Approach¶
python-proptest provides multiple ways to define property tests. Choose based on your needs:
Use run_for_all for Simple Lambda-Based Tests¶
Suitable for simple property checks that can be expressed as lambdas:
from python_proptest import run_for_all, Gen
def test_simple_properties():
# Type checks
result = run_for_all(
lambda x: isinstance(x, int),
Gen.int(min_value=0, max_value=100)
)
# Range validations
result = run_for_all(
lambda x: 0 <= x <= 100,
Gen.int(min_value=0, max_value=100)
)
# Simple assertions
result = run_for_all(
lambda lst: all(isinstance(x, int) for x in lst),
Gen.list(Gen.int())
)
assert result is True
Use @for_all for Complex Function-Based Tests¶
Suitable for complex assertions that benefit from explicit parameter signatures:
from python_proptest import for_all, integers, text
@for_all(integers(), integers())
def test_complex_math_property(x: int, y: int):
"""Test complex mathematical property with multiple conditions."""
result = x * y + x + y
assert result >= x
assert result >= y
assert result % 2 == (x + y) % 2
@for_all(text(), text())
def test_string_operations(s1: str, s2: str):
"""Test string operations with multiple assertions."""
combined = s1 + s2
assert len(combined) == len(s1) + len(s2)
assert combined.startswith(s1)
assert combined.endswith(s2)
Guidelines¶
- Use
run_for_allfor simple property checks that can be expressed as lambdas - Use
@for_allfor complex assertions that benefit from explicit function signatures - Use
run_for_allfor seed-based reproducibility testing - Use
@for_allfor tests with complex generator transformations - Use
@run_for_allfor dependent generators (chain,aggregate,accumulate)
Configuration Options¶
Number of Runs¶
from python_proptest import Property, Gen
def my_property(x: int):
return x >= 0
# Using Property class
prop = Property(my_property)
prop.set_num_runs(500).for_all(Gen.int())
# Using run_for_all with num_runs parameter
run_for_all(my_property, Gen.int(), num_runs=500)
Seed for Reproducibility¶
from python_proptest import run_for_all, Gen
def my_property(x: int):
return x >= 0
# Using a fixed seed for reproducible tests
run_for_all(my_property, Gen.int(), seed=42)
# Using a string seed
run_for_all(my_property, Gen.int(), seed="my_test_seed")
Error Handling¶
When a property fails, python-proptest will:
- Report the failure with the original failing input
- Attempt shrinking to find a minimal counterexample
- Raise an exception with details about the failure
from python_proptest import run_for_all, Gen, PropertyTestError
def failing_property(x: int):
return x < 100 # This will fail for x >= 100
try:
run_for_all(failing_property, Gen.int())
except PropertyTestError as e:
print(f"Property failed: {e}")
# The error will include the minimal counterexample found through shrinking
Best Practices¶
- Keep properties simple: Each property should test one specific invariant
- Use meaningful names: Name your properties clearly to understand what they test
- Handle edge cases: Consider what happens with empty lists, zero values, etc. See
@examplefor testing specific edge cases. - Use appropriate generators: Choose generators that match your function's expected input domain
- Test both positive and negative cases: Verify that your function behaves correctly in both success and failure scenarios
- Use reproducible seeds: Use
@settings(seed=...)for reproducibility
# Good: Simple, focused property
def test_list_length_after_append():
def property_func(lst: list, item: str):
original_length = len(lst)
lst.append(item)
return len(lst) == original_length + 1
run_for_all(property_func, Gen.list(Gen.str()), Gen.str())
# Good: Testing edge cases
def test_division_by_zero():
def property_func(a: float, b: float):
if b == 0:
# Test that division by zero raises an exception
try:
result = a / b
return False # Should not reach here
except ZeroDivisionError:
return True # Expected behavior
else:
# Test normal division
result = a / b
return isinstance(result, float)
run_for_all(property_func, Gen.float(), Gen.float())
Related Documentation¶
- Decorators - Using
@for_all,@run_for_all,@example,@settings, and@matrix - Generators - Available generators for creating test data
- Combinators - Combining and transforming generators
- Shrinking - How python-proptest finds minimal counterexamples