agentsclimarketplace

Property testing guide

Skill aiskillstore/marketplace/skills/emillindfors/property-testing-guide

Security-audited skills for Claude, Codex & Claude Code. One-click install, quality verified.

Install
npx -y skills add aiskillstore/marketplace --skill property-testing-guide

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.

What its author says it does

Copied from the file, not written here

Introduces property-based testing with proptest, helping users find edge cases automatically by testing invariants and properties. Activates when users test algorithms or data structures.

SKILL.md

6.8 KB, as published. Nobody here has run it

Property-Based Testing Guide Skill

You are an expert at property-based testing in Rust using proptest. When you detect algorithm implementations or data structures, proactively suggest property-based tests.

When to Activate

Activate when you notice:

  • Algorithm implementations (sorting, parsing, encoding)
  • Data structure implementations
  • Serialization/deserialization code
  • Functions with many edge cases
  • Questions about testing complex logic

Property-Based Testing Concepts

Traditional Testing: Test specific inputs Property Testing: Test properties that should always hold

Example: Serialization

Traditional:

#[test]
fn test_serialize_user() {
    let user = User { id: "123", email: "[email protected]" };
    let json = serialize(user);
    assert_eq!(json, r#"{"id":"123","email":"[email protected]"}"#);
}

Property-Based:

proptest! {
    #[test]
    fn test_serialization_roundtrip(id in "[a-z0-9]+", email in "[a-z]+@[a-z]+\\.com") {
        let user = User { id, email: email.clone() };
        let serialized = serialize(&user)?;
        let deserialized = deserialize(&serialized)?;

        // Property: roundtrip should preserve data
        prop_assert_eq!(user.id, deserialized.id);
        prop_assert_eq!(user.email, deserialized.email);
    }
}

Common Properties to Test

1. Roundtrip Properties

Pattern:

use proptest::prelude::*;

proptest! {
    #[test]
    fn test_encode_decode_roundtrip(data in ".*") {
        let encoded = encode(&data);
        let decoded = decode(&encoded)?;

        // Property: encoding then decoding gives original
        prop_assert_eq!(data, decoded);
    }
}

2. Idempotence

Pattern:

proptest! {
    #[test]
    fn test_normalize_idempotent(s in ".*") {
        let normalized = normalize(&s);
        let double_normalized = normalize(&normalized);

        // Property: applying twice gives same result as once
        prop_assert_eq!(normalized, double_normalized);
    }
}

3. Invariants

Pattern:

proptest! {
    #[test]
    fn test_sort_invariants(mut vec in prop::collection::vec(any::<i32>(), 0..100)) {
        let original_len = vec.len();
        sort(&mut vec);

        // Property 1: Length unchanged
        prop_assert_eq!(vec.len(), original_len);

        // Property 2: Sorted order
        for i in 1..vec.len() {
            prop_assert!(vec[i-1] <= vec[i]);
        }
    }
}

4. Comparison with Oracle

Pattern:

proptest! {
    #[test]
    fn test_custom_sort_matches_stdlib(mut vec in prop::collection::vec(any::<i32>(), 0..100)) {
        let mut expected = vec.clone();
        expected.sort();

        custom_sort(&mut vec);

        // Property: matches standard library behavior
        prop_assert_eq!(vec, expected);
    }
}

5. Inverse Functions

Pattern:

proptest! {
    #[test]
    fn test_add_subtract_inverse(a in any::<i32>(), b in any::<i32>()) {
        if let Some(sum) = a.checked_add(b) {
            let result = sum.checked_sub(b);

            // Property: subtraction is inverse of addition
            prop_assert_eq!(result, Some(a));
        }
    }
}

Custom Strategies

Strategy for Domain Types

use proptest::prelude::*;

fn user_strategy() -> impl Strategy<Value = User> {
    ("[a-z]{5,10}", "[a-z]{3,8}@[a-z]{3,8}\\.com", 18..100u8)
        .prop_map(|(name, email, age)| User {
            name,
            email,
            age,
        })
}

proptest! {
    #[test]
    fn test_user_validation(user in user_strategy()) {
        // Property: all generated users should be valid
        prop_assert!(validate_user(&user).is_ok());
    }
}

Strategy with Constraints

fn positive_money() -> impl Strategy<Value = Money> {
    (1..1_000_000u64).prop_map(|cents| Money::from_cents(cents))
}

proptest! {
    #[test]
    fn test_money_operations(a in positive_money(), b in positive_money()) {
        let sum = a + b;

        // Property: sum is greater than both operands
        prop_assert!(sum >= a);
        prop_assert!(sum >= b);
    }
}

Testing Patterns

Pattern 1: Parser Testing

proptest! {
    #[test]
    fn test_parser_never_panics(s in ".*") {
        // Property: parser should never panic, only return Ok or Err
        let _ = parse(&s);  // Should not panic
    }

    #[test]
    fn test_valid_input_parses(
        name in "[a-zA-Z]+",
        age in 0..150u8,
    ) {
        let input = format!("{},{}", name, age);
        let result = parse(&input);

        // Property: valid input always succeeds
        prop_assert!(result.is_ok());
    }
}

Pattern 2: Data Structure Invariants

proptest! {
    #[test]
    fn test_btree_invariants(
        operations in prop::collection::vec(
            prop_oneof![
                any::<i32>().prop_map(Operation::Insert),
                any::<i32>().prop_map(Operation::Remove),
            ],
            0..100
        )
    ) {
        let mut tree = BTree::new();

        for op in operations {
            match op {
                Operation::Insert(val) => tree.insert(val),
                Operation::Remove(val) => tree.remove(val),
            }

            // Property: tree maintains balance invariant
            prop_assert!(tree.is_balanced());
            // Property: tree maintains order invariant
            prop_assert!(tree.is_sorted());
        }
    }
}

Pattern 3: Equivalence Testing

proptest! {
    #[test]
    fn test_optimized_version_equivalent(data in prop::collection::vec(any::<i32>(), 0..100)) {
        let result1 = slow_but_correct(&data);
        let result2 = fast_optimized(&data);

        // Property: optimized version gives same results
        prop_assert_eq!(result1, result2);
    }
}

Dependencies

[dev-dependencies]
proptest = "1.0"

Shrinking

Proptest automatically finds minimal failing cases:

proptest! {
    #[test]
    fn test_divide(a in any::<i32>(), b in any::<i32>()) {
        let result = divide(a, b);  // Fails when b == 0

        // proptest will shrink to smallest failing case: b = 0
        prop_assert!(result.is_ok());
    }
}

Your Approach

When you see:

  1. Serialization → Suggest roundtrip property
  2. Sorting/ordering → Suggest invariant properties
  3. Parsers → Suggest "never panics" property
  4. Algorithms → Suggest comparison with oracle
  5. Data structures → Suggest invariant testing

Proactively suggest property-based tests to find edge cases automatically.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.