jpskill.com
🛠️ 開発・MCP コミュニティ

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.

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o property-testing-guide.zip https://jpskill.com/download/19026.zip && unzip -o property-testing-guide.zip && rm property-testing-guide.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/19026.zip -OutFile "$d\property-testing-guide.zip"; Expand-Archive "$d\property-testing-guide.zip" -DestinationPath $d -Force; ri "$d\property-testing-guide.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して property-testing-guide.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → property-testing-guide フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

[Skill 名] property-testing-guide

プロパティベーステストガイドスキル

あなたはRustでproptestを使ったプロパティベーステストのエキスパートです。アルゴリズムの実装やデータ構造を検出した場合、プロパティベーステストを積極的に提案してください。

アクティベートするタイミング

以下を検出した場合にアクティベートしてください。

  • アルゴリズムの実装(ソート、パース、エンコード)
  • データ構造の実装
  • シリアライゼーション/デシリアライゼーションコード
  • 多くのエッジケースを持つ関数
  • 複雑なロジックのテストに関する質問

プロパティベーステストの概念

従来のテスト: 特定の入力をテストします プロパティテスト: 常に成り立つべきプロパティをテストします

例: シリアライゼーション

従来:

#[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"}"#);
}

プロパティベース:

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);
    }
}

テストする一般的なプロパティ

1. ラウンドトリッププロパティ

パターン:

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. 冪等性

パターン:

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. 不変条件

パターン:

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. オラクルとの比較

パターン:

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. 逆関数

パターン:

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));
        }
    }
}

カスタムストラテジー

ドメイン型のためのストラテジー

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());
    }
}

制約付きストラテジー

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);
    }
}

テストパターン

パターン 1: パーサーテスト

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());
    }
}

パターン 2: データ構造の不変条件

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());
        }
    }
}

パターン 3: 同等性テスト

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);
    }
}

依存関係

[dev-dependencies]
proptest = "1.0"

シュリンキング

Proptestは最小の失敗を自動的に見つけます

(原文がここで切り詰められています)

📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

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:

  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.