rust-testing
単体テスト、統合テスト、非同期テスト、プロパティベーステスト、モック、カバレッジを含むRustテストパターン。TDD方法論に従う。
DeepseekModel
キュレーション済みスキル
品質 優秀 · 90
v1.0.0
取得
https://deepseekmodel.com/api/download.php?id=affaan-m-ecc-docs-ja-jp-skills-rust-testing-skill-md&format=skill
ダウンロード .skill
標準形式。system_prompt と model_config を収録し、任意の Agent で利用可能
.skill ファイルの system_prompt フィールドの実際の内容。
name rust-testing description 単体テスト、統合テスト、非同期テスト、プロパティベーステスト、モック、カバレッジを含むRustテストパターン。TDD方法論に従う。 origin ECC Rust テストパターン TDD方法論に従って信頼性が高く保守しやすいテストを書くための包括的なRustテストパターン。 使用場面 新しいRustの関数、メソッド、またはトレイトを書く場合 既存のコードにテストカバレッジを追加する場合 パフォーマンスクリティカルなコードのベンチマークを作成する場合 入力検証にプロパティベーステストを実装する場合 RustプロジェクトでTDDワークフローに従う場合 動作原理 ターゲットコードを特定する — テストする関数、トレイト、またはモジュールを見つける テストを書く — #[cfg(test)] モジュール内で #[test] を使用、rstest でパラメータ化テスト、または proptest でプロパティベーステスト 依存関係をモックする — mockall を使用してテスト対象のユニットを分離する テストを実行する (RED) — テストが期待通りに失敗することを確認する 実装する (GREEN) — テストを通過するための最小限のコードを書く リファクタリングする — テストを通過したまま、コードを改善する カバレッジを確認する — cargo-llvm-cov を使用し、80%以上を目標にする RustのTDDワークフロー RED-GREEN-REFACTOR サイクル RED → まず失敗するテストを書く GREEN → テストを通過する最小限のコードを書く REFACTOR → テストを通過したままコードをリファクタリングする REPEAT → 次の要件に進む Rustでの段階的TDD // RED: Write test first, use todo!() as placeholder pub fn add (a: i32 , b: i32 ) -> i32 { todo!() } #[cfg(test)] mod tests { use super::*; #[test] fn test_add () { assert_eq! ( add ( 2 , 3 ), 5 ); } } // cargo test → panics at 'not yet implemented' // GREEN: Replace todo!() with minimal implementation pub fn add (a: i32 , b: i32 ) -> i32 { a + b } // cargo test → PASS, then REFACTOR while keeping tests green 単体テスト モジュールレベルのテスト整理 // src/user.rs pub struct User { pub name: String , pub email: String , } impl User { pub fn new (name: impl Into < String >, email: impl Into < String >) -> Result < Self , String > { let email = email. into (); if !email. contains ( '@' ) { return Err ( format! ( "invalid email: {email}" )); } Ok ( Self { name: name. into (), email }) } pub fn display_name (& self ) -> & str { & self .name } } #[cfg(test)] mod tests { use super::*; #[test] fn creates_user_with_valid_email () { let user = User:: new ( "Alice" , "alice@example.com" ). unwrap (); assert_eq! (user. display_name (), "Alice" ); assert_eq! (user.email, "alice@example.com" ); } #[test] fn rejects_invalid_email () { let result = User:: new ( "Bob" , "not-an-email" ); assert! (result. is_err ()); assert! (result. unwrap_err (). contains ( "invalid email" )); } } アサーションマクロ assert_eq! ( 2 + 2 , 4 ); // Equality assert_ne! ( 2 + 2 , 5 ); // Inequality assert! ( vec! [ 1 , 2 , 3 ]. contains (& 2 )); // Boolean assert_eq! (value, 42 , "expected 42 but got {value}" ); // Custom message assert! (( 0.1_f64 + 0.2 - 0.3 ). abs () < f64 ::EPSILON); // Float comparison エラーとパニックのテスト Result の戻り値のテスト #[test] fn parse_returns_error_for_invalid_input () { let result = parse_config ( "}{invalid" ); assert! (result. is_err ()); // Assert specific error variant let err = result. unwrap_err (); assert! (matches!(err, ConfigError:: ParseError (_))); } #[test] fn parse_succeeds_for_valid_input () -> Result <(), Box < dyn std::error::Error>> { let config = parse_config ( r#"{"port": 8080}"# )?; assert_eq! (config.port, 8080 ); Ok (()) // Test fails if any ? returns Err } パニックのテスト #[test] #[should_panic] fn panics_on_empty_input () { process (&[]); } #[test] #[should_panic(expected = "index out of bounds" )] fn panics_with_specific_message () { let v : Vec < i32 > = vec! []; let _ = v[ 0 ]; } 統合テスト ファイル構造 my_crate/ ├── src/ │ └── lib.rs ├── tests/ # 統合テスト │ ├── api_test.rs # 各ファイルが独立したテストバイナリ │ ├── db_test.rs │ └── common/ # 共有テストユーティリティ │ └── mod.rs 統合テストの書き方 // tests/api_test.rs use my_crate::{App, Config}; #[test] fn full_request_lifecycle () { let config = Config:: test_default (); let app = App:: new (config); let response = app. handle_request ( "/health" ); assert_eq! (response.status, 200 ); assert_eq! (response.body, "OK" ); } 非同期テスト Tokioの使用 #[tokio::test] async fn fetches_data_successfully () { let client = TestClient:: new (). await ; let result = client. get ( "/data" ). await ; assert! (result. is_ok ()); assert_eq! (result. unwrap ().items. len (), 3 ); } #[tokio::test] async fn handles_timeout () { use std::time::Duration; let result = tokio::time:: timeout ( Duration:: from_millis ( 100 ), slow_operation (), ). await ; assert! (result. is_err (), "should have timed out" ); } テスト整理パターン rstest を使用したパラメータ化テスト use rstest::{rstest, fixture}; #[rstest] #[case( "hello" , 5)] #[case( "" , 0)] #[case( "rust" , 4)] fn test_string_length ( #[case] input: & str , #[case] expected: usize ) { assert_eq! (input. len (), expected); } // Fixtures #[fixture] fn test_db () -> TestDb { TestDb:: new_in_memory () } #[rstest] fn test_insert (test_db: TestDb) { test_db. insert ( "key" , "value" ); assert_eq! (test_db. get ( "key" ), Some ( "value" . into ())); } テストヘルパー関数 #[cfg(test)] mod tests { use super::*; /// Creates a test user with sensible defaults. fn make_user (name: & str ) -> User { User:: new (name, & format! ( "{name}@test.com" )). unwrap () } #[test] fn user_display () { let user = make_user ( "alice" ); assert_eq! (user. display_name (), "alice" ); } } proptest を使用したプロパティベーステスト 基本的なプロパティテスト use proptest::prelude::*; proptest! { #[test] fn encode_decode_roundtrip (input in ".*" ) { let encoded = encode (&input); let decoded = decode (&encoded). unwrap (); assert_eq! (input, decoded); } #[test] fn sort_preserves_length ( mut vec in prop::collection:: vec (any::< i32 >(), 0 .. 100 )) { let original_len = vec. len (); vec. sort (); assert_eq! (vec. len (), original_len); } #[test] fn sort_produces_ordered_output ( mut vec in prop::collection:: vec (any::< i32 >(), 0 .. 100 )) { vec. sort (); for window in vec. windows ( 2 ) { assert! (window[ 0 ] <= window[ 1 ]); } } } カスタムストラテジー use proptest::prelude::*; fn valid_email () -> impl Strategy <Value = String > { ( "[a-z]{1,10}" , "[a-z]{1,5}" ) . prop_map (|(user, domain)| format! ( "{user}@{domain}.com" )) } proptest! { #[test] fn accepts_valid_emails (email in valid_email ()) { assert! (User:: new ( "Test" , &email). is_ok ()); } } mockall を使用したモック トレイトベースのモック use mockall::{automock, predicate::eq}; #[automock] trait UserRepository { fn find_by_id (& self , id: u64 ) -> Option <User>; fn save (& self , user: &User) -> Result <(), StorageError>; } #[test] fn service_returns_user_when_found () { let mut mock = MockUserRepository:: new (); mock. expect_find_by_id () . with ( eq ( 42 )) . times ( 1 ) . returning (|_| Some (User { id: 42 , name: "Alice" . into () })); let service = UserService:: new ( Box :: new (mock)); let user = service. get_user ( 42 ). unwrap (); assert_eq! (user.name, "Alice" ); } #[test] fn service_returns_none_when_not_found () { let mut mock = MockUserRepository:: new (); mock. expect_find_by_id () . returning (|_| None ); let service = UserService:: new ( Box :: new (mock)); assert! (service. get_user ( 99 ). is_none ()); } ドキュメントテスト 実行可能なドキュメント /// Adds two numbers together. /// /// # Examples /// /// ``` /// use my_crate::add; /// /// assert_eq!(add(2, 3), 5); /// assert_eq!(add(-1, 1), 0); /// ``` pub fn add (a: i32 , b: i32 ) -> i32 { a + b } /// Parses a config string. /// /// # Errors /// /// Returns `Err` if the input is not valid TOML. /// /// ```no_run /// use my_crate::parse_config; /// /// let config = parse_config(r#"port = 8080"#).unwrap(); /// assert_eq!(config.port, 8080); /// ``` /// /// ```no_run /// use my_crate::parse_config; /// /// assert!(parse_config("}{invalid").is_err());
このスキルを起動するキーワード。クリックでコピーできます。
このスキルにはトリガーワードがありません。
ダウンロードした .skill に含まれるフィールド。
| フィールド | 説明 |
|---|---|
| format | フォーマット識別子(skill/v1) |
| skill_id | スキル固有 ID |
| name | スキル名 |
| version | バージョン |
| description | 説明 |
| category | カテゴリ(配列) |
| trigger_words | トリガーワード |
| tags | タグ |
| source | ソース |
| source_url | ソース URL(本ページ) |
| exported_at | エクスポート日時(ダウンロード毎) |
| system_prompt | システムプロンプト本文 |
| model_config | モデル設定:provider / model / temperature / max_tokens / top_p |
| examples | サンプル |
| install_guide | 各プラットフォームの導入説明(Coze / Dify / Claude / カスタム) |