[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7AKFNMS3FAVNK83W853PZ62":3,"skill-same:01M7AKFNMS3FAVNK83W853PZ62":48},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":25,"commitSha":26,"skillDirectory":27,"forksCount":28,"repositoryHtmlUrl":29,"repositoryHomepageUrl":6,"ownerHtmlUrl":30,"ownerBlogUrl":31,"ownerDescription":32,"updatedAt":33,"install":34,"resources":47},"01M7AKFNMS3FAVNK83W853PZ62","rust-testing",null,"単体テスト、統合テスト、非同期テスト、プロパティベーステスト、モック、カバレッジを含むRustテストパターン。TDD方法論に従う。","Cyrilthetecchieee\u002Fcyril-code-","Cyrilthetecchieee","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F219787998?v=4","01M7AF0MMMKRAA0H61XMJPV92H","Julian Cyril D ","01M7AF0MQ10AVG6QJQ1C2BC0F5",2,"MIT","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fdocs\u002Fja-JP\u002Fskills\u002Frust-testing\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nname: rust-testing\ndescription: 単体テスト、統合テスト、非同期テスト、プロパティベーステスト、モック、カバレッジを含むRustテストパターン。TDD方法論に従う。\norigin: ECC\n---\n\n# Rust テストパターン\n\nTDD方法論に従って信頼性が高く保守しやすいテストを書くための包括的なRustテストパターン。\n\n## 使用場面\n\n* 新しいRustの関数、メソッド、またはトレイトを書く場合\n* 既存のコードにテストカバレッジを追加する場合\n* パフォーマンスクリティカルなコードのベンチマークを作成する場合\n* 入力検証にプロパティベーステストを実装する場合\n* RustプロジェクトでTDDワークフローに従う場合\n\n## 動作原理\n\n1. **ターゲットコードを特定する** — テストする関数、トレイト、またはモジュールを見つける\n2. **テストを書く** — `#[cfg(test)]` モジュール内で `#[test]` を使用、rstest でパラメータ化テスト、または proptest でプロパティベーステスト\n3. **依存関係をモックする** — mockall を使用してテスト対象のユニットを分離する\n4. **テストを実行する (RED)** — テストが期待通りに失敗することを確認する\n5. **実装する (GREEN)** — テストを通過するための最小限のコードを書く\n6. **リファクタリングする** — テストを通過したまま、コードを改善する\n7. **カバレッジを確認する** — cargo-llvm-cov を使用し、80%以上を目標にする\n\n## RustのTDDワークフロー\n\n### RED-GREEN-REFACTOR サイクル\n\n```\nRED     → まず失敗するテストを書く\nGREEN   → テストを通過する最小限のコードを書く\nREFACTOR → テストを通過したままコードをリファクタリングする\nREPEAT  → 次の要件に進む\n```\n\n### Rustでの段階的TDD\n\n```rust\n\u002F\u002F RED: Write test first, use todo!() as placeholder\npub fn add(a: i32, b: i32) -> i32 { todo!() }\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    #[test]\n    fn test_add() { assert_eq!(add(2, 3), 5); }\n}\n\u002F\u002F cargo test → panics at 'not yet implemented'\n```\n\n```rust\n\u002F\u002F GREEN: Replace todo!() with minimal implementation\npub fn add(a: i32, b: i32) -> i32 { a + b }\n\u002F\u002F cargo test → PASS, then REFACTOR while keeping tests green\n```\n\n## 単体テスト\n\n### モジュールレベルのテスト整理\n\n```rust\n\u002F\u002F src\u002Fuser.rs\npub struct User {\n    pub name: String,\n    pub email: String,\n}\n\nimpl User {\n    pub fn new(name: impl Into\u003CString>, email: impl Into\u003CString>) -> Result\u003CSelf, String> {\n        let email = email.into();\n        if !email.contains('@') {\n            return Err(format!(\"invalid email: {email}\"));\n        }\n        Ok(Self { name: name.into(), email })\n    }\n\n    pub fn display_name(&self) -> &str {\n        &self.name\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn creates_user_with_valid_email() {\n        let user = User::new(\"Alice\", \"alice@example.com\").unwrap();\n        assert_eq!(user.display_name(), \"Alice\");\n        assert_eq!(user.email, \"alice@example.com\");\n    }\n\n    #[test]\n    fn rejects_invalid_email() {\n        let result = User::new(\"Bob\", \"not-an-email\");\n        assert!(result.is_err());\n        assert!(result.unwrap_err().contains(\"invalid email\"));\n    }\n}\n```\n\n### アサーションマクロ\n\n```rust\nassert_eq!(2 + 2, 4);                                    \u002F\u002F Equality\nassert_ne!(2 + 2, 5);                                    \u002F\u002F Inequality\nassert!(vec![1, 2, 3].contains(&2));                     \u002F\u002F Boolean\nassert_eq!(value, 42, \"expected 42 but got {value}\");    \u002F\u002F Custom message\nassert!((0.1_f64 + 0.2 - 0.3).abs() \u003C f64::EPSILON);   \u002F\u002F Float comparison\n```\n\n## エラーとパニックのテスト\n\n### `Result` の戻り値のテスト\n\n```rust\n#[test]\nfn parse_returns_error_for_invalid_input() {\n    let result = parse_config(\"}{invalid\");\n    assert!(result.is_err());\n\n    \u002F\u002F Assert specific error variant\n    let err = result.unwrap_err();\n    assert!(matches!(err, ConfigError::ParseError(_)));\n}\n\n#[test]\nfn parse_succeeds_for_valid_input() -> Result\u003C(), Box\u003Cdyn std::error::Error>> {\n    let config = parse_config(r#\"{\"port\": 8080}\"#)?;\n    assert_eq!(config.port, 8080);\n    Ok(()) \u002F\u002F Test fails if any ? returns Err\n}\n```\n\n### パニックのテスト\n\n```rust\n#[test]\n#[should_panic]\nfn panics_on_empty_input() {\n    process(&[]);\n}\n\n#[test]\n#[should_panic(expected = \"index out of bounds\")]\nfn panics_with_specific_message() {\n    let v: Vec\u003Ci32> = vec![];\n    let _ = v[0];\n}\n```\n\n## 統合テスト\n\n### ファイル構造\n\n```text\nmy_crate\u002F\n├── src\u002F\n│   └── lib.rs\n├── tests\u002F              # 統合テスト\n│   ├── api_test.rs     # 各ファイルが独立したテストバイナリ\n│   ├── db_test.rs\n│   └── common\u002F         # 共有テストユーティリティ\n│       └── mod.rs\n```\n\n### 統合テストの書き方\n\n```rust\n\u002F\u002F tests\u002Fapi_test.rs\nuse my_crate::{App, Config};\n\n#[test]\nfn full_request_lifecycle() {\n    let config = Config::test_default();\n    let app = App::new(config);\n\n    let response = app.handle_request(\"\u002Fhealth\");\n    assert_eq!(response.status, 200);\n    assert_eq!(response.body, \"OK\");\n}\n```\n\n## 非同期テスト\n\n### Tokioの使用\n\n```rust\n#[tokio::test]\nasync fn fetches_data_successfully() {\n    let client = TestClient::new().await;\n    let result = client.get(\"\u002Fdata\").await;\n    assert!(result.is_ok());\n    assert_eq!(result.unwrap().items.len(), 3);\n}\n\n#[tokio::test]\nasync fn handles_timeout() {\n    use std::time::Duration;\n    let result = tokio::time::timeout(\n        Duration::from_millis(100),\n        slow_operation(),\n    ).await;\n\n    assert!(result.is_err(), \"should have timed out\");\n}\n```\n\n## テスト整理パターン\n\n### `rstest` を使用したパラメータ化テスト\n\n```rust\nuse rstest::{rstest, fixture};\n\n#[rstest]\n#[case(\"hello\", 5)]\n#[case(\"\", 0)]\n#[case(\"rust\", 4)]\nfn test_string_length(#[case] input: &str, #[case] expected: usize) {\n    assert_eq!(input.len(), expected);\n}\n\n\u002F\u002F Fixtures\n#[fixture]\nfn test_db() -> TestDb {\n    TestDb::new_in_memory()\n}\n\n#[rstest]\nfn test_insert(test_db: TestDb) {\n    test_db.insert(\"key\", \"value\");\n    assert_eq!(test_db.get(\"key\"), Some(\"value\".into()));\n}\n```\n\n### テストヘルパー関数\n\n```rust\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    \u002F\u002F\u002F Creates a test user with sensible defaults.\n    fn make_user(name: &str) -> User {\n        User::new(name, &format!(\"{name}@test.com\")).unwrap()\n    }\n\n    #[test]\n    fn user_display() {\n        let user = make_user(\"alice\");\n        assert_eq!(user.display_name(), \"alice\");\n    }\n}\n```\n\n## `proptest` を使用したプロパティベーステスト\n\n### 基本的なプロパティテスト\n\n```rust\nuse proptest::prelude::*;\n\nproptest! {\n    #[test]\n    fn encode_decode_roundtrip(input in \".*\") {\n        let encoded = encode(&input);\n        let decoded = decode(&encoded).unwrap();\n        assert_eq!(input, decoded);\n    }\n\n    #[test]\n    fn sort_preserves_length(mut vec in prop::collection::vec(any::\u003Ci32>(), 0..100)) {\n        let original_len = vec.len();\n        vec.sort();\n        assert_eq!(vec.len(), original_len);\n    }\n\n    #[test]\n    fn sort_produces_ordered_output(mut vec in prop::collection::vec(any::\u003Ci32>(), 0..100)) {\n        vec.sort();\n        for window in vec.windows(2) {\n            assert!(window[0] \u003C= window[1]);\n        }\n    }\n}\n```\n\n### カスタムストラテジー\n\n```rust\nuse proptest::prelude::*;\n\nfn valid_email() -> impl Strategy\u003CValue = String> {\n    (\"[a-z]{1,10}\", \"[a-z]{1,5}\")\n        .prop_map(|(user, domain)| format!(\"{user}@{domain}.com\"))\n}\n\nproptest! {\n    #[test]\n    fn accepts_valid_emails(email in valid_email()) {\n        assert!(User::new(\"Test\", &email).is_ok());\n    }\n}\n```\n\n## `mockall` を使用したモック\n\n### トレイトベースのモック\n\n```rust\nuse mockall::{automock, predicate::eq};\n\n#[automock]\ntrait UserRepository {\n    fn find_by_id(&self, id: u64) -> Option\u003CUser>;\n    fn save(&self, user: &User) -> Result\u003C(), StorageError>;\n}\n\n#[test]\nfn service_returns_user_when_found() {\n    let mut mock = MockUserRepository::new();\n    mock.expect_find_by_id()\n        .with(eq(42))\n        .times(1)\n        .returning(|_| Some(User { id: 42, name: \"Alice\".into() }));\n\n    let service = UserService::new(Box::new(mock));\n    let user = service.get_user(42).unwrap();\n    assert_eq!(user.name, \"Alice\");\n}\n\n#[test]\nfn service_returns_none_when_not_found() {\n    let mut mock = MockUserRepository::new();\n    mock.expect_find_by_id()\n        .returning(|_| None);\n\n    let service = UserService::new(Box::new(mock));\n    assert!(service.get_user(99).is_none());\n}\n```\n\n## ドキュメントテスト\n\n### 実行可能なドキュメント\n\n````rust\n\u002F\u002F\u002F Adds two numbers together.\n\u002F\u002F\u002F\n\u002F\u002F\u002F # Examples\n\u002F\u002F\u002F\n\u002F\u002F\u002F ```\n\u002F\u002F\u002F use my_crate::add;\n\u002F\u002F\u002F\n\u002F\u002F\u002F assert_eq!(add(2, 3), 5);\n\u002F\u002F\u002F assert_eq!(add(-1, 1), 0);\n\u002F\u002F\u002F ```\npub fn add(a: i32, b: i32) -> i32 {\n    a + b\n}\n\n\u002F\u002F\u002F Parses a config string.\n\u002F\u002F\u002F\n\u002F\u002F\u002F # Errors\n\u002F\u002F\u002F\n\u002F\u002F\u002F Returns `Err` if the input is not valid TOML.\n\u002F\u002F\u002F\n\u002F\u002F\u002F ```no_run\n\u002F\u002F\u002F use my_crate::parse_config;\n\u002F\u002F\u002F\n\u002F\u002F\u002F let config = parse_config(r#\"port = 8080\"#).unwrap();\n\u002F\u002F\u002F assert_eq!(config.port, 8080);\n\u002F\u002F\u002F ```\n\u002F\u002F\u002F\n\u002F\u002F\u002F ```no_run\n\u002F\u002F\u002F use my_crate::parse_config;\n\u002F\u002F\u002F\n\u002F\u002F\u002F assert!(parse_config(\"}{invalid\").is_err());\n\u002F\u002F\u002F ```\npub fn parse_config(input: &str) -> Result\u003CConfig, ParseError> {\n    todo!()\n}\n````\n\n## Criterionを使用したベンチマーク\n\n```toml\n# Cargo.toml\n[dev-dependencies]\ncriterion = { version = \"0.5\", features = [\"html_reports\"] }\n\n[[bench]]\nname = \"benchmark\"\nharness = false\n```\n\n```rust\n\u002F\u002F benches\u002Fbenchmark.rs\nuse criterion::{black_box, criterion_group, criterion_main, Criterion};\n\nfn fibonacci(n: u64) -> u64 {\n    match n {\n        0 | 1 => n,\n        _ => fibonacci(n - 1) + fibonacci(n - 2),\n    }\n}\n\nfn bench_fibonacci(c: &mut Criterion) {\n    c.bench_function(\"fib 20\", |b| b.iter(|| fibonacci(black_box(20))));\n}\n\ncriterion_group!(benches, bench_fibonacci);\ncriterion_main!(benches);\n```\n\n## テストカバレッジ\n\n### カバレッジの実行\n\n```bash\n# Install: cargo install cargo-llvm-cov (or use taiki-e\u002Finstall-action in CI)\ncargo llvm-cov                    # Summary\ncargo llvm-cov --html             # HTML report\ncargo llvm-cov --lcov > lcov.info # LCOV format for CI\ncargo llvm-cov --fail-under-lines 80  # Fail if below threshold\n```\n\n### カバレッジ目標\n\n| コードの種類 | 目標 |\n|-----------|--------|\n| クリティカルなビジネスロジック | 100% |\n| パブリックAPI | 90%以上 |\n| 汎用コード | 80%以上 |\n| 生成済み \u002F FFIバインディング | 除外 |\n\n## テストコマンド\n\n```bash\ncargo test                        # Run all tests\ncargo test -- --nocapture         # Show println output\ncargo test test_name              # Run tests matching pattern\ncargo test --lib                  # Unit tests only\ncargo test --test api_test        # Integration tests only\ncargo test --doc                  # Doc tests only\ncargo test --no-fail-fast         # Don't stop on first failure\ncargo test -- --ignored           # Run ignored tests\n```\n\n## ベストプラクティス\n\n**すべきこと：**\n\n* まずテストを書く (TDD)\n* 単体テストには `#[cfg(test)]` モジュールを使用する\n* 実装ではなく動作をテストする\n* シナリオを説明する記述的なテスト名を使用する\n* より良いエラーメッセージのために `assert!` より `assert_eq!` を優先する\n* クリーンなエラー出力のために `Result` を返すテストでは `?` を使用する\n* テストを独立させる——共有の可変状態なし\n\n**すべきでないこと：**\n\n* `Result::is_err()` をテストできる場合に `#[should_panic]` を使用する\n* すべてをモックする——可能なら統合テストを優先する\n* フレーキーなテストを無視する——修正または分離する\n* テストで `sleep()` を使用する——チャンネル、バリア、または `tokio::time::pause()` を使用する\n* エラーパスのテストをスキップする\n\n## CI統合\n\n```yaml\n# GitHub Actions\ntest:\n  runs-on: ubuntu-latest\n  steps:\n    - uses: actions\u002Fcheckout@v4\n    - uses: dtolnay\u002Frust-toolchain@stable\n      with:\n        components: clippy, rustfmt\n\n    - name: Check formatting\n      run: cargo fmt --check\n\n    - name: Clippy\n      run: cargo clippy -- -D warnings\n\n    - name: Run tests\n      run: cargo test\n\n    - uses: taiki-e\u002Finstall-action@cargo-llvm-cov\n\n    - name: Coverage\n      run: cargo llvm-cov --fail-under-lines 80\n```\n\n**覚えておくこと**：テストはドキュメントである。コードをどのように使うべきかを示している。明確に書き、最新の状態を保つこと。\n","# Rust テストパターン\n\nTDD方法論に従って信頼性が高く保守しやすいテストを書くための包括的なRustテストパターン。\n\n## 使用場面\n\n* 新しいRustの関数、メソッド、またはトレイトを書く場合\n* 既存のコードにテストカバレッジを追加する場合\n* パフォーマンスクリティカルなコードのベンチマークを作成する場合\n* 入力検証にプロパティベーステストを実装する場合\n* RustプロジェクトでTDDワークフローに従う場合\n\n## 動作原理\n\n1. **ターゲットコードを特定する** — テストする関数、トレイト、またはモジュールを見つける\n2. **テストを書く** — `#[cfg(test)]` モジュール内で `#[test]` を使用、rstest でパラメータ化テスト、または proptest でプロパティベーステスト\n3. **依存関係をモックする** — mockall を使用してテスト対象のユニットを分離する\n4. **テストを実行する (RED)** — テストが期待通りに失敗することを確認する\n5. **実装する (GREEN)** — テストを通過するための最小限のコードを書く\n6. **リファクタリングする** — テストを通過したまま、コードを改善する\n7. **カバレッジを確認する** — cargo-llvm-cov を使用し、80%以上を目標にする\n\n## RustのTDDワークフロー\n\n### RED-GREEN-REFACTOR サイクル\n\n```\nRED     → まず失敗するテストを書く\nGREEN   → テストを通過する最小限のコードを書く\nREFACTOR → テストを通過したままコードをリファクタリングする\nREPEAT  → 次の要件に進む\n```\n\n### Rustでの段階的TDD\n\n```rust\n\u002F\u002F RED: Write test first, use todo!() as placeholder\npub fn add(a: i32, b: i32) -> i32 { todo!() }\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    #[test]\n    fn test_add() { assert_eq!(add(2, 3), 5); }\n}\n\u002F\u002F cargo test → panics at 'not yet implemented'\n```\n\n```rust\n\u002F\u002F GREEN: Replace todo!() with minimal implementation\npub fn add(a: i32, b: i32) -> i32 { a + b }\n\u002F\u002F cargo test → PASS, then REFACTOR while keeping tests green\n```\n\n## 単体テスト\n\n### モジュールレベルのテスト整理\n\n```rust\n\u002F\u002F src\u002Fuser.rs\npub struct User {\n    pub name: String,\n    pub email: String,\n}\n\nimpl User {\n    pub fn new(name: impl Into\u003CString>, email: impl Into\u003CString>) -> Result\u003CSelf, String> {\n        let email = email.into();\n        if !email.contains('@') {\n            return Err(format!(\"invalid email: {email}\"));\n        }\n        Ok(Self { name: name.into(), email })\n    }\n\n    pub fn display_name(&self) -> &str {\n        &self.name\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn creates_user_with_valid_email() {\n        let user = User::new(\"Alice\", \"alice@example.com\").unwrap();\n        assert_eq!(user.display_name(), \"Alice\");\n        assert_eq!(user.email, \"alice@example.com\");\n    }\n\n    #[test]\n    fn rejects_invalid_email() {\n        let result = User::new(\"Bob\", \"not-an-email\");\n        assert!(result.is_err());\n        assert!(result.unwrap_err().contains(\"invalid email\"));\n    }\n}\n```\n\n### アサーションマクロ\n\n```rust\nassert_eq!(2 + 2, 4);                                    \u002F\u002F Equality\nassert_ne!(2 + 2, 5);                                    \u002F\u002F Inequality\nassert!(vec![1, 2, 3].contains(&2));                     \u002F\u002F Boolean\nassert_eq!(value, 42, \"expected 42 but got {value}\");    \u002F\u002F Custom message\nassert!((0.1_f64 + 0.2 - 0.3).abs() \u003C f64::EPSILON);   \u002F\u002F Float comparison\n```\n\n## エラーとパニックのテスト\n\n### `Result` の戻り値のテスト\n\n```rust\n#[test]\nfn parse_returns_error_for_invalid_input() {\n    let result = parse_config(\"}{invalid\");\n    assert!(result.is_err());\n\n    \u002F\u002F Assert specific error variant\n    let err = result.unwrap_err();\n    assert!(matches!(err, ConfigError::ParseError(_)));\n}\n\n#[test]\nfn parse_succeeds_for_valid_input() -> Result\u003C(), Box\u003Cdyn std::error::Error>> {\n    let config = parse_config(r#\"{\"port\": 8080}\"#)?;\n    assert_eq!(config.port, 8080);\n    Ok(()) \u002F\u002F Test fails if any ? returns Err\n}\n```\n\n### パニックのテスト\n\n```rust\n#[test]\n#[should_panic]\nfn panics_on_empty_input() {\n    process(&[]);\n}\n\n#[test]\n#[should_panic(expected = \"index out of bounds\")]\nfn panics_with_specific_message() {\n    let v: Vec\u003Ci32> = vec![];\n    let _ = v[0];\n}\n```\n\n## 統合テスト\n\n### ファイル構造\n\n```text\nmy_crate\u002F\n├── src\u002F\n│   └── lib.rs\n├── tests\u002F              # 統合テスト\n│   ├── api_test.rs     # 各ファイルが独立したテストバイナリ\n│   ├── db_test.rs\n│   └── common\u002F         # 共有テストユーティリティ\n│       └── mod.rs\n```\n\n### 統合テストの書き方\n\n```rust\n\u002F\u002F tests\u002Fapi_test.rs\nuse my_crate::{App, Config};\n\n#[test]\nfn full_request_lifecycle() {\n    let config = Config::test_default();\n    let app = App::new(config);\n\n    let response = app.handle_request(\"\u002Fhealth\");\n    assert_eq!(response.status, 200);\n    assert_eq!(response.body, \"OK\");\n}\n```\n\n## 非同期テスト\n\n### Tokioの使用\n\n```rust\n#[tokio::test]\nasync fn fetches_data_successfully() {\n    let client = TestClient::new().await;\n    let result = client.get(\"\u002Fdata\").await;\n    assert!(result.is_ok());\n    assert_eq!(result.unwrap().items.len(), 3);\n}\n\n#[tokio::test]\nasync fn handles_timeout() {\n    use std::time::Duration;\n    let result = tokio::time::timeout(\n        Duration::from_millis(100),\n        slow_operation(),\n    ).await;\n\n    assert!(result.is_err(), \"should have timed out\");\n}\n```\n\n## テスト整理パターン\n\n### `rstest` を使用したパラメータ化テスト\n\n```rust\nuse rstest::{rstest, fixture};\n\n#[rstest]\n#[case(\"hello\", 5)]\n#[case(\"\", 0)]\n#[case(\"rust\", 4)]\nfn test_string_length(#[case] input: &str, #[case] expected: usize) {\n    assert_eq!(input.len(), expected);\n}\n\n\u002F\u002F Fixtures\n#[fixture]\nfn test_db() -> TestDb {\n    TestDb::new_in_memory()\n}\n\n#[rstest]\nfn test_insert(test_db: TestDb) {\n    test_db.insert(\"key\", \"value\");\n    assert_eq!(test_db.get(\"key\"), Some(\"value\".into()));\n}\n```\n\n### テストヘルパー関数\n\n```rust\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    \u002F\u002F\u002F Creates a test user with sensible defaults.\n    fn make_user(name: &str) -> User {\n        User::new(name, &format!(\"{name}@test.com\")).unwrap()\n    }\n\n    #[test]\n    fn user_display() {\n        let user = make_user(\"alice\");\n        assert_eq!(user.display_name(), \"alice\");\n    }\n}\n```\n\n## `proptest` を使用したプロパティベーステスト\n\n### 基本的なプロパティテスト\n\n```rust\nuse proptest::prelude::*;\n\nproptest! {\n    #[test]\n    fn encode_decode_roundtrip(input in \".*\") {\n        let encoded = encode(&input);\n        let decoded = decode(&encoded).unwrap();\n        assert_eq!(input, decoded);\n    }\n\n    #[test]\n    fn sort_preserves_length(mut vec in prop::collection::vec(any::\u003Ci32>(), 0..100)) {\n        let original_len = vec.len();\n        vec.sort();\n        assert_eq!(vec.len(), original_len);\n    }\n\n    #[test]\n    fn sort_produces_ordered_output(mut vec in prop::collection::vec(any::\u003Ci32>(), 0..100)) {\n        vec.sort();\n        for window in vec.windows(2) {\n            assert!(window[0] \u003C= window[1]);\n        }\n    }\n}\n```\n\n### カスタムストラテジー\n\n```rust\nuse proptest::prelude::*;\n\nfn valid_email() -> impl Strategy\u003CValue = String> {\n    (\"[a-z]{1,10}\", \"[a-z]{1,5}\")\n        .prop_map(|(user, domain)| format!(\"{user}@{domain}.com\"))\n}\n\nproptest! {\n    #[test]\n    fn accepts_valid_emails(email in valid_email()) {\n        assert!(User::new(\"Test\", &email).is_ok());\n    }\n}\n```\n\n## `mockall` を使用したモック\n\n### トレイトベースのモック\n\n```rust\nuse mockall::{automock, predicate::eq};\n\n#[automock]\ntrait UserRepository {\n    fn find_by_id(&self, id: u64) -> Option\u003CUser>;\n    fn save(&self, user: &User) -> Result\u003C(), StorageError>;\n}\n\n#[test]\nfn service_returns_user_when_found() {\n    let mut mock = MockUserRepository::new();\n    mock.expect_find_by_id()\n        .with(eq(42))\n        .times(1)\n        .returning(|_| Some(User { id: 42, name: \"Alice\".into() }));\n\n    let service = UserService::new(Box::new(mock));\n    let user = service.get_user(42).unwrap();\n    assert_eq!(user.name, \"Alice\");\n}\n\n#[test]\nfn service_returns_none_when_not_found() {\n    let mut mock = MockUserRepository::new();\n    mock.expect_find_by_id()\n        .returning(|_| None);\n\n    let service = UserService::new(Box::new(mock));\n    assert!(service.get_user(99).is_none());\n}\n```\n\n## ドキュメントテスト\n\n### 実行可能なドキュメント\n\n````rust\n\u002F\u002F\u002F Adds two numbers together.\n\u002F\u002F\u002F\n\u002F\u002F\u002F # Examples\n\u002F\u002F\u002F\n\u002F\u002F\u002F ```\n\u002F\u002F\u002F use my_crate::add;\n\u002F\u002F\u002F\n\u002F\u002F\u002F assert_eq!(add(2, 3), 5);\n\u002F\u002F\u002F assert_eq!(add(-1, 1), 0);\n\u002F\u002F\u002F ```\npub fn add(a: i32, b: i32) -> i32 {\n    a + b\n}\n\n\u002F\u002F\u002F Parses a config string.\n\u002F\u002F\u002F\n\u002F\u002F\u002F # Errors\n\u002F\u002F\u002F\n\u002F\u002F\u002F Returns `Err` if the input is not valid TOML.\n\u002F\u002F\u002F\n\u002F\u002F\u002F ```no_run\n\u002F\u002F\u002F use my_crate::parse_config;\n\u002F\u002F\u002F\n\u002F\u002F\u002F let config = parse_config(r#\"port = 8080\"#).unwrap();\n\u002F\u002F\u002F assert_eq!(config.port, 8080);\n\u002F\u002F\u002F ```\n\u002F\u002F\u002F\n\u002F\u002F\u002F ```no_run\n\u002F\u002F\u002F use my_crate::parse_config;\n\u002F\u002F\u002F\n\u002F\u002F\u002F assert!(parse_config(\"}{invalid\").is_err());\n\u002F\u002F\u002F ```\npub fn parse_config(input: &str) -> Result\u003CConfig, ParseError> {\n    todo!()\n}\n````\n\n## Criterionを使用したベンチマーク\n\n```toml\n# Cargo.toml\n[dev-dependencies]\ncriterion = { version = \"0.5\", features = [\"html_reports\"] }\n\n[[bench]]\nname = \"benchmark\"\nharness = false\n```\n\n```rust\n\u002F\u002F benches\u002Fbenchmark.rs\nuse criterion::{black_box, criterion_group, criterion_main, Criterion};\n\nfn fibonacci(n: u64) -> u64 {\n    match n {\n        0 | 1 => n,\n        _ => fibonacci(n - 1) + fibonacci(n - 2),\n    }\n}\n\nfn bench_fibonacci(c: &mut Criterion) {\n    c.bench_function(\"fib 20\", |b| b.iter(|| fibonacci(black_box(20))));\n}\n\ncriterion_group!(benches, bench_fibonacci);\ncriterion_main!(benches);\n```\n\n## テストカバレッジ\n\n### カバレッジの実行\n\n```bash\n# Install: cargo install cargo-llvm-cov (or use taiki-e\u002Finstall-action in CI)\ncargo llvm-cov                    # Summary\ncargo llvm-cov --html             # HTML report\ncargo llvm-cov --lcov > lcov.info # LCOV format for CI\ncargo llvm-cov --fail-under-lines 80  # Fail if below threshold\n```\n\n### カバレッジ目標\n\n| コードの種類 | 目標 |\n|-----------|--------|\n| クリティカルなビジネスロジック | 100% |\n| パブリックAPI | 90%以上 |\n| 汎用コード | 80%以上 |\n| 生成済み \u002F FFIバインディング | 除外 |\n\n## テストコマンド\n\n```bash\ncargo test                        # Run all tests\ncargo test -- --nocapture         # Show println output\ncargo test test_name              # Run tests matching pattern\ncargo test --lib                  # Unit tests only\ncargo test --test api_test        # Integration tests only\ncargo test --doc                  # Doc tests only\ncargo test --no-fail-fast         # Don't stop on first failure\ncargo test -- --ignored           # Run ignored tests\n```\n\n## ベストプラクティス\n\n**すべきこと：**\n\n* まずテストを書く (TDD)\n* 単体テストには `#[cfg(test)]` モジュールを使用する\n* 実装ではなく動作をテストする\n* シナリオを説明する記述的なテスト名を使用する\n* より良いエラーメッセージのために `assert!` より `assert_eq!` を優先する\n* クリーンなエラー出力のために `Result` を返すテストでは `?` を使用する\n* テストを独立させる——共有の可変状態なし\n\n**すべきでないこと：**\n\n* `Result::is_err()` をテストできる場合に `#[should_panic]` を使用する\n* すべてをモックする——可能なら統合テストを優先する\n* フレーキーなテストを無視する——修正または分離する\n* テストで `sleep()` を使用する——チャンネル、バリア、または `tokio::time::pause()` を使用する\n* エラーパスのテストをスキップする\n\n## CI統合\n\n```yaml\n# GitHub Actions\ntest:\n  runs-on: ubuntu-latest\n  steps:\n    - uses: actions\u002Fcheckout@v4\n    - uses: dtolnay\u002Frust-toolchain@stable\n      with:\n        components: clippy, rustfmt\n\n    - name: Check formatting\n      run: cargo fmt --check\n\n    - name: Clippy\n      run: cargo clippy -- -D warnings\n\n    - name: Run tests\n      run: cargo test\n\n    - uses: taiki-e\u002Finstall-action@cargo-llvm-cov\n\n    - name: Coverage\n      run: cargo llvm-cov --fail-under-lines 80\n```\n\n**覚えておくこと**：テストはドキュメントである。コードをどのように使うべきかを示している。明確に書き、最新の状態を保つこと。\n",{"name":5,"origin":24,"description":7},"ECC","main","57e7aa6b6e03bce1a7d7862ba9c2ec312f7f19b5","src\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fdocs\u002Fja-JP\u002Fskills\u002Frust-testing",0,"https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee","https:\u002F\u002Fwithcyril.me\u002F","Hi, I’m Julian Cyril D, a Computer Science and Business Systems student at PSNACET.\r\nI’m passionate about full-stack development, UI\u002FUX design, and building sma","2026-11-04 23:28:50",{"source":8,"skillName":5,"commands":35,"prompt":45,"zipAvailable":46},[36,39,42],{"tool":37,"command":38},"npx","npx skills add Cyrilthetecchieee\u002Fcyril-code- --skill rust-testing",{"tool":40,"command":41},"bunx","bunx skills add Cyrilthetecchieee\u002Fcyril-code- --skill rust-testing",{"tool":43,"command":44},"pnpm","pnpm dlx skills add Cyrilthetecchieee\u002Fcyril-code- --skill rust-testing","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fdocs\u002Fja-JP\u002Fskills\u002Frust-testing\u002FSKILL.md",true,[],[49,58,65,73,80,87],{"skillNo":50,"name":51,"nameZh":6,"description":52,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":53,"licenseFileUrl":54,"sourceHtmlUrl":55,"categories":56,"keywords":57,"publishedAt":19},"01M7AQV6CYX8RMC78420V9WZ62","webapp-testing","Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.","Apache-2.0","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fwebapp-testing\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fwebapp-testing\u002FSKILL.md",[],[],{"skillNo":59,"name":60,"nameZh":6,"description":61,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":62,"categories":63,"keywords":64,"publishedAt":19},"01M7AQV2895N3S1MEX6K76G6G2","youtube-downloader","Download YouTube videos with customizable quality and format options. Use this skill when the user asks to download, save, or grab YouTube videos. Supports various quality settings (best, 1080p, 720p, 480p, 360p), multiple formats (mp4, webm, mkv), and audio-only downloads as MP3.","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fvideo-downloader\u002FSKILL.md",[],[],{"skillNo":66,"name":67,"nameZh":6,"description":68,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":53,"licenseFileUrl":69,"sourceHtmlUrl":70,"categories":71,"keywords":72,"publishedAt":19},"01M7AQTRNX62DNKVZ5GXEHRBKB","theme-factory","Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors\u002Ffonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly.","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Ftheme-factory\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Ftheme-factory\u002FSKILL.md",[],[],{"skillNo":74,"name":75,"nameZh":6,"description":76,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":77,"categories":78,"keywords":79,"publishedAt":19},"01M7AQTNNVHAAN9H6DA8T6ZVE0","template-skill","Replace with description of the skill and when Claude should use it.","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Ftemplate-skill\u002FSKILL.md",[],[],{"skillNo":81,"name":82,"nameZh":6,"description":83,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":84,"categories":85,"keywords":86,"publishedAt":19},"01M7AQTJ3MQ6AKX152MEX2MYCC","tailored-resume-generator","Analyzes job descriptions and generates tailored resumes that highlight relevant experience, skills, and achievements to maximize interview chances","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Ftailored-resume-generator\u002FSKILL.md",[],[],{"skillNo":88,"name":89,"nameZh":6,"description":90,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":53,"licenseFileUrl":91,"sourceHtmlUrl":92,"categories":93,"keywords":94,"publishedAt":19},"01M7AQTD7D5MYET2YWG7HG9WM3","slack-gif-creator","Toolkit for creating animated GIFs optimized for Slack, with validators for size constraints and composable animation primitives. This skill applies when users request animated GIFs or emoji animations for Slack from descriptions like \"make me a GIF for Slack of X doing Y\".","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fslack-gif-creator\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FCyrilthetecchieee\u002Fcyril-code-\u002Fblob\u002Fmain\u002Fsrc\u002Fcyril_code\u002Fassets\u002F.skills_repo\u002Fslack-gif-creator\u002FSKILL.md",[],[]]