Agent skill
property-testing-guide
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.
Install this agent skill to your Project
npx add-skill https://github.com/aiskillstore/marketplace/tree/main/skills/emillindfors/property-testing-guide
SKILL.md
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: "test@example.com" };
let json = serialize(user);
assert_eq!(json, r#"{"id":"123","email":"test@example.com"}"#);
}
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:
- Serialization → Suggest roundtrip property
- Sorting/ordering → Suggest invariant properties
- Parsers → Suggest "never panics" property
- Algorithms → Suggest comparison with oracle
- Data structures → Suggest invariant testing
Proactively suggest property-based tests to find edge cases automatically.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
perigon-backend
Perigon ASP.NET Core + EF Core + Aspire conventions
perigon-agent
Pointers for Copilot/agents to apply Perigon conventions
perigon-angular
Angular 21+ standalone/Material/signal conventions for Perigon WebApp
fastapi-mastery
Comprehensive FastAPI development skill covering REST API creation, routing, request/response handling, validation, authentication, database integration, middleware, and deployment. Use when working with FastAPI projects, building APIs, implementing CRUD operations, setting up authentication/authorization, integrating databases (SQL/NoSQL), adding middleware, handling WebSockets, or deploying FastAPI applications. Triggered by requests involving .py files with FastAPI code, API endpoint creation, Pydantic models, or FastAPI-specific features.
context7-efficient
Token-efficient library documentation fetcher using Context7 MCP with 86.8% token savings through intelligent shell pipeline filtering. Fetches code examples, API references, and best practices for JavaScript, Python, Go, Rust, and other libraries. Use when users ask about library documentation, need code examples, want API usage patterns, are learning a new framework, need syntax reference, or troubleshooting with library-specific information. Triggers include questions like "Show me React hooks", "How do I use Prisma", "What's the Next.js routing syntax", or any request for library/framework documentation.
browser-use
Browser automation using Playwright MCP. Navigate websites, fill forms, click elements, take screenshots, and extract data. Use when tasks require web browsing, form submission, web scraping, UI testing, or any browser interaction.
Didn't find tool you were looking for?