rust-testing
Patrones de pruebas en Rust incluyendo pruebas unitarias, de integración, async, basadas en propiedades, mocking y cobertura. Sigue la metodología TDD.
DeepseekModel
官方收录技能
质量 优秀 · 90
v1.0.0
获取
https://deepseekmodel.com/api/download.php?id=affaan-m-ecc-docs-es-skills-rust-testing-skill-md&format=skill
下载 .skill
标准格式,含 system_prompt 与 model_config,导入任意 Agent 框架即可使用
.skill 文件中 system_prompt 字段的实际内容。
name rust-testing description Patrones de pruebas en Rust incluyendo pruebas unitarias, de integración, async, basadas en propiedades, mocking y cobertura. Sigue la metodología TDD. origin ECC Patrones de Pruebas Rust Patrones completos de pruebas en Rust para escribir pruebas confiables y mantenibles siguiendo la metodología TDD. Cuándo Usar Escribir nuevas funciones, métodos o traits en Rust Agregar cobertura de pruebas a código existente Crear benchmarks para código con requisitos de rendimiento Implementar pruebas basadas en propiedades para validación de entrada Seguir el flujo de trabajo TDD en proyectos Rust Cómo Funciona Identificar el código objetivo — Encontrar la función, trait o módulo a probar Escribir una prueba — Usar #[test] en un módulo #[cfg(test)] , rstest para pruebas parametrizadas, o proptest para pruebas basadas en propiedades Mockear dependencias — Usar mockall para aislar la unidad bajo prueba Ejecutar pruebas (ROJO) — Verificar que la prueba falla con el error esperado Implementar (VERDE) — Escribir el código mínimo para que pase Refactorizar — Mejorar mientras se mantienen las pruebas en verde Verificar cobertura — Usar cargo-llvm-cov, objetivo 80%+ Flujo de Trabajo TDD en Rust El Ciclo ROJO-VERDE-REFACTORIZAR ROJO → Escribir primero una prueba que falle VERDE → Escribir el código mínimo para que pase REFACTORIZAR → Mejorar el código manteniendo las pruebas en verde REPETIR → Continuar con el siguiente requisito TDD Paso a Paso en Rust // ROJO: Escribir la prueba primero, usar todo!() como 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 → panic en 'not yet implemented' // VERDE: Reemplazar todo!() con implementación mínima pub fn add (a: i32 , b: i32 ) -> i32 { a + b } // cargo test → PASS, luego REFACTORIZAR manteniendo pruebas en verde Pruebas Unitarias Organización de Pruebas a Nivel de Módulo // 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" )); } } Macros de Aserción assert_eq! ( 2 + 2 , 4 ); // Igualdad assert_ne! ( 2 + 2 , 5 ); // Desigualdad assert! ( vec! [ 1 , 2 , 3 ]. contains (& 2 )); // Booleano assert_eq! (value, 42 , "expected 42 but got {value}" ); // Mensaje personalizado assert! (( 0.1_f64 + 0.2 - 0.3 ). abs () < f64 ::EPSILON); // Comparación de flotantes Pruebas de Errores y Panics Probar Retornos de Result #[test] fn parse_returns_error_for_invalid_input () { let result = parse_config ( "}{invalid" ); assert! (result. is_err ()); // Verificar variante de error específica 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 (()) // La prueba falla si algún ? retorna Err } Probar Panics #[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 ]; } Pruebas de Integración Estructura de Archivos my_crate/ ├── src/ │ └── lib.rs ├── tests/ # Pruebas de integración │ ├── api_test.rs # Cada archivo es un binario de prueba separado │ ├── db_test.rs │ └── common/ # Utilidades de prueba compartidas │ └── mod.rs Escribir Pruebas de Integración // 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" ); } Pruebas Async Con 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" ); } Patrones de Organización de Pruebas Pruebas Parametrizadas con 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 ())); } Helpers de Prueba #[cfg(test)] mod tests { use super::*; /// Crea un usuario de prueba con valores predeterminados sensatos. 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" ); } } Pruebas Basadas en Propiedades con proptest Pruebas de Propiedades Básicas 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 ]); } } } Estrategias Personalizadas 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 ()); } } Mocking con mockall Mocking Basado en Traits 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 ()); } Pruebas de Documentación Documentación Ejecutable /// Suma dos números. /// /// # 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 } /// Parsea una cadena de configuración. /// /// # Errors /// /// Retorna `Err` si la entrada no es TOML válido. /// /// ```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());
Agent 识别该技能的关键词,点击任意一个即可复制。
该技能未提供触发词。
下载的 .skill 包内含以下字段。
| 字段 | 说明 |
|---|---|
| format | 格式标识(skill/v1) |
| skill_id | 技能唯一 ID |
| name | 技能名称 |
| version | 版本号 |
| description | 技能描述 |
| category | 所属分类(数组) |
| trigger_words | 触发词列表 |
| tags | 标签列表 |
| source | 来源标识 |
| source_url | 来源链接(本页地址) |
| exported_at | 导出时间(每次下载生成) |
| system_prompt | 系统提示词正文 |
| model_config | 模型参数:provider / model / temperature / max_tokens / top_p |
| examples | 示例 |
| install_guide | 各平台导入说明(Coze / Dify / Claude / 自定义框架) |