返回 Skill 列表
extension
分类: 开发与工程无需 API Key

proptest

使用proptest进行Rust的基于属性的测试。当编写验证任意输入下属性是否成立的测试、实现用于生成测试数据的自定义策略、自动测试边界情况,或者用户提到“proptest”、“基于属性的测试”、“Rust中的模糊测试”、“缩小范围”,或需要生成任意测试数据时使用。触发条件包括请求使用随机输入测试函数、查找边界情况、为自定义类型实现Arbitrary接口,或创建数据生成器。

person作者: jakexiaohubgithub

Proptest - Property-Based Testing for Rust

Proptest tests properties that hold for arbitrary inputs, automatically finding minimal failing cases through shrinking.

Setup

# Cargo.toml
[dev-dependencies]
proptest = "1.0"
proptest-derive = "0.5"  # Optional: for #[derive(Arbitrary)]

Core Patterns

Basic Property Test

use proptest::prelude::*;

proptest! {
    #[test]
    fn my_property(input in 0..100i32) {
        // Property assertions go here
        prop_assert!(input >= 0);
        prop_assert!(input < 100);
    }
}

Multiple Inputs

proptest! {
    #[test]
    fn test_addition_commutative(a in any::<i32>(), b in any::<i32>()) {
        prop_assert_eq!(a.wrapping_add(b), b.wrapping_add(a));
    }
}

Strategies

Strategies define how to generate test values. Core strategy types:

| Strategy | Generates | Example | |----------|-----------|---------| | any::<T>() | Any value of type T | any::<u32>() | | a..b | Range values | 0..100i32 | | "regex" | Strings matching regex | "[a-z]{1,10}" | | Just(v) | Always returns v | Just(42) | | prop_oneof![...] | One of multiple strategies | prop_oneof![Just(1), Just(2)] |

String Strategies

// Regex-based strings
"[a-zA-Z0-9]{1,20}"           // Alphanumeric, 1-20 chars
"\\PC*"                        // Any non-control chars
"[0-9]{4}-[0-9]{2}-[0-9]{2}"  // Date format YYYY-MM-DD

// Any string
any::<String>()

Collection Strategies

use proptest::collection;

// Vectors
collection::vec(any::<i32>(), 0..100)     // Vec<i32> with 0-99 elements
collection::vec("[a-z]+", 1..10)          // Vec<String> of words

// Other collections
collection::hash_map(any::<String>(), any::<i32>(), 0..10)
collection::btree_set(0..1000i32, 5..20)

Strategy Combinators

prop_map - Transform Values

fn point_strategy() -> impl Strategy<Value = Point> {
    (any::<f32>(), any::<f32>())
        .prop_map(|(x, y)| Point { x, y })
}

prop_filter - Reject Values (Use Sparingly)

fn even_numbers() -> impl Strategy<Value = i32> {
    any::<i32>().prop_filter("must be even", |x| x % 2 == 0)
}

// BETTER: Generate directly instead of filtering
fn even_numbers_better() -> impl Strategy<Value = i32> {
    any::<i32>().prop_map(|x| x * 2)
}

prop_flat_map - Dependent Strategies

Use when one generated value determines how to generate another:

// Generate a vec and a valid index into it
fn vec_and_index() -> impl Strategy<Value = (Vec<i32>, usize)> {
    collection::vec(any::<i32>(), 1..100)
        .prop_flat_map(|vec| {
            let len = vec.len();
            (Just(vec), 0..len)
        })
}

prop_compose! Macro

Cleaner syntax for composing strategies:

prop_compose! {
    fn arb_order(max_qty: u32)
        (id in any::<u32>(), item in "[a-z]+", qty in 1..max_qty)
        -> Order
    {
        Order { id: id.to_string(), item, quantity: qty }
    }
}

// Three-arg form for dependent generation
prop_compose! {
    fn vec_and_index()
        (vec in collection::vec(any::<i32>(), 1..100))
        (index in 0..vec.len(), vec in Just(vec))
        -> (Vec<i32>, usize)
    {
        (vec, index)
    }
}

Deriving Arbitrary

use proptest_derive::Arbitrary;

#[derive(Debug, Arbitrary)]
struct User {
    #[proptest(regex = "[a-z]{3,10}")]
    name: String,
    #[proptest(strategy = "18..120u8")]
    age: u8,
}

#[derive(Debug, Arbitrary)]
enum Status {
    Active,
    #[proptest(weight = 3)]  // 3x more likely
    Pending,
    #[proptest(skip)]        // Never generated
    Deleted,
}

Recursive Data Structures

fn arb_tree() -> impl Strategy<Value = Tree> {
    let leaf = any::<i32>().prop_map(Tree::Leaf);

    leaf.prop_recursive(
        8,    // max depth
        256,  // max nodes
        10,   // expected branch size
        |inner| {
            collection::vec(inner, 0..10)
                .prop_map(Tree::Branch)
        }
    )
}

Assertions

| Macro | Purpose | |-------|---------| | prop_assert!(cond) | Assert condition | | prop_assert_eq!(a, b) | Assert equality | | prop_assert_ne!(a, b) | Assert inequality | | prop_assume!(cond) | Skip test case if false (global filter) |

Use prop_assert* instead of regular assert* - they produce cleaner output during shrinking.

Configuration

proptest! {
    #![proptest_config(ProptestConfig {
        cases: 1000,           // Number of test cases (default: 256)
        max_shrink_iters: 100, // Max shrink attempts (default: unlimited)
        .. ProptestConfig::default()
    })]

    #[test]
    fn configured_test(x in any::<i32>()) {
        prop_assert!(true);
    }
}

Failure Persistence

Failed test cases are saved to proptest-regressions/ directory. Add to version control:

git add proptest-regressions/

Best Practices

  1. Prefer generation over filtering - Filtering slows tests and harms shrinking
  2. Use prop_assert* macros - Better error output than assert*
  3. Keep strategies deterministic - Same seed should produce same values
  4. Add regression files to VCS - Ensures bugs stay fixed
  5. Start simple, add complexity - Begin with any::<T>(), constrain as needed
  6. Test properties, not implementations - Focus on invariants: reverse(reverse(x)) == x

Common Patterns

Roundtrip Testing

proptest! {
    #[test]
    fn serialization_roundtrip(original in any::<MyStruct>()) {
        let serialized = serde_json::to_string(&original)?;
        let deserialized: MyStruct = serde_json::from_str(&serialized)?;
        prop_assert_eq!(original, deserialized);
    }
}

Testing Invariants

proptest! {
    #[test]
    fn sorted_output_is_sorted(mut vec in collection::vec(any::<i32>(), 0..100)) {
        vec.sort();
        for window in vec.windows(2) {
            prop_assert!(window[0] <= window[1]);
        }
    }
}

Oracle Testing (Compare Implementations)

proptest! {
    #[test]
    fn fast_matches_slow(input in any::<Input>()) {
        prop_assert_eq!(fast_implementation(&input), slow_reference(&input));
    }
}

For advanced patterns (state machine testing, no_std support, async), see references/advanced.md.