AgentSkillsCN

Property Based Testing

基于属性的测试

SKILL.md

Property-Based Testing Design

Purpose

Guide the design and implementation of property-based tests for a given module, identifying invariants, selecting generators, and producing executable test code in Go (rapid), Python (Hypothesis), or TypeScript (fast-check).

When to Use

  • Building or reviewing code with domain invariants (financial, mathematical, serialization)
  • Adding tests to parsers, validators, encoders, or state machines
  • Strengthening test suites that rely solely on example-based tests
  • Verifying round-trip properties (encode/decode, serialize/deserialize)

Steps

Step 1: Identify the Target Module

Read the source file(s) under test. Identify:

  • Public functions and their signatures
  • Input domains (types, ranges, constraints)
  • Output guarantees (postconditions)
  • Side effects (mutations, I/O)

Step 2: Enumerate Properties

For each function, identify which property patterns apply:

PatternQuestion to AskExample
InvariantWhat must always be true after this operation?Balance >= 0 after any withdrawal
IdempotencyDoes applying this twice give the same result?deduplicate(deduplicate(list)) == deduplicate(list)
CommutativityDoes order matter?merge(a, b) == merge(b, a)
Round-tripCan I reverse this operation?decode(encode(x)) == x
OracleIs there a simpler reference implementation?Optimized sort vs. naive sort
MetamorphicHow does output change when input changes predictably?Adding an element to a sorted list keeps it sorted
Zero-sumDo related operations cancel out?Transfer: source_loss == dest_gain

Document each identified property with:

  • Property name: e.g., balance_never_negative
  • Precondition: e.g., amount > 0 AND amount <= balance
  • Postcondition: e.g., new_balance == old_balance - amount AND new_balance >= 0
  • Edge cases: e.g., amount == balance (zero balance after), amount == 0.01 (minimum)

Step 3: Design Generators (Strategies)

For each input type, design a generator that produces valid and interesting values:

Go (rapid):

go
amount := rapid.Float64Range(0.01, 1_000_000).Draw(t, "amount")
accountType := rapid.SampledFrom([]string{"checking", "savings"}).Draw(t, "type")

Python (Hypothesis):

python
amount = st.decimals(min_value=Decimal("0.01"), max_value=Decimal("1000000"), places=2)
account_type = st.sampled_from(["checking", "savings"])

TypeScript (fast-check):

typescript
const amount = fc.double({ min: 0.01, max: 1_000_000, noNaN: true });
const accountType = fc.constantFrom('checking', 'savings');

For composite types, build composite generators that produce valid domain objects.

Step 4: Implement Property Tests

Write one test per property. Follow the framework conventions:

  • Go: func TestPropertyName(t *testing.T) { rapid.Check(t, func(t *rapid.T) { ... }) }
  • Python: @given(...) def test_property_name(...): ...
  • TypeScript: fc.assert(fc.property(..., (...) => { ... }))

Step 5: Add Stateful Tests (If Applicable)

If the module has state transitions (e.g., account lifecycle, order processing):

  1. Model the state machine with a simple reference implementation
  2. Define operations as rules/commands
  3. Define invariants that must hold after every operation
  4. Let the framework generate random sequences of operations

Step 6: Configure Settings

  • Development: 100 examples (fast feedback)
  • CI: 500-1000 examples (thorough coverage)
  • Nightly: 5000+ examples (deep exploration)
  • Set deadline=None for slow operations (database, network)

Step 7: Review and Refine

After running tests:

  • Check for flaky failures (tighten generators if ranges are too broad)
  • Examine shrunk failing examples (the framework minimizes them)
  • Add the shrunk example as an explicit example-based test for regression
  • Verify edge cases are covered (empty inputs, max values, boundary values)

Output Format

Produce:

  1. A test file with clearly named property tests
  2. Generator/strategy definitions (reusable across tests)
  3. Comments explaining each property and why it matters
  4. Settings configuration for dev vs. CI profiles