摘要:本文系统讲解 Rust 测试与质量保证体系,涵盖单元测试与集成测试、文档测试、Mock 与测试桩、性能测试(Benchmark)、模糊测试(Fuzzing)、CI/CD 集成等核心内容。每个知识点配有完整代码示例、对比表格、实战场景及常见问题解答,帮助开发者编写健壮的 Rust 代码。
关键词:Rust、测试、单元测试、集成测试、Mock、Benchmark、Fuzzing、CI/CD、质量保证
适合人群:已掌握 Rust 基础的开发者、想提高代码质量的程序员、想建立测试体系的团队
阅读时间:约 50 分钟
版本信息:Rust 1.70+ | 兼容 Windows/macOS/Linux
文章目录
- 一、单元测试与集成测试
-
- [1.1 单元测试](#1.1 单元测试)
- [1.2 断言宏](#1.2 断言宏)
- [1.3 集成测试](#1.3 集成测试)
- [1.4 测试组织](#1.4 测试组织)
- 二、文档测试
-
- [2.1 文档测试基础](#2.1 文档测试基础)
- [2.2 文档测试技巧](#2.2 文档测试技巧)
- [三、Mock 与测试桩](#三、Mock 与测试桩)
-
- [3.1 使用 mockall](#3.1 使用 mockall)
- [3.2 测试桩模式](#3.2 测试桩模式)
- [四、性能测试 Benchmark](#四、性能测试 Benchmark)
-
- [4.1 使用 criterion](#4.1 使用 criterion)
- [4.2 Benchmark 对比](#4.2 Benchmark 对比)
- [五、模糊测试 Fuzzing](#五、模糊测试 Fuzzing)
-
- [5.1 使用 cargo-fuzz](#5.1 使用 cargo-fuzz)
- [5.2 Fuzzing 适用场景](#5.2 Fuzzing 适用场景)
- [六、CI/CD 集成](#六、CI/CD 集成)
-
- [6.1 GitHub Actions](#6.1 GitHub Actions)
- [6.2 CI 检查项](#6.2 CI 检查项)
- [6.3 测试覆盖率](#6.3 测试覆盖率)
- [💡 综合实战案例](#💡 综合实战案例)
- [❓ 常见问题 FAQ](#❓ 常见问题 FAQ)
- [📝 学习资源与建议](#📝 学习资源与建议)
- [📚 参考资料](#📚 参考资料)
一、单元测试与集成测试
1.1 单元测试
Rust 内置测试框架,使用 #[test] 属性标记测试函数。
rust
// src/lib.rs
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);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-1, 1), 0);
}
}
测试运行命令:
bash
cargo test # 运行所有测试
cargo test test_add # 运行指定测试
cargo test -- --nocapture # 显示输出
1.2 断言宏
断言宏对比:
| 宏 | 说明 | 示例 |
|---|---|---|
assert! |
条件为真 | assert!(x > 0) |
assert_eq! |
相等 | assert_eq!(a, b) |
assert_ne! |
不相等 | assert_ne!(a, b) |
panic! |
故意 panic | panic!("expected error") |
1.3 集成测试
集成测试放在 tests/ 目录下,测试公共 API。
rust
// tests/integration_test.rs
use my_crate::add;
#[test]
fn test_add_integration() {
assert_eq!(add(10, 20), 30);
}
测试类型对比:
| 类型 | 位置 | 测试范围 | 访问权限 |
|---|---|---|---|
| 单元测试 | src/ 内 |
内部实现 | 可访问私有项 |
| 集成测试 | tests/ 目录 |
公共 API | 仅公共项 |
| 文档测试 | 文档注释中 | 示例代码 | 公共项 |
1.4 测试组织
rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
#[test]
#[should_panic]
fn it_panics() {
panic!("This should panic");
}
#[test]
#[ignore]
fn expensive_test() {
// 耗时测试,默认跳过
}
}
测试属性对比:
| 属性 | 说明 | 示例 |
|---|---|---|
#[test] |
标记测试函数 | #[test] |
#[should_panic] |
期望 panic | #[should_panic] |
#[ignore] |
跳过测试 | #[ignore] |
#[serial] |
串行执行 | #[serial] |
二、文档测试
2.1 文档测试基础
Rust 允许在文档注释中编写可测试的代码示例。
rust
/// Adds two numbers.
///
/// # Examples
///
/// ```
/// use my_crate::add;
///
/// assert_eq!(add(2, 3), 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
文档测试运行:
bash
cargo test --doc # 仅运行文档测试
cargo test # 包含文档测试
2.2 文档测试技巧
rust
/// # 隐藏设置代码
///
/// ```
/// # use my_crate::Config;
/// let config = Config::new();
/// assert!(config.is_valid());
/// ```
pub struct Config {
// ...
}
文档测试说明:
| 技巧 | 说明 | 示例 |
|---|---|---|
# 前缀 |
隐藏代码行 | # use my_crate::Foo; |
should_panic |
期望 panic | /// ```should_panic |
ignore |
跳过测试 | /// ```ignore |
no_run |
编译但不运行 | /// ```no_run |
三、Mock 与测试桩
3.1 使用 mockall
mockall 是 Rust 流行的 Mock 框架。
Cargo.toml 依赖:
toml
[dev-dependencies]
mockall = "0.11"
rust
use mockall::automock;
#[automock]
trait Database {
fn get_user(&self, id: u32) -> Option<String>;
fn save_user(&self, id: u32, name: &str) -> bool;
}
struct Service {
db: Box<dyn Database>,
}
impl Service {
fn new(db: Box<dyn Database>) -> Self {
Self { db }
}
fn get_user_name(&self, id: u32) -> Option<String> {
self.db.get_user(id)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_user_name() {
let mut mock_db = MockDatabase::new();
mock_db.expect_get_user()
.with(mockall::predicate::eq(1))
.returning(|_| Some("Alice".to_string()));
let service = Service::new(Box::new(mock_db));
assert_eq!(service.get_user_name(1), Some("Alice".to_string()));
}
}
3.2 测试桩模式
rust
// 定义 trait
pub trait HttpClient {
fn get(&self, url: &str) -> Result<String, String>;
}
// 生产实现
pub struct RealClient;
impl HttpClient for RealClient {
fn get(&self, url: &str) -> Result<String, String> {
// 实际 HTTP 请求
unimplemented!()
}
}
// 测试桩
pub struct StubClient;
impl HttpClient for StubClient {
fn get(&self, url: &str) -> Result<String, String> {
Ok("stubbed response".to_string())
}
}
Mock 与 Stub 对比:
| 特性 | Mock | Stub |
|---|---|---|
| 行为验证 | 验证调用次数、参数 | 仅返回固定值 |
| 复杂度 | 较高 | 较低 |
| 适用场景 | 复杂交互 | 简单依赖 |
| 维护成本 | 较高 | 较低 |
四、性能测试 Benchmark
4.1 使用 criterion
criterion 是 Rust 标准的 Benchmark 框架。
Cargo.toml 配置:
toml
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]
name = "my_benchmark"
harness = false
rust
// benches/my_benchmark.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn fibonacci(n: u64) -> u64 {
match n {
0 => 1,
1 => 1,
n => fibonacci(n - 1) + fibonacci(n - 2),
}
}
fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
运行 Benchmark:
bash
cargo bench # 运行所有 benchmark
cargo bench -- --save-baseline # 保存基线
4.2 Benchmark 对比
| 框架 | 说明 | 特点 |
|---|---|---|
| criterion | 统计基准 | HTML 报告、统计分析 |
内置 #[bench] |
简单基准 | 需要 nightly |
| iai | 指令计数 | 不受系统负载影响 |
五、模糊测试 Fuzzing
5.1 使用 cargo-fuzz
cargo-fuzz 用于发现边界条件 bug。
安装:
bash
cargo install cargo-fuzz
初始化:
bash
cargo fuzz init
rust
// fuzz/fuzz_targets/fuzz_target_1.rs
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
if data.len() > 0 {
my_crate::parse(data);
}
});
运行 Fuzzing:
bash
cargo fuzz run fuzz_target_1
5.2 Fuzzing 适用场景
| 场景 | 说明 | 示例 |
|---|---|---|
| 解析器 | 解析未知输入 | JSON、XML 解析 |
| 序列化 | 数据格式转换 | serde 序列化 |
| 网络协议 | 处理网络数据 | HTTP 解析 |
| 加密 | 边界条件 | 加密算法 |
六、CI/CD 集成
6.1 GitHub Actions
yaml
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- run: cargo test
- run: cargo clippy
- run: cargo fmt --check
6.2 CI 检查项
| 检查项 | 命令 | 说明 |
|---|---|---|
| 测试 | cargo test |
运行所有测试 |
| Clippy | cargo clippy |
代码检查 |
| 格式化 | cargo fmt --check |
格式检查 |
| 文档 | cargo doc |
文档生成 |
| 安全审计 | cargo audit |
依赖安全 |
| 覆盖率 | cargo tarpaulin |
测试覆盖率 |
6.3 测试覆盖率
bash
# 安装
cargo install cargo-tarpaulin
# 运行
cargo tarpaulin --out Html
💡 综合实战案例
实战:完整测试体系
rust
// src/lib.rs
pub struct Calculator;
impl Calculator {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("Division by zero".to_string())
} else {
Ok(a / b)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(Calculator::add(2, 3), 5);
}
#[test]
fn test_divide() {
assert_eq!(Calculator::divide(10, 2), Ok(5));
}
#[test]
fn test_divide_by_zero() {
assert!(Calculator::divide(10, 0).is_err());
}
}
rust
// tests/integration_test.rs
use my_crate::Calculator;
#[test]
fn test_calculator_integration() {
let result = Calculator::add(
Calculator::add(1, 2),
Calculator::add(3, 4)
);
assert_eq!(result, 10);
}
项目知识点:
- 单元测试(
#[test]) - 集成测试(
tests/目录) - 错误处理测试
- 断言宏使用
❓ 常见问题 FAQ
Q1:单元测试和集成测试有什么区别?
A:主要区别:
- 单元测试在
src/内,可访问私有项 - 集成测试在
tests/目录,仅测试公共 API - 单元测试测试内部实现,集成测试测试外部接口
Q2:什么时候使用 Mock?
A:以下场景推荐使用 Mock:
- 依赖外部服务(数据库、HTTP)
- 需要验证调用次数和参数
- 测试复杂交互逻辑
- 隔离测试环境
Q3:如何提高测试覆盖率?
A:提高覆盖率技巧:
- 使用
cargo tarpaulin检查覆盖率 - 编写边界条件测试
- 测试错误路径
- 使用 Fuzzing 发现遗漏
Q4:Benchmark 和测试有什么区别?
A:区别:
- 测试验证正确性,Benchmark 测量性能
- 测试使用
cargo test,Benchmark 使用cargo bench - 测试关注功能,Benchmark 关注速度
Q5:如何在 CI 中运行测试?
A:使用 GitHub Actions:
- 创建
.github/workflows/ci.yml - 配置
cargo test、cargo clippy、cargo fmt - 推送到 GitHub 自动运行
📝 学习资源与建议
学习建议
1. 测试驱动开发 :先写测试,再写实现
2. 覆盖边界条件 :测试正常和异常路径
3. 使用 Mock 隔离 :避免依赖外部服务
4. 定期 Benchmark :监控性能变化
5. CI/CD 集成:自动化测试流程