TS006: HasPropertyTests
Overview
| Property | Value |
|---|---|
| ID | TS006 |
| Name | HasPropertyTests |
| Group | tests |
| Severity | NOTE |
Description
Checks if property-based tests are used in the package.
Property-based testing with hypothesis provides more robust test coverage by automatically generating test cases. Instead of writing individual test cases with specific inputs, you describe the properties your code should satisfy, and hypothesis generates hundreds of test cases to verify them.
What it checks
The check looks for evidence of hypothesis usage:
- In dependencies: Checks if
hypothesisis listed inpyproject.tomloptional dependencies (dev, test, etc.) - In test files: Scans
tests/ortest/directories for:from hypothesisimportsimport hypothesisstatements@givendecorators@settingsdecorators
Results
- PASSED: hypothesis is in dependencies OR hypothesis patterns found in test files
- FAILED: No evidence of property-based testing found
- NOT_APPLICABLE: No tests directory found
How to fix
Install hypothesis
Add hypothesis to your dev dependencies:
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"hypothesis>=6.0.0",
]Install:
pip install -e ".[dev]"Write property-based tests
Basic example testing a function that reverses strings:
from hypothesis import given, strategies as st
@given(st.text())
def test_reverse_twice_is_identity(s):
"""Reversing a string twice returns the original."""
assert reverse(reverse(s)) == s
@given(st.text())
def test_reverse_preserves_length(s):
"""Reversing preserves string length."""
assert len(reverse(s)) == len(s)Testing with multiple inputs
from hypothesis import given, strategies as st
@given(st.integers(), st.integers())
def test_addition_is_commutative(a, b):
"""Addition is commutative: a + b == b + a."""
assert a + b == b + a
@given(st.lists(st.integers()))
def test_sorted_list_is_ordered(lst):
"""Sorted list elements are in non-decreasing order."""
result = sorted(lst)
for i in range(len(result) - 1):
assert result[i] <= result[i + 1]Common strategies
from hypothesis import strategies as st
# Basic types
st.integers() # Integers
st.floats() # Floats (includes NaN, inf)
st.text() # Unicode strings
st.binary() # Bytes
st.booleans() # True/False
# Collections
st.lists(st.integers()) # Lists of integers
st.dictionaries(st.text(), st.integers()) # Dict[str, int]
st.tuples(st.integers(), st.text()) # Tuple[int, str]
# Constrained values
st.integers(min_value=0, max_value=100) # 0-100
st.text(min_size=1, max_size=10) # 1-10 chars
st.floats(allow_nan=False, allow_infinity=False) # Finite floatsConfiguration with settings
from hypothesis import given, settings, strategies as st
@given(st.lists(st.integers()))
@settings(max_examples=500) # Run more examples
def test_with_more_examples(lst):
assert sorted(lst) == sorted(sorted(lst))
@given(st.text())
@settings(deadline=1000) # Allow 1 second per example
def test_slow_operation(s):
result = slow_function(s)
assert result is not NoneWhy property-based testing?
Finds edge cases automatically
# Manual test - limited coverage
def test_parse_integer():
assert parse_int("42") == 42
assert parse_int("-1") == -1
# Property test - hypothesis finds edge cases you'd miss
@given(st.integers())
def test_parse_integer_roundtrip(n):
assert parse_int(str(n)) == n
# Hypothesis may find: large numbers, negative zero, etc.Reduces test maintenance
Instead of maintaining lists of test cases:
# Before: many manual test cases
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("world", "WORLD"),
("Test", "TEST"),
# ... many more cases
])
def test_uppercase(input, expected):
assert uppercase(input) == expected
# After: one property test
@given(st.text())
def test_uppercase_properties(s):
result = uppercase(s)
assert result == result.upper()
assert len(result) == len(s)Configuration
Skip this check
[tool.pycmdcheck]
skip = ["TS006"]CLI
pycmdcheck --skip TS006