CtrlK
BlogDocsLog inGet started
Tessl Logo

evilissimo/property-based-testing

Generates **property-based tests** that use randomized input generation to validate invariants and contracts (rather than hand-picked examples). Triggers when the conversation involves: PBT frameworks (Hypothesis library for Python, fast-check for TypeScript, proptest for Rust, rapid for Go, RapidCheck for C++); concepts like invariants, contracts, round-trip symmetry, encode/decode, serialize/deserialize, generative testing, or shrinking; or requests to find edge cases that example-based tests miss — e.g., "find edge cases automatically", "test all possible inputs", "verify this property holds". Does NOT trigger for: writing regular example-based unit tests, debugging, CI/CD setup, UI/component testing, or integration/E2E testing. Identifies up to 7 property patterns (round-trip, idempotence, invariance, metamorphic, inverse, ordering, no-crash), designs input generators, writes property tests, and extracts regression tests from failures.

91

1.11x
Quality

90%

Does it follow best practices?

Impact

94%

1.11x

Average score across 5 eval scenarios

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

rust.mdreferences/

Rust: proptest

Dependency: cargo add --dev proptest

Core API

use proptest::prelude::*;

Common Strategies

any::<i32>()                        // Any i32
0..100i32                           // Range: 0..99
prop::num::i64::ANY                 // Any i64
any::<f64>()                        // Any f64 (includes NaN, inf)
"[a-zA-Z]+"                         // String matching regex (use `prop::string::*` for more control)
"([0-9]{1,3}\\.){3}[0-9]{1,3}"     // Regex-based string generation
any::<bool>()                       // true or false
proptest::option::of(any::<i32>())  // Option<i32>
proptest::collection::vec(any::<i32>(), 0..100)   // Vec<i32>
proptest::collection::vec(any::<i32>(), 1..=10)   // Sized vec
proptest::collection::hash_map("[a-z]+", any::<i32>(), 0..10)  // HashMap
(any::<i32>(), any::<String>())     // Tuples (up to 10 elements)
prop_oneof![Just(0), Just(1)]       // One of several constant values
prop::sample::select(&["a", "b"])   // Select from a slice

Writing Property Tests (proptest! macro)

proptest! {
    #[test]
    fn test_sort_length_invariant(mut v: Vec<i32>) {
        let original_len = v.len();
        v.sort();
        prop_assert_eq!(v.len(), original_len);
    }

    #[test]
    fn test_sort_ordered(mut v: Vec<i32>) {
        v.sort();
        for i in 0..v.len().saturating_sub(1) {
            prop_assert!(v[i] <= v[i + 1]);
        }
    }
}

Using prop_assert! vs assert!

Always use prop_assert! / prop_assert_eq! inside proptest! — they properly report the shrunken input on failure. Regular assert! panics without the nice failure output.

Preconditions with prop_assume!

proptest! {
    #[test]
    fn test_division(a: i32, b: i32) {
        prop_assume!(b != 0);
        prop_assert_eq!(a / b * b + a % b, a);  // Basic integer division property
    }
}

Strategies Outside proptest!

#[test]
fn sorted_list_properties() {
    let strategy = proptest::collection::vec(any::<i32>(), 0..100);
    // Run with default config
    proptest!(|(v in strategy)| {
        let mut sorted = v.clone();
        sorted.sort();
        prop_assert_eq!(sorted.len(), v.len());
    });
}

Custom Strategies (Strategy Combinators)

// Map
let even_ints = any::<i32>().prop_map(|x| x * 2);

// Filter
let nonzero = any::<i32>().prop_filter("must be nonzero", |x| *x != 0);

// And-then (chain dependent strategies)
let user_name = "[a-zA-Z0-9_]+"
    .prop_map(|s: String| -> String { s })
    .prop_filter("no empty names", |s| !s.is_empty());

Struct/Object Building

#[derive(Debug, Clone)]
struct User {
    name: String,
    age: u8,
}

fn user_strategy() -> impl Strategy<Value = User> {
    ("[a-zA-Z]+", 0..120u8).prop_map(|(name, age)| User { name, age })
}

proptest! {
    #[test]
    fn test_user_properties(user in user_strategy()) {
        prop_assert!(!user.name.is_empty());
        prop_assert!(user.age <= 150);
    }
}

Configuration

// Per-test config
proptest! {
    #![proptest_config = ProptestConfig { cases: 500, .. ProptestConfig::default() }]
    #[test]
    fn test_heavy(v: Vec<i32>) { ... }
}

// Or inline
#[test]
fn test_custom_config() {
    let config = ProptestConfig { cases: 10, .. ProptestConfig::default() };
    proptest!(config, |(v in any::<Vec<i32>>())| {
        // fast check
    });
}

Reproducing Failures

proptest saves the seed in the failure output:

Test failed: assertion failed: ...; minimal failing input: v = [3, 1, 2]
successes: 42, failures: 1, shrinks: 12

Re-run with the same seed via environment:

PROPTEST_FACTOR=12345 cargo test <test_name>

Or set seed in config:

ProptestConfig { seed: Some(42), .. ProptestConfig::default() }

Running

cargo test                           # All tests
cargo test test_sort                 # Specific test
PROPTEST_CASES=500 cargo test       # Override iteration count

SKILL.md

tessl.json

tile.json