测试策略
问题
Rust 的测试体系是怎样的?
答案
单元测试
// 在同一文件中,用 #[cfg(test)] 模块
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("除数不能为零".into())
} else {
Ok(a / b)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_divide_success() {
assert!((divide(10.0, 3.0).unwrap() - 3.333).abs() < 0.001);
}
#[test]
fn test_divide_by_zero() {
assert!(divide(10.0, 0.0).is_err());
}
#[test]
#[should_panic(expected = "溢出")]
fn test_overflow() {
let _ = (i32::MAX as i64 + 1) as i32; // 可能 panic
panic!("溢出");
}
}
集成测试
tests/api_test.rs
// tests/ 目录下的文件是独立的 crate
use my_lib::add;
#[test]
fn integration_test() {
assert_eq!(add(1, 1), 2);
}
文档测试
/// 计算两个数的最大公约数
///
/// # Examples
///
/// ```
/// use my_lib::gcd;
/// assert_eq!(gcd(12, 8), 4);
/// assert_eq!(gcd(7, 13), 1);
/// ```
pub fn gcd(mut a: u64, mut b: u64) -> u64 {
while b != 0 {
let t = b;
b = a % b;
a = t;
}
a
}
// cargo test 会自动运行文档中的代码示例
测试层级
| 层级 | 位置 | 作用 |
|---|---|---|
| 单元测试 | #[cfg(test)] mod tests | 测试私有函数 |
| 集成测试 | tests/ 目录 | 测试公共 API |
| 文档测试 | `/// ``` ... ```` | 保证文档示例正确 |
常见面试问题
Q1: 如何在 Rust 中做 Mock?
答案:
// 方式 1:trait + 手动 mock
#[cfg(test)]
struct MockDb;
#[cfg(test)]
impl Database for MockDb {
fn query(&self, _: &str) -> Vec<Row> { vec![] }
}
// 方式 2:mockall crate
use mockall::automock;
#[automock]
trait Database {
fn query(&self, sql: &str) -> Vec<Row>;
}
#[test]
fn test_with_mock() {
let mut mock = MockDatabase::new();
mock.expect_query()
.returning(|_| vec![]);
}
Q2: #[cfg(test)] 有什么作用?
答案:
#[cfg(test)] 标记的代码只在 cargo test 时编译,不会出现在发布构建中。这样测试辅助代码和 mock 不会增加产物大小。