Skip to content

Day 20: 单元测试与文档测试

Rust内置测试框架,支持三种测试:

  • 单元测试:测试单个函数或模块
  • 集成测试:测试多个模块的协作
  • 文档测试:测试代码示例的正确性

在源文件中使用 #[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(())
}
}
#[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
);
}
}

集成测试位于 tests/ 目录下,用于测试多个模块的协作:

tests/integration_test.rs
// 注意:集成测试会编译为独立的 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.rs
pub fn setup() -> Vec<i32> {
vec![1, 2, 3]
}
pub fn teardown() {
// 清理资源
}
Terminal window
# 运行测试
cargo test # 运行所有测试
cargo test test_add # 运行指定名称的测试
cargo test --test integration # 运行集成测试
cargo test --lib # 仅运行库测试(单元测试)
cargo test -- --nocapture # 显示 println! 输出

使用 /// 文档注释编写测试,直接验证示例代码:

/// 将两个数字相加
///
/// # 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)
}
}
Terminal window
# 运行文档测试
cargo test --doc
# 仅运行特定函数的文档测试
cargo test --doc -- factorial

#[cfg(test)] 确保测试代码仅在测试时编译:

src/math.rs
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() {
// 启动模拟服务器
}
}

建议的测试组织方式:

src/lib.rs
// 模块:核心业务逻辑
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);
}
}
}
Terminal window
# 显示测试中的打印输出
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(测试驱动开发)或至少保持较高的测试覆盖率。