测试
这篇讲 Rust 的测试体系——单元测试、集成测试和文档测试。Rust 把测试作为语言的一等公民内置,无需第三方测试框架。
单元测试
用 #[test] 属性标记测试函数,通常放在与被测试代码同一个文件中:
// src/lib.rs
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)] // 只在 cargo test 时编译
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 2), 4);
}
#[test]
fn test_add_zero() {
assert_eq!(add(0, 5), 5);
}
}运行测试:
cargo test # 运行所有测试
cargo test test_add # 按名称过滤
cargo test -- --nocapture # 显示测试中的 println! 输出断言宏
#[test]
fn assertion_demo() {
assert!(true); // 布尔值为 true
assert_eq!(2 + 2, 4); // 相等(需要 PartialEq + Debug)
assert_ne!(2 + 2, 5); // 不等
assert_eq!(
vec![1, 2, 3],
vec![1, 2, 3]
);
}
#[test]
#[should_panic(expected = "除数不能为零")] // 期望 panic
fn test_divide_by_zero() {
divide(10, 0);
}
fn divide(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("除数不能为零");
}
a / b
}assert_eq! 和 assert_ne! 在失败时会打印两个值——它们依赖 Debug trait。所以被比较的值必须实现了 Debug(基本类型和大多数标准类型默认已实现)。使用 Result 的测试
测试函数也可以返回 Result,这样可以使用 ? 运算符:
#[cfg(test)]
mod tests {
#[test]
fn it_works() -> Result<(), String> {
let result = 2 + 2;
if result == 4 {
Ok(())
} else {
Err(String::from("2+2 不等于 4"))
}
}
}集成测试
集成测试放在项目根目录的 tests/ 下,每个文件作为一个独立的 crate,只能测试公开 API:
my-project/
src/
lib.rs
tests/
integration_test.rs
common/
mod.rs # 共享的测试辅助模块// tests/integration_test.rs
use my_crate; // 把我们的库当作外部 crate 引入
#[test]
fn test_public_api() {
let result = my_crate::add(2, 3);
assert_eq!(result, 5);
}只运行集成测试:
cargo test --test integration_test文档测试
Rust 的文档注释 /// 中的代码块在 cargo test 时也会被执行——这确保文档永远不会过时:
/// 将两个数字相加。
///
/// # 示例
///
/// ```
/// let result = my_crate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(left: usize, right: usize) -> usize {
left + right
}文档测试是 Rust 最被低估的特性之一。它强制文档中的代码示例保持正确——没有「Example 编译都通不过」的过时文档。这与 Go 的 Example 测试有相似之处。
测试组织最佳实践
// 测试私有函数:放在同一文件内的 tests 模块
#[cfg(test)]
mod tests {
use super::*; // 引入父模块的所有内容(包括私有函数)
#[test]
fn test_private_helper() {
assert_eq!(private_helper(5), 10);
}
}
fn private_helper(x: i32) -> i32 {
x * 2
}一句话小结
单元测试用 #[test] + assert! 系列宏,放在源码文件的 #[cfg(test)] mod tests {} 中。集成测试在 tests/ 目录。文档测试用 /// 注释,让代码示例即是测试。cargo test 一键运行所有测试。
Rust 基础系列到此完结。掌握了这 11 篇内容,你已经覆盖了 Rust 核心语言的大部分知识点。接下来可以深入 Web 框架 或 并发与异步 等具体领域。
练习
- 为以下函数编写单元测试(正常情况和 panic 情况都要覆盖):
fn safe_reciprocal(x: f64) -> Result<f64, String> {
if x == 0.0 {
Err(String::from("不能计算零的倒数"))
} else {
Ok(1.0 / x)
}
}- 给
safe_reciprocal添加文档注释,并包含一个可执行的文档测试。
参考答案
/// 计算一个数的倒数。
///
/// # 示例
///
/// ```
/// let result = safe_reciprocal(2.0);
/// assert_eq!(result, Ok(0.5));
/// ```
///
/// # 错误
///
/// 当输入为 0.0 时返回错误。
///
/// ```
/// let result = safe_reciprocal(0.0);
/// assert!(result.is_err());
/// ```
fn safe_reciprocal(x: f64) -> Result<f64, String> {
if x == 0.0 {
Err(String::from("不能计算零的倒数"))
} else {
Ok(1.0 / x)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normal_case() {
let result = safe_reciprocal(2.0);
assert_eq!(result.unwrap(), 0.5);
}
#[test]
fn test_large_number() {
let result = safe_reciprocal(100.0);
assert!((result.unwrap() - 0.01).abs() < 0.001);
}
#[test]
#[should_panic(expected = "不能计算零的倒数")]
fn test_zero_panics() {
safe_reciprocal(0.0).unwrap();
}
}最后更新于