Unittest Integration¶
python-proptest provides seamless integration with Python's built-in unittest framework through the @for_all decorator. The decorator automatically detects whether it's being used in a unittest context (unittest.TestCase methods), pytest context, or standalone functions and adapts accordingly.
Note: If you're using pytest, see Pytest Integration and Pytest Best Practices for framework-specific guidance. Both pytest and unittest are fully supported with identical functionality.
Basic Usage¶
Unittest TestCase Methods¶
import unittest
from python_proptest import for_all, Gen
class TestMathProperties(unittest.TestCase):
"""Test class for mathematical properties using unittest."""
@for_all(Gen.int(), Gen.int())
def test_addition_commutativity(self, x: int, y: int):
"""Test that addition is commutative."""
result1 = x + y
result2 = y + x
self.assertEqual(result1, result2)
@for_all(Gen.int(), Gen.int(), Gen.int())
def test_multiplication_associativity(self, x: int, y: int, z: int):
"""Test that multiplication is associative."""
result1 = (x * y) * z
result2 = x * (y * z)
self.assertEqual(result1, result2)
@for_all(Gen.str(), Gen.str())
def test_string_concatenation(self, s1: str, s2: str):
"""Test string concatenation properties."""
result = s1 + s2
self.assertEqual(len(result), len(s1) + len(s2))
self.assertTrue(result.startswith(s1))
self.assertTrue(result.endswith(s2))
✅ Direct decoration works. The @for_all decorator automatically detects unittest context and handles the self parameter correctly.
Running Unittest Tests¶
# Run with unittest
python -m unittest tests.integration.test_unittest_integration -v
# Run with pytest (also works)
python -m pytest tests/integration/test_unittest_integration.py -v
Advanced Features¶
Mixed Assertion Styles¶
You can mix unittest assertions with regular assertions in the same test:
class TestMixedAssertions(unittest.TestCase):
@for_all(Gen.int(), Gen.int())
def test_mixed_assertions(self, x: int, y: int):
"""Test mixing different assertion styles."""
# Unittest assertions
self.assertIsInstance(x, int)
self.assertIsInstance(y, int)
# Regular assertions (work in both frameworks)
assert x + y == y + x
assert x * 0 == 0
assert x * 1 == x
# More unittest assertions
self.assertGreaterEqual(x + y, x)
self.assertGreaterEqual(x + y, y)
Using Gen Class Directly¶
from python_proptest import for_all, Gen
class TestWithGen(unittest.TestCase):
@for_all(Gen.int(), Gen.str())
def test_int_string_properties(self, x: int, s: str):
"""Test properties with int and string generators."""
self.assertIsInstance(x, int)
self.assertIsInstance(s, str)
self.assertGreaterEqual(len(s), 0)
@for_all(Gen.int(min_value=0, max_value=100), Gen.int(min_value=0, max_value=100))
def test_positive_int_properties(self, x: int, y: int):
"""Test properties with positive integers."""
self.assertGreaterEqual(x, 0)
self.assertGreaterEqual(y, 0)
self.assertGreaterEqual(x + y, x)
self.assertGreaterEqual(x + y, y)
Error Handling¶
Unittest-Specific Error Handling¶
When a property test fails in a unittest context, python-proptest automatically raises the appropriate unittest failure exception:
class TestFailingProperties(unittest.TestCase):
@for_all(Gen.int(), Gen.int())
def test_failing_property(self, x: int, y: int):
"""This test will fail and show proper unittest error reporting."""
# This will fail for some inputs
self.assertLess(x + y, 100) # Fails when x + y >= 100
The error will be reported using unittest's standard failure mechanism, making it compatible with unittest's test runners and reporting tools.
Framework Detection¶
python-proptest automatically detects the test framework by checking the class hierarchy:
- Unittest Detection: Checks if the class inherits from
unittest.TestCase - Pytest Detection: If not unittest, assumes pytest for class methods with
selfparameter - Standalone Functions: Functions without
selfparameter are treated as standalone
Detection Logic¶
# The decorator automatically detects:
class TestUnittest(unittest.TestCase): # ← Detected as unittest
@for_all(Gen.int())
def test_method(self, x: int):
self.assertEqual(x, x)
class TestPytest: # ← Detected as pytest
@for_all(Gen.int())
def test_method(self, x: int):
assert x == x
@for_all(Gen.int()) # ← Detected as standalone
def test_standalone(x: int):
assert x == x
Best Practices¶
1. Use Descriptive Test Names¶
class TestStringProperties(unittest.TestCase):
@for_all(Gen.str(), Gen.str())
def test_string_concatenation_length_property(self, s1: str, s2: str):
"""Test that concatenated string length equals sum of individual lengths."""
result = s1 + s2
self.assertEqual(len(result), len(s1) + len(s2))
2. Use Appropriate Assertions¶
class TestNumberProperties(unittest.TestCase):
@for_all(integers(min_value=1, max_value=100))
def test_positive_number_properties(self, x: int):
"""Test properties of positive numbers."""
self.assertGreater(x, 0) # Use unittest assertions
self.assertIsInstance(x, int) # Type checking
assert x * 2 > x # Mix with regular assertions
3. Handle Edge Cases¶
class TestDivisionProperties(unittest.TestCase):
@for_all(Gen.int(), integers(min_value=1, max_value=100))
def test_division_properties(self, x: int, y: int):
"""Test division properties with non-zero divisor."""
quotient = x // y
remainder = x % y
self.assertEqual(quotient * y + remainder, x)
self.assertGreaterEqual(remainder, 0)
self.assertLess(remainder, y)
Comparison with Pytest¶
| Feature | Unittest | Pytest |
|---|---|---|
| Assertions | self.assertEqual(), self.assertTrue() |
assert statements |
| Error Reporting | Unittest failure exceptions | Pytest assertion rewriting |
| Test Discovery | python -m unittest |
pytest |
| Class Inheritance | Must inherit from unittest.TestCase |
No inheritance required |
| Method Naming | Must start with test_ |
Must start with test_ |
Migration from Pytest to Unittest¶
If you have existing pytest tests and want to migrate to unittest:
# Before (pytest)
class TestMathProperties:
@for_all(Gen.int(), Gen.int())
def test_addition(self, x: int, y: int):
assert x + y == y + x
# After (unittest)
import unittest
class TestMathProperties(unittest.TestCase):
@for_all(Gen.int(), Gen.int())
def test_addition(self, x: int, y: int):
self.assertEqual(x + y, y + x)
The @for_all decorator works identically in both frameworks - only the assertion style changes.
Examples¶
See the complete examples in:
- examples/unittest_pytest_comparison.py - Side-by-side comparison
- tests/integration/test_unittest_integration.py - Comprehensive test suite