> 技术文档 > Rust 单元测试详细教程

Rust 单元测试详细教程


Rust 单元测试详细教程

  • 一、Rust 单元测试详细教程
    • 1、基本概念
    • 2、核心组件
    • 3、 高级技巧
    • 4、测试运行控制
    • 5、最佳实践
    • 6、添加自定义消息
      • 1、 `assert!` 宏添加消息
      • 2、`assert_eq!` 宏添加消息
      • 3、使用 `format!` 构建复杂消息
      • 4、最佳实践
      • 5、错误消息原理
    • 7、常见问题解决
  • 二、代码示例
    • 1、正常通过
    • 2、异常代码

一、Rust 单元测试详细教程

1、基本概念

在 Rust 中,单元测试直接写在源代码文件中,通常位于 #[cfg(test)] 标记的模块内。测试函数需用 #[test] 属性标记。

// 被测试函数pub fn add(a: i32, b: i32) -> i32 { a + b}// 测试模块#[cfg(test)]mod tests { use super::*; // 引入外部函数 #[test] fn test_add() { assert_eq!(add(2, 3), 5); // 断言测试 }}

2、核心组件

(1) 断言宏

  • assert!(expr):表达式为真则通过
  • assert_eq!(left, right):验证相等
  • assert_ne!(left, right):验证不等
  • #[should_panic]:测试预期 panic
#[test]#[should_panic(expected = \"除数不能为零\")]fn test_divide_by_zero() { divide(10, 0); // 预期触发 panic}

(2) 测试模块组织

#[cfg(test)]mod tests { // 测试私有函数 #[test] fn test_private() { assert_eq!(internal_logic(), 42); } // 子测试模块 mod edge_cases { #[test] fn test_negative() { assert_eq!(add(-1, -1), -2); } }}

3、 高级技巧

(1) 测试初始化

#[cfg(test)]mod tests { // 共享初始化代码 fn setup() -> String { String::from(\"test_data\") } #[test] fn test_data_processing() { let data = setup(); assert!(!data.is_empty()); }}

(2) 忽略测试

#[test]#[ignore = \"性能测试暂不运行\"]fn heavy_computation_test() { // 耗时操作}

(3) 条件编译

#[test]#[cfg(feature = \"network\")]fn network_api_test() { // 仅当启用 network 特性时编译}

4、测试运行控制

(1) 运行特定测试

cargo test test_add # 运行单个测试cargo test add # 运行名称包含\"add\"的测试

(2) 并行控制

cargo test -- --test-threads=1 # 单线程运行

(3) 显示输出

cargo test -- --nocapture # 显示 println! 输出

5、最佳实践

  1. 测试覆盖率:使用 tarpaulin 工具

    cargo tarpaulin --ignore-tests
  2. 测试私有函数:直接测试模块内部实现

  3. BDD 风格:使用 speculoos

    use speculoos::*;#[test]fn test_list() { let list = vec![1, 2, 3]; assert_that(&list).has_length(3);}
  4. Mock 对象:使用 mockall

    #[automock]trait DataSource { fn fetch(&self) -> u32;}#[test]fn test_mock() { let mut mock = MockDataSource::new(); mock.expect_fetch().returning(|| 42); assert_eq!(mock.fetch(), 42);}

6、添加自定义消息

在 Rust 测试中添加自定义消息可通过断言宏的额外参数实现,以下是具体方法和示例:

1、 assert! 宏添加消息

#[test]fn test_basic() { let result = 2 + 2; assert!(result == 4, \"加法计算错误,得到 {} 但期望 4\", result);}

当断言失败时输出:

thread \'test_basic\' panicked at \'加法计算错误,得到 5 但期望 4\'

2、assert_eq! 宏添加消息

#[test]fn test_equality() { let actual = \"hello\".to_string(); let expected = \"world\"; assert_eq!(actual, expected, \"字符串不匹配: 实际=\'{}\', 期望=\'{}\'\", actual, expected);}

失败时输出:

thread \'test_equality\' panicked at \'字符串不匹配: 实际=\'hello\', 期望=\'world\'\'

3、使用 format! 构建复杂消息

#[test]fn test_complex() { let vec = vec![1, 2, 3]; assert!( vec.len() > 5, \"向量长度不足: 长度={}, 内容={:?}\", vec.len(), vec );}

4、最佳实践

  1. 清晰说明:明确说明期望值和实际值
    assert!(user.is_admin(), \"用户 {} 无管理员权限\", user.id)
  2. 包含关键数据:在消息中输出相关变量值
  3. 避免敏感信息:不要在消息中包含密码等敏感数据
  4. 使用格式规范
    assert_eq!(result, 3.14159, \"精度不足: {:.5} vs {:.5}\", result, 3.14159)

5、错误消息原理

Rust 的断言宏本质上是:

macro_rules! assert { ($cond:expr, $($msg:tt)*) => { if !$cond { panic!($($msg)*) } }}

自定义消息通过 panic! 宏的格式化能力实现,支持所有标准格式语法。

7、常见问题解决

  1. 测试未编译:检查 #[cfg(test)] 是否遗漏
  2. 无法访问私有项:使用 use super::*; 引入父模块
  3. I/O 测试失败:使用 tempfile 库处理临时文件
  4. 异步测试:使用 #[tokio::test] 属性

二、代码示例

1、正常通过

测试源码:

pub struct Counter { value: i32,}impl Counter { pub fn new() -> Self { Counter { value: 0 } } pub fn increment(&mut self) { self.value += 1; } pub fn get(&self) -> i32 { self.value } pub fn add(&mut self, n: i32) { self.value += n; }}#[cfg(test)]mod tests { use super::*; #[test] fn test_counter_initial() { let c = Counter::new(); assert_eq!(c.get(), 0); } #[test] fn test_counter_increment() { let mut c = Counter::new(); c.increment(); assert_eq!(c.get(), 1); } #[test] fn test_counter_add() { let mut c = Counter::new(); c.add(5); assert_eq!(c.get(), 5,\"test add failed! please check your code\"); } #[test] #[should_panic] fn test_overflow() { let mut c = Counter::new(); for _ in 0..(i32::MAX as u32 + 1) { c.increment(); //  } }}

测试结果:

PS G:\\Learning\\Rust\\unitTest> cargo test Compiling unitTest v0.1.0 (G:\\Learning\\Rust\\unitTest) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.31s Running unittests src\\main.rs (target\\debug\\deps\\unitTest-cb66f5a5bbe86c70.exe)running 4 teststest tests::test_counter_add ... oktest tests::test_counter_increment ... oktest tests::test_counter_initial ... oktest tests::test_overflow - should panic ... oktest result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.99sPS G:\\Learning\\Rust\\unitTest> 

Rust 单元测试详细教程

2、异常代码

测试代码:

pub struct Counter { value: i32,}impl Counter { pub fn new() -> Self { Counter { value: 0 } } pub fn increment(&mut self) { self.value += 1; } pub fn get(&self) -> i32 { self.value } pub fn add(&mut self, n: i32) { self.value += n; }}#[cfg(test)]mod tests { use super::*; #[test] fn test_counter_initial() { let c = Counter::new(); assert_eq!(c.get(), 0); } #[test] fn test_counter_increment() { let mut c = Counter::new(); c.increment(); assert_eq!(c.get(), 1); } #[test] fn test_counter_add() { let mut c = Counter::new(); c.add(5); assert_eq!(c.get(), 6,\"test add failed! please check your code\"); } #[test] #[should_panic] fn test_overflow() { let mut c = Counter::new(); for _ in 0..(i32::MAX as u32 + 1) { c.increment(); //  } }}

测试结果:

PS G:\\Learning\\Rust\\unitTest> cargo test Compiling unitTest v0.1.0 (G:\\Learning\\Rust\\unitTest) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.31s Running unittests src\\main.rs (target\\debug\\deps\\unitTest-cb66f5a5bbe86c70.exe)running 4 teststest tests::test_counter_add ... FAILEDtest tests::test_counter_increment ... oktest tests::test_counter_initial ... oktest tests::test_overflow - should panic ... okfailures:---- tests::test_counter_add stdout ----thread \'tests::test_counter_add\' panicked at src\\main.rs:42:9:assertion `left == right` failed: test add failed! please check your code left: 5 right: 6note: run with `RUST_BACKTRACE=1` environment variable to display a backtracefailures: tests::test_counter_addtest result: FAILED. 3 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.83serror: test failed, to rerun pass `--bin unitTest`PS G:\\Learning\\Rust\\unitTest> 

Rust 单元测试详细教程

在这里插入图片描述