--- description: 为Rust强制执行TDD工作流。先写测试,然后实现。使用cargo-llvm-cov验证80%以上的覆盖率。 --- # Rust TDD 命令 该命令使用 `#[test]`、rstest、proptest 和 mockall 来强制执行 Rust 代码的测试驱动开发方法。 ## 该命令的作用 1. **定义类型/特征**:使用 `todo!()` 搭建函数签名 2. **编写测试**:创建全面的测试模块(RED) 3. **运行测试**:验证测试因正确的原因而失败 4. **实现代码**:编写通过测试所需的最少代码(GREEN) 5. **重构**:在保持测试通过的前提下进行改进 6. **检查覆盖率**:使用 cargo-llvm-cov 确保 80%+ 的覆盖率 ## 何时使用 在以下情况使用 `/rust-test`: * 实现新的 Rust 函数、方法或特征时 * 为现有 Rust 代码添加测试覆盖时 * 修复错误时(首先编写失败的测试) * 构建关键业务逻辑时 * 学习 Rust 中的 TDD 工作流程时 ## TDD 循环 ``` RED -> 先编写失败的测试 GREEN -> 实现最小化代码以通过测试 REFACTOR -> 改进代码,保持测试通过 REPEAT -> 下一个测试用例 ``` ## 示例会话 ````text User: /rust-test I need a function to validate user registration Agent: # TDD Session: Registration Validator ## Step 1: Define Interface ```rust // src/validator.rs pub struct RegistrationRequest { pub name: String, pub email: String, pub password: String, } pub enum ValidationResult { Valid, Invalid(Vec), } pub fn validate_registration(request: &RegistrationRequest) -> ValidationResult { todo!() } ``` ## Step 2: Write Tests (RED) ```rust #[cfg(test)] mod tests { use super::*; #[test] fn valid_registration_returns_valid() { let request = RegistrationRequest { name: "Alice".into(), email: "alice@example.com".into(), password: "SecureP@ss1".into(), }; assert!(matches!(validate_registration(&request), ValidationResult::Valid)); } #[test] fn blank_name_returns_invalid() { let request = RegistrationRequest { name: "".into(), email: "alice@example.com".into(), password: "SecureP@ss1".into(), }; match validate_registration(&request) { ValidationResult::Invalid(errors) => { assert!(errors.contains(&"Name is required".to_string())); } ValidationResult::Valid => panic!("expected Invalid"), } } #[test] fn invalid_email_returns_invalid() { let request = RegistrationRequest { name: "Alice".into(), email: "not-an-email".into(), password: "SecureP@ss1".into(), }; assert!(matches!( validate_registration(&request), ValidationResult::Invalid(_) )); } #[test] fn short_password_returns_invalid() { let request = RegistrationRequest { name: "Alice".into(), email: "alice@example.com".into(), password: "short".into(), }; assert!(matches!( validate_registration(&request), ValidationResult::Invalid(_) )); } } ``` ## Step 3: Run Tests — Verify FAIL ```bash $ cargo test running 4 tests test tests::valid_registration_returns_valid ... FAILED (not yet implemented) test tests::blank_name_returns_invalid ... FAILED test tests::invalid_email_returns_invalid ... FAILED test tests::short_password_returns_invalid ... FAILED ``` Tests fail as expected (todo! panic). ## Step 4: Implement Minimal Code (GREEN) ```rust pub fn validate_registration(request: &RegistrationRequest) -> ValidationResult { let mut errors = Vec::new(); if request.name.trim().is_empty() { errors.push("Name is required".into()); } if !request.email.contains('@') { errors.push("Invalid email format".into()); } if request.password.len() < 8 { errors.push("Password must be at least 8 characters".into()); } if errors.is_empty() { ValidationResult::Valid } else { ValidationResult::Invalid(errors) } } ``` ## Step 5: Run Tests — Verify PASS ```bash $ cargo test running 4 tests test tests::valid_registration_returns_valid ... ok test tests::blank_name_returns_invalid ... ok test tests::invalid_email_returns_invalid ... ok test tests::short_password_returns_invalid ... ok test result: ok. 4 passed; 0 failed ``` All tests passing! ## Step 6: Check Coverage ```bash $ cargo llvm-cov Coverage: 100.0% of lines ``` Coverage: 100% ## TDD Complete! ```` ## 测试模式 ### 单元测试 ```rust #[cfg(test)] mod tests { use super::*; #[test] fn adds_two_numbers() { assert_eq!(add(2, 3), 5); } #[test] fn handles_error() -> Result<(), Box> { let result = parse_config(r#"port = 8080"#)?; assert_eq!(result.port, 8080); Ok(()) } } ``` ### 使用 rstest 进行参数化测试 ```rust 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); } ``` ### 异步测试 ```rust #[tokio::test] async fn fetches_data_successfully() { let client = TestClient::new().await; let result = client.get("/data").await; assert!(result.is_ok()); } ``` ### 基于属性的测试 ```rust use proptest::prelude::*; proptest! { #[test] fn encode_decode_roundtrip(input in ".*") { let encoded = encode(&input); let decoded = decode(&encoded).unwrap(); assert_eq!(input, decoded); } } ``` ## 覆盖率命令 ```bash # Summary report cargo llvm-cov # HTML report cargo llvm-cov --html # Fail if below threshold cargo llvm-cov --fail-under-lines 80 # Run specific test cargo test test_name # Run with output cargo test -- --nocapture # Run without stopping on first failure cargo test --no-fail-fast ``` ## 覆盖率目标 | 代码类型 | 目标 | |-----------|--------| | 关键业务逻辑 | 100% | | 公共 API | 90%+ | | 通用代码 | 80%+ | | 生成的 / FFI 绑定 | 排除 | ## TDD 最佳实践 **应做:** * **首先**编写测试,在任何实现之前 * 每次更改后运行测试 * 使用 `assert_eq!` 而非 `assert!` 以获得更好的错误信息 * 在返回 `Result` 的测试中使用 `?` 以获得更清晰的输出 * 测试行为,而非实现 * 包含边界情况(空值、边界值、错误路径) **不应做:** * 在测试之前编写实现 * 跳过 RED 阶段 * 在 `Result::is_err()` 可用时使用 `#[should_panic]` * 在测试中使用 `sleep()` — 应使用通道或 `tokio::time::pause()` * 模拟一切 — 在可行时优先使用集成测试 ## 相关命令 * `/rust-build` - 修复构建错误 * `/rust-review` - 在实现后审查代码 * `/verify` - 运行完整的验证循环 ## 相关 * 技能:`skills/rust-testing/` * 技能:`skills/rust-patterns/`