
关于
Rust 测试模式,包括单元测试、集成测试、异步测试、基于属性的测试、Mock 和覆盖率。遵循 TDD 方法论。
name: rust-testing description: Rust 测试模式,包括单元测试、集成测试、异步测试、基于属性的测试、模拟和覆盖率。遵循 TDD 方法论。 origin: ECC
Rust 测试模式
全面的 Rust 测试模式,用于编写可靠、可维护的测试,遵循 TDD 方法论。
使用场景
- 编写新的 Rust 函数、方法或 trait
- 为现有代码添加测试覆盖
- 为性能关键代码创建基准测试
- 为输入验证实现基于属性的测试
- 在 Rust 项目中遵循 TDD 工作流
工作原理
- 识别目标代码 — 找到要测试的函数、trait 或模块
- 编写测试 — 在
#[cfg(test)]模块中使用#[test],使用 rstest 进行参数化测试,或使用 proptest 进行基于属性的测试 - 模拟依赖 — 使用 mockall 隔离被测单元
- 运行测试(红色) — 验证测试以预期错误失败
- 实现(绿色) — 编写最少代码使测试通过
- 重构 — 在保持测试通过的同时改进代码
- 检查覆盖率 — 使用 cargo-llvm-cov,目标 80%+
Rust 的 TDD 工作流
红-绿-重构循环
RED → Write a failing test first
GREEN → Write minimal code to pass the test
REFACTOR → Improve code while keeping tests green
REPEAT → Continue with next requirement
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
错误和 Panic 测试
测试 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
}
测试 Panic
#[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/ # Integration tests
│ ├── api_test.rs # Each file is a separate test binary
│ ├── db_test.rs
│ └── common/ # Shared test utilities
│ └── 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_secs(1), slow_operation()).await;
assert!(result.is_err()); // Timeout error
}
限制
- 仅在任务明确符合上述范围时使用此技能。
- 不要将输出视为环境特定验证、测试或专家审查的替代品。
- 如果缺少必需的输入、权限、安全边界或成功标准,请停下来要求澄清。
兼容工具
Claude CodeCursor
标签
测试

