Decorators¶
python-proptest provides several decorators to enhance property-based testing with additional features like examples, settings, and matrix testing.
Overview¶
The decorators work together to provide a flexible and powerful testing experience:
@for_all- Core property-based testing decorator@run_for_all- Versatile decorator for dependent generators@example- Provides specific example values to test@settings- Configures test parameters like number of runs and seed@matrix- Provides exhaustive Cartesian product testing of fixed inputs
For information on generators to use with these decorators, see the Generators documentation.
@for_all¶
The core decorator for property-based testing. It automatically detects the testing framework (pytest, unittest, or standalone) and adapts accordingly.
Basic Usage¶
from python_proptest import for_all, Gen
@for_all(Gen.int(), Gen.str())
def test_property(x: int, s: str):
assert isinstance(x, int)
assert isinstance(s, str)
assert len(s) >= 0
# Run the test
test_property()
With pytest¶
import pytest
from python_proptest import for_all, Gen
class TestMathProperties:
@for_all(Gen.int(), Gen.int())
def test_addition_commutativity(self, x: int, y: int):
assert x + y == y + x
With unittest¶
import unittest
from python_proptest import for_all, Gen
class TestMathProperties(unittest.TestCase):
@for_all(Gen.int(), Gen.int())
def test_addition_commutativity(self, x: int, y: int):
self.assertEqual(x + y, y + x)
Parameters¶
*generators: Variable number of generators for function argumentsnum_runs: Number of test runs (default: 100)seed: Random seed for reproducibility (default: None)
See Also: @run_for_all for dependent generators, @example for specific test cases, @settings for configuration
@run_for_all¶
A versatile decorator that works in three different modes, combining the power of property-based testing with flexible syntax. Unlike @for_all, it doesn't unpack generator values, making it ideal for working with complex generators like chain, aggregate, and accumulate.
Mode 1: Function Call (Traditional)¶
Use run_for_all as a function to explicitly run property tests:
from python_proptest import run_for_all, Gen
def test_addition():
def check(x, y):
return x + y == y + x
result = run_for_all(check, Gen.int(0, 100), Gen.int(0, 100), num_runs=100)
assert result is True
Mode 2: Test Method Decorator¶
Decorate test methods to have property tests executed by the test framework:
import unittest
from python_proptest import run_for_all, Gen
class TestProperties(unittest.TestCase):
@run_for_all(Gen.int(0, 100), Gen.int(0, 100), num_runs=50)
def test_addition_commutative(self, x, y):
self.assertEqual(x + y, y + x)
Mode 3: Nested Function Decorator (Auto-Execute)¶
The most powerful mode - decorate nested functions inside test methods and they execute immediately:
import unittest
from python_proptest import run_for_all, Gen
class TestChain(unittest.TestCase):
def test_chain_dependency(self):
gen = Gen.chain(Gen.int(1, 10), lambda x: Gen.int(x, x + 10))
@run_for_all(gen, num_runs=20, seed=42)
def check_dependency(pair):
base, dependent = pair
self.assertGreaterEqual(dependent, base)
self.assertLessEqual(dependent, base + 10)
# No explicit call needed - the property test already ran!
Working with Complex Generators¶
@run_for_all is perfect for generators that return tuples or complex structures. For detailed documentation on these combinators, see Dependent Generation Combinators.
With chain¶
Gen.chain() creates dependent tuples where each value depends on the previous:
def test_dependent_generation(self):
# chain returns (base_value, dependent_value) tuple
gen = Gen.chain(
Gen.int(1, 100),
lambda x: Gen.int(x, x + 50)
)
@run_for_all(gen, num_runs=50)
def check_range(pair):
start, end = pair
self.assertGreaterEqual(end, start)
self.assertLessEqual(end, start + 50)
With aggregate¶
Gen.aggregate() generates sequences where each element depends on the previous:
def test_increasing_sequence(self):
# aggregate returns a list of dependent values
gen = Gen.aggregate(
Gen.int(0, 10),
lambda n: Gen.int(n, n + 5),
min_size=5,
max_size=10
)
@run_for_all(gen, num_runs=30)
def check_sequence(values):
self.assertGreaterEqual(len(values), 5)
# Check each value >= previous
for i in range(1, len(values)):
self.assertGreaterEqual(values[i], values[i - 1])
With accumulate¶
Gen.accumulate() generates the final value after N dependent steps:
def test_final_value(self):
# accumulate returns only the final value after N steps
gen = Gen.accumulate(
Gen.int(10, 20),
lambda n: Gen.int(n + 1, n + 5),
min_size=10,
max_size=10
)
@run_for_all(gen, num_runs=25)
def check_final(final_value):
# After 10 steps of +1 to +5, should be >= initial + 10
self.assertGreaterEqual(final_value, 20)
Comparison: @for_all vs @run_for_all¶
# @for_all unpacks the tuple arguments
@for_all(Gen.int(1, 10), Gen.int(1, 10))
def test_with_for_all(self, x, y):
# Gets two separate arguments: x and y
assert x <= y
# @run_for_all with chain - receives tuple as-is
@run_for_all(Gen.chain(Gen.int(1, 10), lambda x: Gen.int(x, x + 10)))
def test_with_run_for_all(self, pair):
# Gets tuple: (base, dependent)
base, dependent = pair
assert base <= dependent
Parameters¶
*generators: One or more generators (when used as decorator, generators come first)num_runs: Number of test runs (default: 100)seed: Random seed for reproducibility (default: None)
When to Use @run_for_all¶
Use @run_for_all when:
- Working with chain, aggregate, or accumulate combinators
- You want auto-execution in nested function contexts
- You prefer explicit control over value unpacking
- Testing properties that operate on tuples or complex structures
Use @for_all when:
- You want automatic argument unpacking
- Working with simple, independent generators
- Following traditional property testing patterns
See Also: @for_all, Dependent Generation Combinators, @settings
@example¶
Provides specific example values that are tested before random generation. Examples are useful for testing edge cases, known good values, or debugging specific scenarios.
Basic Usage¶
from python_proptest import for_all, Gen, example
@for_all(Gen.int(), Gen.str())
@example(0, "")
@example(42, "hello")
def test_property(x: int, s: str):
assert isinstance(x, int)
assert isinstance(s, str)
Multiple Examples¶
@for_all(Gen.int(), Gen.str())
@example(0, "")
@example(1, "a")
@example(-1, "negative")
def test_property(x: int, s: str):
assert isinstance(x, int)
assert isinstance(s, str)
Examples with Different Types¶
@for_all(Gen.int(), Gen.list(Gen.int()))
@example(0, [])
@example(1, [1])
@example(2, [1, 2])
def test_list_property(x: int, lst: list):
assert isinstance(x, int)
assert isinstance(lst, list)
Parameters¶
*values: Variable number of values matching the function parameters
See Also: @for_all, @matrix, Best Practices
@settings¶
Configures test parameters like number of runs and random seed. Settings override the defaults provided to @for_all.
Basic Usage¶
from python_proptest import for_all, Gen, settings
@for_all(Gen.int(), Gen.str())
@settings(num_runs=50, seed=42)
def test_property(x: int, s: str):
assert isinstance(x, int)
assert isinstance(s, str)
Seed for Reproducibility¶
@for_all(Gen.int(), Gen.str())
@settings(seed="deterministic")
def test_property(x: int, s: str):
assert isinstance(x, int)
assert isinstance(s, str)
Multiple Settings (Last Wins)¶
@for_all(Gen.int(), Gen.str())
@settings(num_runs=100)
@settings(seed=42) # This overrides the previous settings
def test_property(x: int, s: str):
assert isinstance(x, int)
assert isinstance(s, str)
Parameters¶
num_runs: Number of test runs (overrides @for_all default)seed: Random seed for reproducibility (overrides @for_all default)
See Also: @for_all, @run_for_all, Best Practices
@matrix¶
Provides exhaustive Cartesian product testing of fixed input combinations. Matrix cases are executed once per combination, before examples and random runs, and do not count toward num_runs.
Basic Usage¶
from python_proptest import for_all, Gen, matrix
@for_all(Gen.int(), Gen.str())
@matrix(x=[0, 1], s=["a", "b"])
def test_property(x: int, s: str):
assert isinstance(x, int)
assert isinstance(s, str)
This will test all combinations:
- (0, "a")
- (0, "b")
- (1, "a")
- (1, "b")
Multiple Matrix Decorators¶
@for_all(Gen.int(), Gen.str())
@matrix(x=[0, 1])
@matrix(s=["a", "b"])
def test_property(x: int, s: str):
assert isinstance(x, int)
assert isinstance(s, str)
Matrix with Different Types¶
@for_all(Gen.int(), Gen.list(Gen.int()))
@matrix(x=[0, 1, 2], lst=[[], [1], [1, 2]])
def test_property(x: int, lst: list):
assert isinstance(x, int)
assert isinstance(lst, list)
Matrix with Complex Types¶
@for_all(Gen.int(), Gen.dict(Gen.str(), Gen.int()))
@matrix(x=[1, 2], d=[{"a": 1}, {"b": 2}])
def test_property(x: int, d: dict):
assert isinstance(x, int)
assert isinstance(d, dict)
Parameters¶
**kwargs: Named parameters with lists of values to test
See Also: @for_all, @example, Best Practices, Error Handling
Decorator Composition¶
All decorators can be combined in various ways. The execution order is:
- Matrix cases (exhaustive combinations)
- Example cases (specific values)
- Random cases (property-based generation)
Complete Example¶
from python_proptest import for_all, Gen, example, settings, matrix
@for_all(Gen.int(), Gen.str())
@matrix(x=[0, 1], s=["a", "b"]) # 4 matrix cases
@example(42, "special") # 1 example case
@settings(num_runs=10, seed=42) # 10 random cases
def test_property(x: int, s: str):
assert isinstance(x, int)
assert isinstance(s, str)
# Total: 4 + 1 + 10 = 15 test cases
Decorator Order¶
The order of decorators matters for merging behavior:
@for_all(Gen.int(), Gen.str())
@matrix(x=[1, 2]) # First matrix decorator
@matrix(x=[3, 4]) # Second matrix decorator (overwrites first)
@settings(num_runs=50) # First settings decorator
@settings(seed=42) # Second settings decorator (overwrites first)
def test_property(x: int, s: str):
assert isinstance(x, int)
assert isinstance(s, str)
# Matrix will use x=[3, 4], settings will use seed=42
Integration with Testing Frameworks¶
All decorators work seamlessly with both pytest and unittest:
Pytest Integration¶
import pytest
from python_proptest import for_all, Gen, example, settings, matrix
class TestMathProperties:
@for_all(Gen.int(), Gen.int())
@matrix(x=[0, 1], y=[0, 1])
@example(42, 24)
@settings(num_runs=50, seed=42)
def test_addition_commutativity(self, x: int, y: int):
assert x + y == y + x
Unittest Integration¶
import unittest
from python_proptest import for_all, Gen, example, settings, matrix
class TestMathProperties(unittest.TestCase):
@for_all(Gen.int(), Gen.int())
@matrix(x=[0, 1], y=[0, 1])
@example(42, 24)
@settings(num_runs=50, seed=42)
def test_addition_commutativity(self, x: int, y: int):
self.assertEqual(x + y, y + x)
Programmatic Usage¶
For programmatic testing, you can use the run_matrix function:
from python_proptest import run_matrix
def test_func(x: int, y: str):
assert isinstance(x, int)
assert isinstance(y, str)
# Run matrix cases programmatically
matrix_spec = {"x": [0, 1], "y": ["a", "b"]}
run_matrix(test_func, matrix_spec)
Parameters¶
test_func: Function to testmatrix_spec: Dictionary mapping parameter names to lists of valuesself_obj: Optional self object for class methods
Best Practices¶
1. Use Examples for Edge Cases¶
Combine @example with @for_all to test specific edge cases before random generation:
@for_all(Gen.int())
@example(0) # Edge case: zero
@example(1) # Edge case: one
@example(-1) # Edge case: negative
def test_property(x: int):
assert isinstance(x, int)
2. Use Matrix for Exhaustive Testing¶
Use @matrix to test all combinations of important values:
@for_all(Gen.int(), Gen.int())
@matrix(x=[0, 1, -1], y=[0, 1, -1]) # Test all combinations of edge cases
def test_property(x: int, y: int):
assert isinstance(x, int)
assert isinstance(y, int)
3. Use Settings for Reproducibility¶
Use @settings to ensure tests are reproducible:
@for_all(Gen.int(), Gen.str())
@settings(seed=42) # Reproducible tests
def test_property(x: int, s: str):
assert isinstance(x, int)
assert isinstance(s, str)
4. Combine Decorators Strategically¶
Combine @for_all, @matrix, @example, and @settings for comprehensive testing. See also Decorator Composition.
@for_all(Gen.int(), Gen.str())
@matrix(x=[0, 1], s=["a", "b"]) # Exhaustive edge cases
@example(42, "special") # Known good value
@settings(num_runs=100, seed=42) # Reproducible random testing
def test_property(x: int, s: str):
assert isinstance(x, int)
assert isinstance(s, str)
See Also: Decorator Composition, Decorator Order
Error Handling¶
Matrix Parameter Coverage¶
Matrix decorators require all function parameters to be covered:
@for_all(Gen.int(), Gen.str())
@matrix(x=[0, 1]) # Missing 's' parameter - matrix cases will be skipped
def test_property(x: int, s: str):
assert isinstance(x, int)
assert isinstance(s, str)
Example Parameter Count¶
Example decorators must provide the correct number of arguments:
@for_all(Gen.int(), Gen.str())
@example(42) # Missing second argument - example will be skipped
def test_property(x: int, s: str):
assert isinstance(x, int)
assert isinstance(s, str)
Settings Override¶
Later settings decorators override earlier ones:
@for_all(Gen.int(), Gen.str())
@settings(num_runs=100, seed=1)
@settings(num_runs=50, seed=2) # Only seed=2 will be used
def test_property(x: int, s: str):
assert isinstance(x, int)
assert isinstance(s, str)
Related Documentation¶
- Generators - Available generators for creating test data
- Combinators - Combining and transforming generators
- Properties - Alternative approaches to property testing
- Pytest Integration - Using decorators with pytest
- Unittest Integration - Using decorators with unittest