Property-Based Testing Guide
Use this skill proactively during development when you encounter patterns where PBT provides stronger coverage than example-based tests.
When to Invoke (Automatic Detection)
Invoke this skill when you detect:
- Serialization pairs:
encode/decode, serialize/deserialize, toJSON/fromJSON, pack/unpack
- Parsers: URL parsing, config parsing, protocol parsing, string-to-structured-data
- Normalization:
normalize, sanitize, clean, canonicalize, format
- Validators:
is_valid, validate, check_* (especially with normalizers)
- Data structures: Custom collections with
add/remove/get operations
- Mathematical/algorithmic: Pure functions, sorting, ordering, comparators
- Smart contracts: Solidity/Vyper contracts, token operations, state invariants, access control
Priority by pattern:
| Pattern |
Property |
Priority |
| encode/decode pair |
Roundtrip |
HIGH |
| Pure function |
Multiple |
HIGH |
| Validator |
Valid after normalize |
MEDIUM |
| Sorting/ordering |
Idempotence + ordering |
MEDIUM |
| Normalization |
Idempotence |
MEDIUM |
| Builder/factory |
Output invariants |
LOW |
| Smart contract |
State invariants |
HIGH |
When NOT to Use
Do NOT use this skill for:
- Simple CRUD operations without transformation logic
- One-off scripts or throwaway code
- Code with side effects that cannot be isolated (network calls, database writes)
- Tests where specific example cases are sufficient and edge cases are well-understood
- Integration or end-to-end testing (PBT is best for unit/component testing)
Property Catalog (Quick Reference)
| Property |
Formula |
When to Use |
| Roundtrip |
decode(encode(x)) == x |
Serialization, conversion pairs |
| Idempotence |
f(f(x)) == f(x) |
Normalization, formatting, sorting |
| Invariant |
Property holds before/after |
Any transformation |
| Commutativity |
f(a, b) == f(b, a) |
Binary/set operations |
| Associativity |
f(f(a,b), c) == f(a, f(b,c)) |
Combining operations |
| Identity |
f(x, identity) == x |
Operations with neutral element |
| Inverse |
f(g(x)) == x |
encrypt/decrypt, compress/decompress |
| Oracle |
new_impl(x) == reference(x) |
Optimization, refactoring |
| Easy to Verify |
is_sorted(sort(x)) |
Complex algorithms |
| No Exception |
No crash on valid input |
Baseline property |
Strength hierarchy (weakest to strongest):
No Exception → Type Preservation → Invariant → Idempotence → Roundtrip
Decision Tree
Based on the current task, read the appropriate section:
TASK: Writing new tests
→ Read [{baseDir}/references/generating.md]({baseDir}/references/generating.md) (test generation patterns and examples)
→ Then [{baseDir}/references/strategies.md]({baseDir}/references/strategies.md) if input generation is complex
TASK: Designing a new feature
→ Read [{baseDir}/references/design.md]({baseDir}/references/design.md) (Property-Driven Development approach)
TASK: Code is difficult to test (mixed I/O, missing inverses)
→ Read [{baseDir}/references/refactoring.md]({baseDir}/references/refactoring.md) (refactoring patterns for testability)
TASK: Reviewing existing PBT tests
→ Read [{baseDir}/references/reviewing.md]({baseDir}/references/reviewing.md) (quality checklist and anti-patterns)
TASK: Test failed, need to interpret
→ Read [{baseDir}/references/interpreting-failures.md]({baseDir}/references/interpreting-failures.md) (failure analysis and bug classification)
TASK: Need library reference
→ Read [{baseDir}/references/libraries.md]({baseDir}/references/libraries.md) (PBT libraries by language, includes smart contract tools)
How to Suggest PBT
When you detect a high-value pattern while writing tests, offer PBT as an option:
"I notice encode_message/decode_message is a serialization pair. Property-based testing with a roundtrip property would provide stronger coverage than example tests. Want me to use that approach?"
If codebase already uses a PBT library (Hypothesis, fast-check, proptest, Echidna), be more direct:
"This codebase uses Hypothesis. I'll write property-based tests for this serialization pair using a roundtrip property."
If user declines, write good example-based tests without further prompting.
When NOT to Use PBT
- Simple CRUD without complex validation
- UI/presentation logic
- Integration tests requiring complex external setup
- Prototyping where requirements are fluid
- User explicitly requests example-based tests only
Red Flags
- Recommending trivial getters/setters
- Missing paired operations (encode without decode)
- Ignoring type hints (well-typed = easier to test)
- Overwhelming user with candidates (limit to top 5-10)
- Being pushy after user declines
Rationalizations to Reject
Do not accept these shortcuts:
- "Example tests are good enough" - If serialization/parsing/normalization is involved, PBT finds edge cases examples miss
- "The function is simple" - Simple functions with complex input domains (strings, floats, nested structures) benefit most from PBT
- "We don't have time" - PBT tests are often shorter than comprehensive example suites
- "It's too hard to write generators" - Most PBT libraries have excellent built-in strategies; custom generators are rarely needed
- "The test failed, so it's a bug" - Failures require validation; see interpreting-failures.md
- "No crash means it works" - "No exception" is the weakest property; always push for stronger guarantees
Source: trailofbits/skills → plugins/property-based-testing/skills/property-based-testing/SKILL.md
1---2name: property-based-testing3description: Provides guidance for property-based testing across multiple languages and smart contracts. Use when writing tests, reviewing code with serialization/validation/parsing patterns, designing features, or when property-based testing would provide stronger coverage than example-based tests.4---567# Property-Based Testing Guide89Use this skill proactively during development when you encounter patterns where PBT provides stronger coverage than example-based tests.1011## When to Invoke (Automatic Detection)1213**Invoke this skill when you detect:**1415- **Serialization pairs**: `encode`/`decode`, `serialize`/`deserialize`, `toJSON`/`fromJSON`, `pack`/`unpack`16- **Parsers**: URL parsing, config parsing, protocol parsing, string-to-structured-data17- **Normalization**: `normalize`, `sanitize`, `clean`, `canonicalize`, `format`18- **Validators**: `is_valid`, `validate`, `check_*` (especially with normalizers)19- **Data structures**: Custom collections with `add`/`remove`/`get` operations20- **Mathematical/algorithmic**: Pure functions, sorting, ordering, comparators21- **Smart contracts**: Solidity/Vyper contracts, token operations, state invariants, access control2223**Priority by pattern:**2425| Pattern | Property | Priority |26|---------|----------|----------|27| encode/decode pair | Roundtrip | HIGH |28| Pure function | Multiple | HIGH |29| Validator | Valid after normalize | MEDIUM |30| Sorting/ordering | Idempotence + ordering | MEDIUM |31| Normalization | Idempotence | MEDIUM |32| Builder/factory | Output invariants | LOW |33| Smart contract | State invariants | HIGH |3435## When NOT to Use3637Do NOT use this skill for:38- Simple CRUD operations without transformation logic39- One-off scripts or throwaway code40- Code with side effects that cannot be isolated (network calls, database writes)41- Tests where specific example cases are sufficient and edge cases are well-understood42- Integration or end-to-end testing (PBT is best for unit/component testing)4344## Property Catalog (Quick Reference)4546| Property | Formula | When to Use |47|----------|---------|-------------|48| **Roundtrip** | `decode(encode(x)) == x` | Serialization, conversion pairs |49| **Idempotence** | `f(f(x)) == f(x)` | Normalization, formatting, sorting |50| **Invariant** | Property holds before/after | Any transformation |51| **Commutativity** | `f(a, b) == f(b, a)` | Binary/set operations |52| **Associativity** | `f(f(a,b), c) == f(a, f(b,c))` | Combining operations |53| **Identity** | `f(x, identity) == x` | Operations with neutral element |54| **Inverse** | `f(g(x)) == x` | encrypt/decrypt, compress/decompress |55| **Oracle** | `new_impl(x) == reference(x)` | Optimization, refactoring |56| **Easy to Verify** | `is_sorted(sort(x))` | Complex algorithms |57| **No Exception** | No crash on valid input | Baseline property |5859**Strength hierarchy** (weakest to strongest):60No Exception → Type Preservation → Invariant → Idempotence → Roundtrip6162## Decision Tree6364Based on the current task, read the appropriate section:6566```67TASK: Writing new tests68 → Read [{baseDir}/references/generating.md]({baseDir}/references/generating.md) (test generation patterns and examples)69 → Then [{baseDir}/references/strategies.md]({baseDir}/references/strategies.md) if input generation is complex7071TASK: Designing a new feature72 → Read [{baseDir}/references/design.md]({baseDir}/references/design.md) (Property-Driven Development approach)7374TASK: Code is difficult to test (mixed I/O, missing inverses)75 → Read [{baseDir}/references/refactoring.md]({baseDir}/references/refactoring.md) (refactoring patterns for testability)7677TASK: Reviewing existing PBT tests78 → Read [{baseDir}/references/reviewing.md]({baseDir}/references/reviewing.md) (quality checklist and anti-patterns)7980TASK: Test failed, need to interpret81 → Read [{baseDir}/references/interpreting-failures.md]({baseDir}/references/interpreting-failures.md) (failure analysis and bug classification)8283TASK: Need library reference84 → Read [{baseDir}/references/libraries.md]({baseDir}/references/libraries.md) (PBT libraries by language, includes smart contract tools)85```8687## How to Suggest PBT8889When you detect a high-value pattern while writing tests, **offer PBT as an option**:9091> "I notice `encode_message`/`decode_message` is a serialization pair. Property-based testing with a roundtrip property would provide stronger coverage than example tests. Want me to use that approach?"9293**If codebase already uses a PBT library** (Hypothesis, fast-check, proptest, Echidna), be more direct:9495> "This codebase uses Hypothesis. I'll write property-based tests for this serialization pair using a roundtrip property."9697**If user declines**, write good example-based tests without further prompting.9899## When NOT to Use PBT100101- Simple CRUD without complex validation102- UI/presentation logic103- Integration tests requiring complex external setup104- Prototyping where requirements are fluid105- User explicitly requests example-based tests only106107## Red Flags108109- Recommending trivial getters/setters110- Missing paired operations (encode without decode)111- Ignoring type hints (well-typed = easier to test)112- Overwhelming user with candidates (limit to top 5-10)113- Being pushy after user declines114115## Rationalizations to Reject116117Do not accept these shortcuts:118119- **"Example tests are good enough"** - If serialization/parsing/normalization is involved, PBT finds edge cases examples miss120- **"The function is simple"** - Simple functions with complex input domains (strings, floats, nested structures) benefit most from PBT121- **"We don't have time"** - PBT tests are often shorter than comprehensive example suites122- **"It's too hard to write generators"** - Most PBT libraries have excellent built-in strategies; custom generators are rarely needed123- **"The test failed, so it's a bug"** - Failures require validation; see [interpreting-failures.md]({baseDir}/references/interpreting-failures.md)124- **"No crash means it works"** - "No exception" is the weakest property; always push for stronger guarantees125126---127128**Source:** [`trailofbits/skills`](https://github.com/trailofbits/skills) → `plugins/property-based-testing/skills/property-based-testing/SKILL.md`