Harness Property Test
Property-based and generative testing with fast-check, hypothesis, and automatic shrinking. Discovers edge cases that example-based tests miss by generating thousands of random inputs and verifying invariants hold for all of them.
When to Use
- Testing functions with large input spaces (parsers, serializers, encoders, validators)
- Verifying mathematical or algebraic properties (commutativity, associativity, round-trip encoding)
- Finding edge cases in data transformation, sorting, or filtering logic
- NOT when testing UI rendering or visual output (use harness-visual-regression instead)
- NOT when testing simple CRUD operations with well-defined inputs (use harness-tdd instead)
- NOT when testing external service integrations (use harness-integration-test instead)
Process
Phase 1: IDENTIFY -- Discover Testable Properties and Invariants
Catalog candidate functions. Search for functions that exhibit testable properties:
- Pure functions with deterministic output for given input
- Serializers/deserializers where
decode(encode(x)) === x (round-trip property)
- Sorting/filtering where output maintains invariants (sorted order, subset relationship)
- Validators where valid input always passes and specific invalid inputs always fail
- Mathematical functions with known algebraic properties
Identify properties for each candidate. Common property categories:
- Round-trip:
deserialize(serialize(x)) === x for any valid x
- Idempotence:
f(f(x)) === f(x) (applying the function twice gives the same result)
- Invariant preservation: output always satisfies a postcondition regardless of input
- Commutativity:
f(a, b) === f(b, a) for operations where order should not matter
- No-crash (robustness): function does not throw for any input in the domain
- Monotonicity: if
a <= b, then f(a) <= f(b) for order-preserving functions
- Equivalence:
fastImpl(x) === referenceImpl(x) for optimized implementations
Define input domains. For each property, specify:
- The type and range of valid inputs
- Constraints that inputs must satisfy (e.g., non-empty arrays, positive integers)
- Edge cases that the generator should emphasize (empty strings, zero, max int, Unicode)
Prioritize by risk. Focus property tests on:
- Functions where bugs have high business impact
- Functions with complex branching logic
- Functions that have had historical bugs or regression issues
Report findings. List candidate functions, their properties, and the expected generator configuration.
Phase 2: DEFINE -- Write Property Specifications and Custom Generators
Select the property testing framework. Based on the project's language:
- TypeScript/JavaScript: fast-check
- Python: hypothesis
- Rust: proptest or quickcheck
- Scala: ScalaCheck
- Haskell: QuickCheck
- Java/Kotlin: jqwik
Define custom generators (arbitraries) for domain types. For each domain model:
- Build a generator that produces valid instances with realistic field values
- Add constraints matching the model's validation rules
- Compose generators for nested structures using
map, flatMap, and filter
Write property test specifications. For each property identified in Phase 1:
- State the property as a universally quantified assertion: "For all inputs X satisfying constraint C, property P holds"
- Use the framework's property definition syntax
- Configure iteration count (default: 100 iterations for fast properties, 1000 for critical properties)
Configure shrinking. Ensure the framework's automatic shrinking is enabled:
- Shrinking reduces failing inputs to the minimal counterexample
- Custom generators should support shrinking (use
map over filter where possible, since filter breaks shrinking)
- Set a shrink limit to prevent infinite shrinking on complex inputs
Write seed values for reproducibility. Configure:
- A fixed seed for CI to ensure deterministic reruns
- Seed logging so that any failure can be reproduced exactly
- Replay capability: failed seeds are stored and replayed on subsequent runs
Phase 3: EXECUTE -- Run Property Tests and Collect Counterexamples
Run property tests with verbose output. Execute the test suite and observe:
- Number of test cases generated per property
- Any counterexamples found (failing inputs)
- Shrinking progress (how the framework reduces counterexamples)
Analyze counterexamples. For each failing property:
- Read the shrunk counterexample -- this is the minimal input that violates the property
- Understand why this input causes a failure
- Classify: is this a real bug, or is the property specification too strict?
Reproduce counterexamples deterministically. For each counterexample:
- Record the failing seed value
- Write an explicit example-based test using the shrunk counterexample as a regression test
- This regression test serves as documentation and prevents the same bug from recurring
Handle flaky property tests. If a property test fails intermittently:
- Increase the iteration count to reproduce more reliably
- Check if the property is sensitive to floating-point precision
- Verify that the generator does not produce inputs outside the valid domain
Iterate on generator quality. If the generator frequently produces uninteresting inputs:
- Add bias toward edge cases (empty collections, boundary values)
- Use
filter sparingly (it discards inputs, wasting iterations)
- Prefer
map and flatMap to construct valid inputs directly
Phase 4: ANALYZE -- Diagnose Root Causes and Harden Implementations
Fix bugs exposed by counterexamples. For each real bug found:
- Understand the root cause using the minimal counterexample
- Fix the implementation
- Verify the property now holds (rerun with the same seed)
- Keep the regression test with the explicit counterexample
Strengthen property specifications. After fixing bugs:
- Consider whether additional properties are now testable
- Tighten existing properties if the fix enables stricter invariants
- Add properties for edge cases revealed by the counterexamples
Measure property test effectiveness. Evaluate:
- Number of unique bugs found by property tests vs. example-based tests
- Types of bugs found (off-by-one, overflow, Unicode handling, null handling)
- Generator coverage: what percentage of the input domain is being explored
Integrate property tests into CI. Configure:
- Property tests run on every PR with a moderate iteration count (100)
- Nightly runs use a higher iteration count (10,000) for deeper exploration
- Failed seeds are stored as artifacts for reproduction
Run harness validate. Confirm the project passes all harness checks with property tests in place.
Graph Refresh
If a knowledge graph exists at .harness/graph/, refresh it after code changes to keep graph queries accurate:
harness scan [path]
Harness Integration
harness validate -- Run in ANALYZE phase after property tests are written and bugs are fixed. Confirms project health.
harness check-deps -- Run after DEFINE phase to verify property testing framework is in devDependencies.
emit_interaction -- Used to present counterexample analysis and property specification decisions to the human.
- Grep -- Used in IDENTIFY phase to find pure functions, serializers, validators, and mathematical operations.
- Glob -- Used to catalog existing property test files and domain type definitions.
Success Criteria
- Every function with large input space has at least one property test
- Custom generators produce valid domain objects without relying heavily on
filter
- All counterexamples are investigated: real bugs are fixed, property specs are adjusted for false positives
- Shrunk counterexamples are preserved as explicit regression tests
- Property tests are deterministic in CI (fixed seed) while still exploring randomly in local development
harness validate passes with property tests in place
Examples
Example: fast-check for a TypeScript URL Parser
IDENTIFY -- Properties of a URL parser:
Function: parseUrl(input: string): ParsedUrl
Properties:
1. Round-trip: formatUrl(parseUrl(url)) === url for any valid URL
2. No-crash: parseUrl(arbitrary_string) never throws (returns Result type)
3. Invariant: parsed.protocol is always lowercase
4. Invariant: parsed.host never contains a trailing slash
DEFINE -- Custom generator and property tests:
// tests/property/url-parser.prop.test.ts
import fc from 'fast-check';
import { parseUrl, formatUrl } from '../../src/url-parser';
// Custom generator for valid URLs
const urlArb = fc
.record({
protocol: fc.constantFrom('http', 'https', 'ftp'),
host: fc.domain(),
port: fc.option(fc.integer({ min: 1, max: 65535 }), { nil: undefined }),
path: fc
.array(
fc.stringOf(fc.constantFrom(...'abcdefghijklmnopqrstuvwxyz0123456789-_'.split('')), {
minLength: 1,
})
)
.map((segments) => '/' + segments.join('/')),
})
.map(({ protocol, host, port, path }) => `${protocol}://${host}${port ? ':' + port : ''}${path}`);
describe('URL parser properties', () => {
it('round-trips valid URLs', () => {
fc.assert(
fc.property(urlArb, (url) => {
const parsed = parseUrl(url);
if (!parsed.ok) return false; // skip invalid (generator should not produce these)
return formatUrl(parsed.value) === url;
}),
{ numRuns: 1000, seed: 42 }
);
});
it('never throws on arbitrary string input', () => {
fc.assert(
fc.property(fc.string(), (input) => {
const result = parseUrl(input);
// Must return a Result, never throw
return result.ok === true || result.ok === false;
}),
{ numRuns: 5000 }
);
});
it('always produces lowercase protocol', () => {
fc.assert(
fc.property(urlArb, (url) => {
const parsed = parseUrl(url.toUpperCase());
if (!parsed.ok) return true; // skip failures
return parsed.value.protocol === parsed.value.protocol.toLowerCase();
})
);
});
});
Example: hypothesis for a Python Sorting Algorithm
DEFINE -- Property tests with hypothesis:
# tests/property/test_sort_properties.py
from hypothesis import given, settings, assume
from hypothesis import strategies as st
from myapp.sorting import merge_sort
@given(st.lists(st.integers()))
def test_sort_preserves_length(xs):
"""Sorted output has the same length as input."""
assert len(merge_sort(xs)) == len(xs)
@given(st.lists(st.integers()))
def test_sort_preserves_elements(xs):
"""Sorted output contains exactly the same elements as input."""
assert sorted(merge_sort(xs)) == sorted(xs)
@given(st.lists(st.integers(), min_size=1))
def test_sort_produces_ordered_output(xs):
"""Every element is less than or equal to the next."""
result = merge_sort(xs)
for i in range(len(result) - 1):
assert result[i] <= result[i + 1]
@given(st.lists(st.integers()))
def test_sort_is_idempotent(xs):
"""Sorting an already-sorted list produces the same result."""
twice = merge_sort(once)
assert twice
@settings(max_examples=5000)
@given(st.lists(st.floats(allow_nan=False, allow_infinity=False)))
def test_sort_handles_floats(xs):
"""Sort works correctly with floating-point numbers."""
result = merge_sort(xs)
for i in range(len(result) - 1):
assert result[i] <= result[i + 1]
Rationalizations to Reject
| Rationalization |
Reality |
| "We already have example-based tests that cover the edge cases — property tests would just be redundant." |
Example-based tests cover the cases the author thought of. Property tests cover the cases they did not. The entire value of generative testing is that it explores regions of the input space that human intuition misses — off-by-one errors, Unicode combining characters, signed integer overflow at boundaries. |
| "The generator keeps producing rejected inputs, so I'll just filter more aggressively to make the test pass faster." |
Heavy filter usage is a symptom of a broken generator, not a solution. Each rejected sample wastes an iteration, and filter destroys the shrinking chain, leaving you with an unhelpful counterexample when a bug is found. Rewrite the generator using map and flatMap to construct valid inputs directly. |
| "The counterexample is too strange to be a real-world case — I'll just increase the iteration count so it appears less often." |
A shrunk counterexample that triggers a property failure is a real bug by definition. "Unlikely in practice" is not a property of correctness — the question is whether the invariant holds. If the counterexample is a valid input the function might receive, fix the function. If it is not a valid input, constrain the generator. |
| "This function has too many invariants to specify — I'll just skip property testing and trust the unit tests." |
Complex functions with many invariants are exactly the functions most in need of property testing. High complexity means a larger bug-hiding surface. Start with the most important invariants (no-crash, round-trip, idempotence) rather than attempting to encode all properties at once. |
| "Property tests are too slow — they'll block CI for 10 minutes." |
Run 100 iterations on PR, 10,000 iterations nightly. The CI time argument justifies reducing iteration count, never eliminating property tests entirely. A suite that runs 0 property tests found 0 edge cases. |
Gates
- No property tests without shrinking. If the framework's automatic shrinking is disabled or the generator uses patterns that break shrinking (excessive
filter), counterexamples will be unhelpfully large. Fix the generator to support shrinking.
- No ignoring counterexamples. Every counterexample produced by a property test must be investigated. If it reveals a real bug, fix it. If it is a false positive, adjust the property specification or generator. Never just increase the iteration count to make it "less likely to fail."
- No property tests that always pass trivially. A property that returns
true for every input is useless. Review that properties make substantive assertions. If a property has a return true fallback for most inputs, the generator is producing too many invalid inputs.
- Regression tests are mandatory for counterexamples. Every shrunk counterexample that revealed a bug must be preserved as an explicit example-based test, even after the property test passes. The explicit test serves as documentation and prevents regression.
Escalation
- When the generator cannot produce valid inputs efficiently (> 50% rejection rate): Rewrite the generator to construct valid inputs directly rather than filtering. Use
flatMap to build constrained structures incrementally. If the domain constraints are too complex for a generator, consider whether the function's API needs simplification.
- When a counterexample is too complex to understand even after shrinking: The shrinking strategy may be insufficient for the data type. Write a custom shrinker that targets the specific structure. Alternatively, add intermediate logging to the property to trace which sub-property fails.
- When property tests are too slow for CI (> 5 minutes): Reduce the iteration count for PR runs (100 iterations). Run high-iteration tests (10,000+) as a nightly job. Consider whether some properties can be tested with smaller input ranges without losing coverage.
- When the team debates whether a property is correct: The property may be encoding an assumption that does not hold. Review the specification or domain requirements. If the correct behavior is ambiguous, escalate to product/domain experts before encoding the property in a test.
1---2name: harness-property-test3description: Harness Property Test4---5# Harness Property Test67> Property-based and generative testing with fast-check, hypothesis, and automatic shrinking. Discovers edge cases that example-based tests miss by generating thousands of random inputs and verifying invariants hold for all of them.89## When to Use1011- Testing functions with large input spaces (parsers, serializers, encoders, validators)12- Verifying mathematical or algebraic properties (commutativity, associativity, round-trip encoding)13- Finding edge cases in data transformation, sorting, or filtering logic14- NOT when testing UI rendering or visual output (use harness-visual-regression instead)15- NOT when testing simple CRUD operations with well-defined inputs (use harness-tdd instead)16- NOT when testing external service integrations (use harness-integration-test instead)1718## Process1920### Phase 1: IDENTIFY -- Discover Testable Properties and Invariants21221. **Catalog candidate functions.** Search for functions that exhibit testable properties:23 - **Pure functions** with deterministic output for given input24 - **Serializers/deserializers** where `decode(encode(x)) === x` (round-trip property)25 - **Sorting/filtering** where output maintains invariants (sorted order, subset relationship)26 - **Validators** where valid input always passes and specific invalid inputs always fail27 - **Mathematical functions** with known algebraic properties28292. **Identify properties for each candidate.** Common property categories:30 - **Round-trip:** `deserialize(serialize(x)) === x` for any valid `x`31 - **Idempotence:** `f(f(x)) === f(x)` (applying the function twice gives the same result)32 - **Invariant preservation:** output always satisfies a postcondition regardless of input33 - **Commutativity:** `f(a, b) === f(b, a)` for operations where order should not matter34 - **No-crash (robustness):** function does not throw for any input in the domain35 - **Monotonicity:** if `a <= b`, then `f(a) <= f(b)` for order-preserving functions36 - **Equivalence:** `fastImpl(x) === referenceImpl(x)` for optimized implementations37383. **Define input domains.** For each property, specify:39 - The type and range of valid inputs40 - Constraints that inputs must satisfy (e.g., non-empty arrays, positive integers)41 - Edge cases that the generator should emphasize (empty strings, zero, max int, Unicode)42434. **Prioritize by risk.** Focus property tests on:44 - Functions where bugs have high business impact45 - Functions with complex branching logic46 - Functions that have had historical bugs or regression issues47485. **Report findings.** List candidate functions, their properties, and the expected generator configuration.4950### Phase 2: DEFINE -- Write Property Specifications and Custom Generators51521. **Select the property testing framework.** Based on the project's language:53 - **TypeScript/JavaScript:** fast-check54 - **Python:** hypothesis55 - **Rust:** proptest or quickcheck56 - **Scala:** ScalaCheck57 - **Haskell:** QuickCheck58 - **Java/Kotlin:** jqwik59602. **Define custom generators (arbitraries) for domain types.** For each domain model:61 - Build a generator that produces valid instances with realistic field values62 - Add constraints matching the model's validation rules63 - Compose generators for nested structures using `map`, `flatMap`, and `filter`64653. **Write property test specifications.** For each property identified in Phase 1:66 - State the property as a universally quantified assertion: "For all inputs X satisfying constraint C, property P holds"67 - Use the framework's property definition syntax68 - Configure iteration count (default: 100 iterations for fast properties, 1000 for critical properties)69704. **Configure shrinking.** Ensure the framework's automatic shrinking is enabled:71 - Shrinking reduces failing inputs to the minimal counterexample72 - Custom generators should support shrinking (use `map` over `filter` where possible, since `filter` breaks shrinking)73 - Set a shrink limit to prevent infinite shrinking on complex inputs74755. **Write seed values for reproducibility.** Configure:76 - A fixed seed for CI to ensure deterministic reruns77 - Seed logging so that any failure can be reproduced exactly78 - Replay capability: failed seeds are stored and replayed on subsequent runs7980### Phase 3: EXECUTE -- Run Property Tests and Collect Counterexamples81821. **Run property tests with verbose output.** Execute the test suite and observe:83 - Number of test cases generated per property84 - Any counterexamples found (failing inputs)85 - Shrinking progress (how the framework reduces counterexamples)86872. **Analyze counterexamples.** For each failing property:88 - Read the shrunk counterexample -- this is the minimal input that violates the property89 - Understand why this input causes a failure90 - Classify: is this a real bug, or is the property specification too strict?91923. **Reproduce counterexamples deterministically.** For each counterexample:93 - Record the failing seed value94 - Write an explicit example-based test using the shrunk counterexample as a regression test95 - This regression test serves as documentation and prevents the same bug from recurring96974. **Handle flaky property tests.** If a property test fails intermittently:98 - Increase the iteration count to reproduce more reliably99 - Check if the property is sensitive to floating-point precision100 - Verify that the generator does not produce inputs outside the valid domain1011025. **Iterate on generator quality.** If the generator frequently produces uninteresting inputs:103 - Add bias toward edge cases (empty collections, boundary values)104 - Use `filter` sparingly (it discards inputs, wasting iterations)105 - Prefer `map` and `flatMap` to construct valid inputs directly106107### Phase 4: ANALYZE -- Diagnose Root Causes and Harden Implementations1081091. **Fix bugs exposed by counterexamples.** For each real bug found:110 - Understand the root cause using the minimal counterexample111 - Fix the implementation112 - Verify the property now holds (rerun with the same seed)113 - Keep the regression test with the explicit counterexample1141152. **Strengthen property specifications.** After fixing bugs:116 - Consider whether additional properties are now testable117 - Tighten existing properties if the fix enables stricter invariants118 - Add properties for edge cases revealed by the counterexamples1191203. **Measure property test effectiveness.** Evaluate:121 - Number of unique bugs found by property tests vs. example-based tests122 - Types of bugs found (off-by-one, overflow, Unicode handling, null handling)123 - Generator coverage: what percentage of the input domain is being explored1241254. **Integrate property tests into CI.** Configure:126 - Property tests run on every PR with a moderate iteration count (100)127 - Nightly runs use a higher iteration count (10,000) for deeper exploration128 - Failed seeds are stored as artifacts for reproduction1291305. **Run `harness validate`.** Confirm the project passes all harness checks with property tests in place.131132### Graph Refresh133134If a knowledge graph exists at `.harness/graph/`, refresh it after code changes to keep graph queries accurate:135136```137harness scan [path]138```139140## Harness Integration141142- **`harness validate`** -- Run in ANALYZE phase after property tests are written and bugs are fixed. Confirms project health.143- **`harness check-deps`** -- Run after DEFINE phase to verify property testing framework is in devDependencies.144- **`emit_interaction`** -- Used to present counterexample analysis and property specification decisions to the human.145- **Grep** -- Used in IDENTIFY phase to find pure functions, serializers, validators, and mathematical operations.146- **Glob** -- Used to catalog existing property test files and domain type definitions.147148## Success Criteria149150- Every function with large input space has at least one property test151- Custom generators produce valid domain objects without relying heavily on `filter`152- All counterexamples are investigated: real bugs are fixed, property specs are adjusted for false positives153- Shrunk counterexamples are preserved as explicit regression tests154- Property tests are deterministic in CI (fixed seed) while still exploring randomly in local development155- `harness validate` passes with property tests in place156157## Examples158159### Example: fast-check for a TypeScript URL Parser160161**IDENTIFY -- Properties of a URL parser:**162163```164Function: parseUrl(input: string): ParsedUrl165Properties:166 1. Round-trip: formatUrl(parseUrl(url)) === url for any valid URL167 2. No-crash: parseUrl(arbitrary_string) never throws (returns Result type)168 3. Invariant: parsed.protocol is always lowercase169 4. Invariant: parsed.host never contains a trailing slash170```171172**DEFINE -- Custom generator and property tests:**173174```typescript175// tests/property/url-parser.prop.test.ts176import fc from 'fast-check';177import { parseUrl, formatUrl } from '../../src/url-parser';178179// Custom generator for valid URLs180const urlArb = fc181 .record({182 protocol: fc.constantFrom('http', 'https', 'ftp'),183 host: fc.domain(),184 port: fc.option(fc.integer({ min: 1, max: 65535 }), { nil: undefined }),185 path: fc186 .array(187 fc.stringOf(fc.constantFrom(...'abcdefghijklmnopqrstuvwxyz0123456789-_'.split('')), {188 minLength: 1,189 })190 )191 .map((segments) => '/' + segments.join('/')),192 })193 .map(({ protocol, host, port, path }) => `${protocol}://${host}${port ? ':' + port : ''}${path}`);194195describe('URL parser properties', () => {196 it('round-trips valid URLs', () => {197 fc.assert(198 fc.property(urlArb, (url) => {199 const parsed = parseUrl(url);200 if (!parsed.ok) return false; // skip invalid (generator should not produce these)201 return formatUrl(parsed.value) === url;202 }),203 { numRuns: 1000, seed: 42 }204 );205 });206207 it('never throws on arbitrary string input', () => {208 fc.assert(209 fc.property(fc.string(), (input) => {210 const result = parseUrl(input);211 // Must return a Result, never throw212 return result.ok === true || result.ok === false;213 }),214 { numRuns: 5000 }215 );216 });217218 it('always produces lowercase protocol', () => {219 fc.assert(220 fc.property(urlArb, (url) => {221 const parsed = parseUrl(url.toUpperCase());222 if (!parsed.ok) return true; // skip failures223 return parsed.value.protocol === parsed.value.protocol.toLowerCase();224 })225 );226 });227});228```229230### Example: hypothesis for a Python Sorting Algorithm231232**DEFINE -- Property tests with hypothesis:**233234```python235# tests/property/test_sort_properties.py236from hypothesis import given, settings, assume237from hypothesis import strategies as st238from myapp.sorting import merge_sort239240@given(st.lists(st.integers()))241def test_sort_preserves_length(xs):242 """Sorted output has the same length as input."""243 assert len(merge_sort(xs)) == len(xs)244245@given(st.lists(st.integers()))246def test_sort_preserves_elements(xs):247 """Sorted output contains exactly the same elements as input."""248 assert sorted(merge_sort(xs)) == sorted(xs)249250@given(st.lists(st.integers(), min_size=1))251def test_sort_produces_ordered_output(xs):252 """Every element is less than or equal to the next."""253 result = merge_sort(xs)254 for i in range(len(result) - 1):255 assert result[i] <= result[i + 1]256257@given(st.lists(st.integers()))258def test_sort_is_idempotent(xs):259 """Sorting an already-sorted list produces the same result."""260 once = merge_sort(xs)261 twice = merge_sort(once)262 assert once == twice263264@settings(max_examples=5000)265@given(st.lists(st.floats(allow_nan=False, allow_infinity=False)))266def test_sort_handles_floats(xs):267 """Sort works correctly with floating-point numbers."""268 result = merge_sort(xs)269 for i in range(len(result) - 1):270 assert result[i] <= result[i + 1]271```272273## Rationalizations to Reject274275| Rationalization | Reality |276| ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |277| "We already have example-based tests that cover the edge cases — property tests would just be redundant." | Example-based tests cover the cases the author thought of. Property tests cover the cases they did not. The entire value of generative testing is that it explores regions of the input space that human intuition misses — off-by-one errors, Unicode combining characters, signed integer overflow at boundaries. |278| "The generator keeps producing rejected inputs, so I'll just filter more aggressively to make the test pass faster." | Heavy `filter` usage is a symptom of a broken generator, not a solution. Each rejected sample wastes an iteration, and `filter` destroys the shrinking chain, leaving you with an unhelpful counterexample when a bug is found. Rewrite the generator using `map` and `flatMap` to construct valid inputs directly. |279| "The counterexample is too strange to be a real-world case — I'll just increase the iteration count so it appears less often." | A shrunk counterexample that triggers a property failure is a real bug by definition. "Unlikely in practice" is not a property of correctness — the question is whether the invariant holds. If the counterexample is a valid input the function might receive, fix the function. If it is not a valid input, constrain the generator. |280| "This function has too many invariants to specify — I'll just skip property testing and trust the unit tests." | Complex functions with many invariants are exactly the functions most in need of property testing. High complexity means a larger bug-hiding surface. Start with the most important invariants (no-crash, round-trip, idempotence) rather than attempting to encode all properties at once. |281| "Property tests are too slow — they'll block CI for 10 minutes." | Run 100 iterations on PR, 10,000 iterations nightly. The CI time argument justifies reducing iteration count, never eliminating property tests entirely. A suite that runs 0 property tests found 0 edge cases. |282283## Gates284285- **No property tests without shrinking.** If the framework's automatic shrinking is disabled or the generator uses patterns that break shrinking (excessive `filter`), counterexamples will be unhelpfully large. Fix the generator to support shrinking.286- **No ignoring counterexamples.** Every counterexample produced by a property test must be investigated. If it reveals a real bug, fix it. If it is a false positive, adjust the property specification or generator. Never just increase the iteration count to make it "less likely to fail."287- **No property tests that always pass trivially.** A property that returns `true` for every input is useless. Review that properties make substantive assertions. If a property has a `return true` fallback for most inputs, the generator is producing too many invalid inputs.288- **Regression tests are mandatory for counterexamples.** Every shrunk counterexample that revealed a bug must be preserved as an explicit example-based test, even after the property test passes. The explicit test serves as documentation and prevents regression.289290## Escalation291292- **When the generator cannot produce valid inputs efficiently (> 50% rejection rate):** Rewrite the generator to construct valid inputs directly rather than filtering. Use `flatMap` to build constrained structures incrementally. If the domain constraints are too complex for a generator, consider whether the function's API needs simplification.293- **When a counterexample is too complex to understand even after shrinking:** The shrinking strategy may be insufficient for the data type. Write a custom shrinker that targets the specific structure. Alternatively, add intermediate logging to the property to trace which sub-property fails.294- **When property tests are too slow for CI (> 5 minutes):** Reduce the iteration count for PR runs (100 iterations). Run high-iteration tests (10,000+) as a nightly job. Consider whether some properties can be tested with smaller input ranges without losing coverage.295- **When the team debates whether a property is correct:** The property may be encoding an assumption that does not hold. Review the specification or domain requirements. If the correct behavior is ambiguous, escalate to product/domain experts before encoding the property in a test.