第19章:测试
第19章:测试
19.1 单元测试
基本测试
// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("除数不能为零".to_string())
} else {
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);
}
#[test]
fn test_divide_success() {
assert_eq!(divide(10.0, 2.0).unwrap(), 5.0);
}
#[test]
fn test_divide_by_zero() {
assert!(divide(10.0, 0.0).is_err());
}
}
# 运行测试
cargo test
# 运行特定测试
cargo test test_add
# 显示输出
cargo test -- --show-output
# 并行/串行
cargo test -- --test-threads=1
19.2 Assert 宏
| 宏 | 说明 | 示例 |
|---|
assert!(expr) | 断言为 true | assert!(x > 0) |
assert_eq!(a, b) | 断言相等 | assert_eq!(1 + 1, 2) |
assert_ne!(a, b) | 断言不等 | assert_ne!(x, 0) |
debug_assert!(expr) | 仅 debug 模式 | debug_assert!(bounds_check) |
assert_matches!(expr, pat) | 模式匹配 | nightly only |
#[cfg(test)]
mod tests {
#[test]
fn test_assertions() {
let x = 5;
assert!(x > 0, "x 应该大于 0, 实际为 {}", x);
let v = vec![1, 2, 3];
assert_eq!(v.len(), 3);
assert_ne!(v[0], 0);
let name = Some("Alice");
assert!(name.is_some());
}
#[test]
#[should_panic(expected = "除数不能为零")]
fn test_panic() {
let result = std::panic::catch_unwind(|| {
panic!("除数不能为零");
});
// 或者直接测试函数是否 panic
}
}
19.3 测试组织
测试模块结构
// src/lib.rs
pub fn is_even(n: i32) -> bool {
n % 2 == 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_even_numbers() {
assert!(is_even(2));
assert!(is_even(4));
assert!(is_even(0));
}
#[test]
fn test_odd_numbers() {
assert!(!is_even(1));
assert!(!is_even(3));
}
}
测试辅助函数
#[cfg(test)]
mod tests {
// 测试辅助函数(不加 #[test])
fn setup() -> Vec<i32> {
vec![1, 2, 3, 4, 5]
}
#[test]
fn test_sum() {
let v = setup();
assert_eq!(v.iter().sum::<i32>(), 15);
}
#[test]
fn test_max() {
let v = setup();
assert_eq!(v.iter().max(), Some(&5));
}
}
19.4 集成测试
目录结构
my_project/
├── Cargo.toml
├── src/
│ └── lib.rs
└── tests/ # 集成测试目录
├── integration_test.rs
└── api_test.rs
tests/integration_test.rs
// 集成测试是外部代码,需要导入
use my_project;
#[test]
fn test_public_api() {
let result = my_project::add(2, 3);
assert_eq!(result, 5);
}
tests/common/mod.rs
// tests/common/mod.rs
// 共享测试辅助代码(不会被当作独立测试文件)
pub fn setup() {
// 初始化代码
println!("测试环境初始化");
}
// tests/integration_test.rs
mod common;
#[test]
fn test_with_setup() {
common::setup();
// 测试逻辑...
}
19.5 测试属性
忽略测试
#[cfg(test)]
mod tests {
#[test]
#[ignore] // 默认跳过
fn expensive_test() {
// 耗时测试
}
#[test]
fn normal_test() {
assert!(true);
}
}
# 只运行被忽略的测试
cargo test -- --ignored
# 运行所有测试(包括被忽略的)
cargo test -- --include-ignored
条件测试
#[cfg(test)]
mod tests {
#[test]
#[cfg(target_os = "linux")]
fn linux_only_test() {
// 仅在 Linux 上运行
}
#[test]
#[cfg(feature = "integration")]
fn integration_test() {
// 需要启用 integration feature
}
}
19.6 测试最佳实践
参数化测试
#[cfg(test)]
mod tests {
#[test]
fn test_multiple_cases() {
let cases = vec![
(1, 1, 2),
(0, 0, 0),
(-1, 1, 0),
(100, 200, 300),
];
for (a, b, expected) in cases {
assert_eq!(
super::add(a, b),
expected,
"add({}, {}) 应该等于 {}", a, b, expected
);
}
}
}
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
测试 Result
#[cfg(test)]
mod tests {
#[test]
fn test_with_result() -> Result<(), String> {
let value = "42".parse::<i32>().map_err(|e| e.to_string())?;
assert_eq!(value, 42);
Ok(())
}
}
19.7 业务场景示例
完整测试示例
// src/lib.rs
pub struct Calculator {
history: Vec<String>,
}
impl Calculator {
pub fn new() -> Self {
Self { history: Vec::new() }
}
pub fn add(&mut self, a: f64, b: f64) -> f64 {
let result = a + b;
self.history.push(format!("{} + {} = {}", a, b, result));
result
}
pub fn divide(&mut self, a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
return Err("除数不能为零".to_string());
}
let result = a / b;
self.history.push(format!("{} / {} = {}", a, b, result));
Ok(result)
}
pub fn history(&self) -> &[String] {
&self.history
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
let mut calc = Calculator::new();
assert_eq!(calc.add(2.0, 3.0), 5.0);
}
#[test]
fn test_divide_success() {
let mut calc = Calculator::new();
assert_eq!(calc.divide(10.0, 2.0).unwrap(), 5.0);
}
#[test]
fn test_divide_by_zero() {
let mut calc = Calculator::new();
assert!(calc.divide(10.0, 0.0).is_err());
}
#[test]
fn test_history() {
let mut calc = Calculator::new();
calc.add(1.0, 2.0);
calc.add(3.0, 4.0);
assert_eq!(calc.history().len(), 2);
assert!(calc.history()[0].contains("3"));
}
}
19.8 本章小结
| 要点 | 说明 |
|---|
| #[test] | 标记测试函数 |
| assert! | 断言宏族 |
| #[should_panic] | 断言函数 panic |
| tests/ 目录 | 集成测试 |
| #[ignore] | 跳过测试 |
| cargo test | 运行所有测试 |
扩展阅读
- Rust Book - 测试 — 官方教程
- nextest — 更快的测试运行器