R

Rapid Property-based Testing Engine

Boost productivity with intelligent property-based testing for multiple languages. Built for Claude Code with best practices and real-world patterns.

SkillCommunitytestingv1.0.0MIT
0 views0 copies

Property-Based Testing Engine

Advanced property-based testing framework that generates random inputs, discovers edge cases, and verifies program invariants automatically, going beyond example-based testing to find bugs you wouldn't think to test for.

When to Use This Skill

Choose Property-Based Testing when:

  • Testing functions with large or complex input spaces
  • Verifying mathematical properties (commutativity, idempotency, roundtrip)
  • Finding edge cases in parsers, serializers, and data transformations
  • Testing API contracts and data validation logic
  • Validating state machines and concurrent systems

Consider alternatives when:

  • Testing UI interactions — use end-to-end testing
  • Testing specific business scenarios — use example-based tests
  • Need deterministic CI behavior — seed your generators

Quick Start

# Activate property-based testing claude skill activate rapid-property-based-testing-engine # Add property tests to a module claude "Write property-based tests for the UserValidator class" # Find edge cases claude "Use property testing to find edge cases in the parseDate function"

Example Property Tests

// Using fast-check for TypeScript/JavaScript import fc from 'fast-check'; // Property: JSON.parse is the inverse of JSON.stringify test('JSON roundtrip preserves data', () => { fc.assert( fc.property( fc.jsonValue(), (value) => { const roundtripped = JSON.parse(JSON.stringify(value)); expect(roundtripped).toEqual(value); } ) ); }); // Property: sorting is idempotent test('sorting twice equals sorting once', () => { fc.assert( fc.property( fc.array(fc.integer()), (arr) => { const sortedOnce = [...arr].sort((a, b) => a - b); const sortedTwice = [...sortedOnce].sort((a, b) => a - b); expect(sortedTwice).toEqual(sortedOnce); } ) ); }); // Property: email validation accepts valid emails and rejects invalid test('email validator consistency', () => { const validEmailArb = fc.tuple( fc.stringOf(fc.constantFrom(...'abcdefghijklmnopqrstuvwxyz0123456789'.split('')), { minLength: 1, maxLength: 20 }), fc.constantFrom('gmail.com', 'example.org', 'test.co.uk') ).map(([local, domain]) => `${local}@${domain}`); fc.assert( fc.property(validEmailArb, (email) => { expect(isValidEmail(email)).toBe(true); }) ); });

Core Concepts

Common Property Patterns

PatternDescriptionExample
Roundtripencode then decode returns originalparse(stringify(x)) === x
Idempotenceapplying twice equals applying oncesort(sort(x)) === sort(x)
Invariantproperty holds for all valid inputslength(filter(x)) <= length(x)
Commutativityorder doesn't matteradd(a, b) === add(b, a)
Associativitygrouping doesn't matterconcat(a, concat(b, c)) === concat(concat(a, b), c)
Oraclecompare against reference implementationfastSort(x) === referenceSort(x)
Metamorphicrelate outputs of related inputssearch(db, q1 AND q2) ⊆ search(db, q1)

Arbitrary Generators

GeneratorProducesExample
fc.integer()Random integers-2147483648 to 2147483647
fc.string()Unicode strings"", "aB3!@", "🎉"
fc.array()Arrays of values[], [1, 2, 3]
fc.object()Plain objects{a: 1, b: "x"}
fc.date()Date objectsnew Date(...)
fc.jsonValue()Any JSON-valid valueNested objects, arrays, primitives
fc.oneof()One of multiple generatorsUnion types
CustomDomain-specific typesuserArb, orderArb
# Python with Hypothesis from hypothesis import given, strategies as st, settings @given(st.lists(st.integers())) def test_sort_preserves_length(xs): assert len(sorted(xs)) == len(xs) @given(st.lists(st.integers())) def test_sort_preserves_elements(xs): assert sorted(sorted(xs)) == sorted(xs) assert set(sorted(xs)) == set(xs) # Custom strategy for domain objects @st.composite def user_strategy(draw): return User( name=draw(st.text(min_size=1, max_size=100)), age=draw(st.integers(min_value=0, max_value=150)), email=draw(st.emails()), ) @given(user_strategy()) @settings(max_examples=1000) def test_user_serialization_roundtrip(user): serialized = user.to_json() deserialized = User.from_json(serialized) assert deserialized == user

Configuration

ParameterDescriptionDefault
num_runsNumber of random inputs per property100
seedRandom seed for reproducibilityRandom
max_shrinksMaximum shrinking iterations on failure100
timeout_per_runTimeout per individual test run5s
verboseShow generated examples during testingfalse
endOnFailureStop after first failing propertytrue
examplesExplicit examples to always test[]

Best Practices

  1. Test properties, not examples — Instead of testing add(2, 3) === 5, test add(a, b) === add(b, a) (commutativity) and add(a, 0) === a (identity). Properties verify behavior across the entire input space, not just specific cases you thought of.

  2. Invest in custom generators for domain types — Generic generators produce unrealistic inputs that waste test budget. Build generators that produce valid domain objects (users, orders, transactions) that exercise realistic code paths and constraint boundaries.

  3. Use shrinking to get minimal failing examples — When a property fails, the framework automatically shrinks the input to the smallest case that still fails. This makes debugging dramatically easier. Ensure custom generators support shrinking by building on primitive generators.

  4. Combine property tests with example tests — Property tests find unexpected edge cases but can miss specific business rules. Use example-based tests for known scenarios and regulatory requirements, property tests for invariants and boundary exploration.

  5. Pin seeds for CI determinism, randomize locally — Use a fixed seed in CI to prevent flaky builds, but run with random seeds during development. When random testing finds a failure, add the specific failing case as an explicit example test.

Common Issues

Property tests are slow because they generate too many complex inputs. Constrain generator sizes with maxLength, maxDepth, and custom filters. Use fc.statistics() to understand the distribution of generated inputs and ensure test budget is spent on interesting cases, not trivial ones.

Failing to identify useful properties for business logic. Start with these universal properties: roundtrip (serialize/deserialize), idempotence (applying operation twice), "no crash" (function doesn't throw for any valid input), and comparison with a simpler reference implementation. These catch surprisingly many bugs.

Flaky test failures that can't be reproduced. Always log the seed value when a property fails. Use fc.assert(property, { seed: <failing_seed> }) to reproduce deterministically. If failures are timing-dependent, the property may be testing concurrent behavior that needs explicit modeling rather than random exploration.

Community

Reviews

Write a review

No reviews yet. Be the first to review this template!

Similar Templates