844 releases

new 0.0.876 Mar 13, 2025
0.0.863 Feb 28, 2025
0.0.805 Dec 31, 2024
0.0.774 Nov 30, 2024
0.0.7 Nov 11, 2021

#113 in Development tools

Download history 1279/week @ 2024-11-21 1189/week @ 2024-11-28 1113/week @ 2024-12-05 1234/week @ 2024-12-12 1065/week @ 2024-12-19 1517/week @ 2024-12-26 1161/week @ 2025-01-02 1017/week @ 2025-01-09 1057/week @ 2025-01-16 749/week @ 2025-01-23 107/week @ 2025-01-30 93/week @ 2025-02-06 42/week @ 2025-02-13 24/week @ 2025-02-20 1053/week @ 2025-02-27 959/week @ 2025-03-06

2,095 downloads per month
Used in 4 crates

MIT/Apache

115KB
2K SLoC

prop-check-rs

prop-check-rs is a property-based testing library written in Rust. It leverages functional programming concepts to efficiently generate and validate test data.

Read this in other languages: 日本語

Workflow Status crates.io docs.rs tokei

What is Property-Based Testing?

Property-based testing is a testing methodology where instead of testing specific input values, you define properties that your program should satisfy and then verify these properties against a large number of randomly generated inputs. This approach helps discover edge cases that developers might not have anticipated.

Features

  • Rich generators: Easily generate test data of various types
  • Functional programming style: Composable API utilizing monads
  • State-based testing: Support for state machine simulation
  • Advanced customization: Define your own generators and properties

Installation

Add the following to your Cargo.toml:

[dependencies]
prop-check-rs = "0.0.862"

Basic Usage

1. Simple Property Test

Here's an example testing a property about list length:

use prop_check_rs::gen::Gens;
use prop_check_rs::prop::{for_all_gen, test_with_prop};
use prop_check_rs::rng::RNG;
use anyhow::Result;

#[test]
fn test_list_length_property() -> Result<()> {
    // Generator for a list of integers from 0 to 100
    let gen = Gens::list_of_n(10, Gens::choose_i32(0, 100));
    
    // Property: The list length is always 10
    let prop = for_all_gen(gen, |list| {
        list.len() == 10
    });
    
    // Test the property (max size 1, 100 test cases)
    test_with_prop(prop, 1, 100, RNG::new())
}

2. Choosing Values

use prop_check_rs::gen::Gens;
use prop_check_rs::prop::{for_all_gen, test_with_prop};
use prop_check_rs::rng::RNG;
use anyhow::Result;

#[test]
fn test_one_of() -> Result<()> {
    // Generator that selects one of the specified characters
    let gen = Gens::one_of_values(['a', 'b', 'c', 'x', 'y', 'z']);
    
    // Property: The selected character is always one of the specified characters
    let prop = for_all_gen(gen, move |value| {
        log::info!("value = {}", value);
        ['a', 'b', 'c', 'x', 'y', 'z'].contains(&value)
    });
    
    test_with_prop(prop, 1, 100, RNG::new())
}

3. Using Sized Generators

use prop_check_rs::gen::Gens;
use prop_check_rs::prop::{for_all_gen_for_size, test_with_prop};
use prop_check_rs::rng::RNG;
use anyhow::Result;

#[test]
fn test_sized_generator() -> Result<()> {
    let gen = Gens::one_of_values(['a', 'b', 'c', 'x', 'y', 'z']);
    
    // Generate lists based on size
    let prop = for_all_gen_for_size(
        move |size| Gens::list_of_n(size as usize, gen.clone()),
        move || {
            move |list| {
                // Verify that the list length matches the size
                log::info!("list = {:?}", list);
                true
            }
        },
    );
    
    // Max size 10, 100 test cases
    test_with_prop(prop, 10, 100, RNG::new())
}

Key Components

Gen

Gen<A> is a generator for values of type A. It provides methods like map, flat_map, and and_then to create new generators from existing ones.

// Generator for integers from 1 to 100
let int_gen = Gens::choose_i32(1, 100);

// Generator that converts integers to strings
let string_gen = int_gen.map(|n| n.to_string());

Gens

Gens is a factory for creating various generators. It provides generators for:

  • Basic types (integers, floating-point numbers, characters, booleans, etc.)
  • Lists
  • Optional values
  • Choosing one from multiple options
  • Probability-based selection
// Generators for basic types
let int_gen = Gens::one_i32();
let float_gen = Gens::one_f64();
let bool_gen = Gens::one_bool();

// Generator with a specified range
let range_gen = Gens::choose_i32(1, 100);

// Generator for lists
let list_gen = Gens::list_of_n(10, range_gen);

// Generator that chooses one from multiple options
let choice_gen = Gens::one_of_values(["apple", "banana", "orange"]);

// Generator based on probabilities
let weighted_gen = Gens::frequency_values([(1, "rare"), (5, "common"), (2, "uncommon")]);

Prop

Prop is a structure representing a property. A property defines a condition to verify against values generated by a generator.

// Property verifying that integers are always positive
let positive_prop = for_all_gen(Gens::choose_i32(1, 100), |n| n > 0);

// Combining multiple properties
let combined_prop = positive_prop.and(another_prop);

State<S, A>

State<S, A> is a monad representing a computation with state S that produces a value of type A. This allows composing computations while maintaining state.

// Get the state
let get_state = State::<i32, i32>::get();

// Set the state
let set_state = State::<i32, ()>::set(42);

// Modify the state
let modify_state = State::<i32, ()>::modify(|s| s + 1);

// Compose stateful computations
let computation = get_state.flat_map(|s| {
    if s > 0 {
        State::pure(s * 2)
    } else {
        State::pure(0)
    }
});

Advanced Usage Examples

Testing State Machines

The machine.rs module provides an example of simulating a state machine. Here's an example of a candy vending machine simulation:

// Input
enum Input {
    Coin,
    Turn,
}

// State machine
struct Machine {
    locked: bool,
    candies: i32,
    coins: i32,
}

// Simulating the state machine
let inputs = vec![Input::Coin, Input::Turn, Input::Coin, Input::Turn];
let simulation = Machine::simulate_machine(inputs);
let result = simulation.run(Machine { locked: true, candies: 5, coins: 10 });

Creating Custom Generators

You can create your own generators to generate domain-specific test data:

// Generator for valid email addresses
fn email_gen() -> Gen<String> {
    let username_gen = Gens::list_of_n(8, Gens::choose_char('a', 'z'))
        .map(|chars| chars.into_iter().collect::<String>());
    
    let domain_gen = Gens::one_of_values(["example.com", "test.org", "mail.net"]);
    
    username_gen.and_then(domain_gen, |username, domain| {
        format!("{}@{}", username, domain)
    })
}

// Usage example
let prop = for_all_gen(email_gen(), |email| {
    // Email validation logic
    email.contains('@')
});

Benchmarks

prop-check-rs includes optimizations for efficiently generating large amounts of test data. To run the benchmarks:

cargo bench

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~3MB
~49K SLoC