Day 20: 单元测试与文档测试
1. 测试基础
Section titled “1. 测试基础”Rust内置测试框架,支持三种测试:
- 单元测试:测试单个函数或模块
- 集成测试:测试多个模块的协作
- 文档测试:测试代码示例的正确性
2. 单元测试:#[test]
Section titled “2. 单元测试:#[test]”在源文件中使用 #[cfg(test)] 模块包含测试:
// src/lib.rs 或 src/main.rs
pub fn add(a: i32, b: i32) -> i32 { a + b}
pub fn multiply(a: i32, b: i32) -> i32 { a * b}
pub fn divide(a: f64, b: f64) -> Result<f64, &'static str> { if b == 0.0 { return Err("除数不能为零"); } Ok(a / b)}
// 单元测试模块#[cfg(test)]mod tests { use super::*; // 导入父模块的项
#[test] fn test_add() { assert_eq!(add(2, 3), 5); assert_eq!(add(-1, 1), 0); assert_eq!(add(0, 0), 0); }
#[test] fn test_multiply() { assert_eq!(multiply(3, 4), 12); assert_eq!(multiply(-2, 3), -6); assert_eq!(multiply(0, 100), 0); }
#[test] fn test_divide_success() { assert_eq!(divide(10.0, 2.0).unwrap(), 5.0); assert_eq!(divide(9.0, 3.0).unwrap(), 3.0); }
#[test] fn test_divide_by_zero() { let result = divide(10.0, 0.0); assert!(result.is_err()); assert_eq!(result.unwrap_err(), "除数不能为零"); }
// 使用 #[should_panic] 测试 panic #[test] #[should_panic(expected = "除数不能为零")] fn test_divide_panic() { divide(10.0, 0.0).unwrap(); }
// 使用 Result<(), T> 作为测试返回类型 #[test] fn test_divide_with_result() -> Result<(), &'static str> { let result = divide(10.0, 2.0)?; assert_eq!(result, 5.0); Ok(()) }}3. 常用断言宏
Section titled “3. 常用断言宏”#[cfg(test)]mod assertion_tests { // assert! - 条件为真 #[test] fn test_assert() { assert!(true); assert!(1 + 1 == 2); }
// assert_eq! - 相等 #[test] fn test_assert_eq() { assert_eq!(2 + 2, 4); assert_eq!("hello".len(), 5); }
// assert_ne! - 不等 #[test] fn test_assert_ne() { assert_ne!(2 + 2, 5); assert_ne!("hello", "world"); }
// 自定义消息 #[test] fn test_with_message() { let result = 2 + 2; assert_eq!( result, 4, "期望 2 + 2 等于 4,但得到 {}", result ); }}4. 集成测试
Section titled “4. 集成测试”集成测试位于 tests/ 目录下,用于测试多个模块的协作:
// 注意:集成测试会编译为独立的 crate,需要导入主 crate
use my_project::{add, multiply, divide};
#[test]fn test_integration_add_multiply() { let result = add(multiply(2, 3), 4); assert_eq!(result, 10);}
#[test]fn test_integration_divide() { let result = divide(20.0, 4.0).unwrap(); assert_eq!(result, 5.0);}
// tests/helpers.rspub fn setup() -> Vec<i32> { vec![1, 2, 3]}
pub fn teardown() { // 清理资源}# 运行测试cargo test # 运行所有测试
cargo test test_add # 运行指定名称的测试cargo test --test integration # 运行集成测试cargo test --lib # 仅运行库测试(单元测试)cargo test -- --nocapture # 显示 println! 输出5. 文档测试
Section titled “5. 文档测试”使用 /// 文档注释编写测试,直接验证示例代码:
/// 将两个数字相加////// # Examples////// ```/// assert_eq!(my_project::add(2, 3), 5);/// ```////// ```/// # // 隐藏的设置代码/// use my_project::add;/// assert_eq!(add(-1, 1), 0);/// ```pub fn add(a: i32, b: i32) -> i32 { a + b}
/// 计算阶乘////// # Examples////// ```/// assert_eq!(my_project::factorial(5), 120);/// ```////// # Panics////// 在输入为负数时会 panic/// ```/// // 使用 should_panic 测试这个行为/// ```pub fn factorial(n: u32) -> u32 { if n == 0 { 1 } else { n * factorial(n - 1) }}# 运行文档测试cargo test --doc
# 仅运行特定函数的文档测试cargo test --doc -- factorial6. 条件编译与测试模块
Section titled “6. 条件编译与测试模块”#[cfg(test)] 确保测试代码仅在测试时编译:
pub fn complex_algorithm(data: &[i32]) -> i32 { // 真正的算法实现 data.iter().sum()}
#[cfg(test)]mod tests { use super::*;
// 测试辅助函数 fn create_test_data() -> Vec<i32> { vec![1, 2, 3, 4, 5] }
#[test] fn test_algorithm() { let data = create_test_data(); assert_eq!(complex_algorithm(&data), 15); }}
// 模块级别的 #[cfg(test)]#[cfg(test)]mod test_helpers { // 仅为测试提供的辅助代码 pub fn mock_api_server() { // 启动模拟服务器 }}7. 测试的组织结构
Section titled “7. 测试的组织结构”建议的测试组织方式:
// 模块:核心业务逻辑mod calculator { pub fn add(a: i32, b: i32) -> i32 { a + b } pub fn subtract(a: i32, b: i32) -> i32 { a - b }
#[cfg(test)] mod tests { use super::*;
#[test] fn test_add() { assert_eq!(add(1, 2), 3); }
#[test] fn test_subtract() { assert_eq!(subtract(5, 3), 2); } }}
// 模块:数据结构mod queue { pub struct Queue<T> { items: Vec<T>, }
impl<T> Queue<T> { pub fn new() -> Self { Queue { items: Vec::new() } }
pub fn enqueue(&mut self, item: T) { self.items.push(item); }
pub fn dequeue(&mut self) -> Option<T> { if self.items.is_empty() { None } else { Some(self.items.remove(0)) } } }
#[cfg(test)] mod tests { use super::*;
#[test] fn test_queue() { let mut q = Queue::new(); q.enqueue(1); q.enqueue(2); assert_eq!(q.dequeue(), Some(1)); assert_eq!(q.dequeue(), Some(2)); assert_eq!(q.dequeue(), None); } }}8. 测试输出与调试
Section titled “8. 测试输出与调试”# 显示测试中的打印输出cargo test -- --nocapture
# 显示所有测试输出(包括 pass 和 fail)cargo test -v
# 显示更详细的输出cargo test -vv
# 仅显示失败的测试cargo test -- --show-output
# 列出所有测试(不运行)cargo test -- --list今天我们学习了Rust测试框架:
- 单元测试:
#[cfg(test)]模块中的#[test]函数 - 常用断言:
assert!、assert_eq!、assert_ne! - #[should_panic]:测试预期 panic 的情况
- 集成测试:位于
tests/目录,测试模块协作 - 文档测试:
///注释中的示例代码自动测试 - 条件编译:
#[cfg(test)]确保测试代码仅在测试时编译 - 测试命令:
cargo test运行测试,--nocapture显示输出
测试是保证代码质量的关键。建议TDD(测试驱动开发)或至少保持较高的测试覆盖率。