Rustlings 是 Rust 官方推荐的交互式学习工具,通过修复代码错误的方式帮助开发者掌握 Rust 编程语言。这个速通系列的第27关聚焦测试模块,这正是 Rust 语言的核心竞争力之一。与其他语言不同,Rust 将测试作为语言的一等公民,内置了完善的测试框架和断言库。
我在实际项目中发现,很多从其他语言转来的开发者容易忽略 Rust 测试的几个独特优势:
///注释中的代码示例可以直接作为测试用例Rust 的测试函数需要满足三个基本条件:
#[test] 属性标注test_ 前缀(非强制)典型测试函数示例:
rust复制#[test]
fn test_addition() {
assert_eq!(2 + 2, 4);
}
注意:测试函数默认会并行运行,需要确保测试用例之间没有共享状态依赖
Rust 标准库提供了丰富的断言宏:
| 断言宏 | 作用描述 | 适用场景 |
|---|---|---|
assert! |
检查布尔表达式为真 | 通用条件验证 |
assert_eq! |
比较两个值相等 | 结果比对 |
assert_ne! |
比较两个值不相等 | 异常路径验证 |
assert_matches! |
模式匹配验证 | 复杂结构体检查 |
#[should_panic] |
验证代码应当panic | 错误处理测试 |
建议按功能模块组织测试文件:
code复制src/
lib.rs
network/
mod.rs
tests.rs # 对应模块的测试
通过属性实现测试分类:
rust复制#[test]
#[ignore = "需要网络连接"]
fn test_http_request() {
// 网络相关测试
}
执行时可选择运行特定类型测试:
bash复制cargo test -- --ignored # 只运行被忽略的测试
Rust 的可见性规则要求测试必须能访问被测函数。对于私有函数,有两种解决方案:
rust复制mod my_module {
fn private_func() -> u32 { 42 }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_private() {
assert_eq!(private_func(), 42);
}
}
}
pub(crate) 可见性修饰符虽然 Rust 官方移除了内置的 benchmark 功能,但可以通过第三方库实现:
toml复制[dev-dependencies]
criterion = "0.4"
rust复制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);
以开发一个栈结构为例:
rust复制#[test]
fn test_stack() {
let mut s = Stack::new();
assert!(s.is_empty());
s.push(1);
assert!(!s.is_empty());
assert_eq!(s.pop(), Some(1));
}
rust复制pub struct Stack<T> {
elements: Vec<T>,
}
impl<T> Stack<T> {
pub fn new() -> Self {
Stack { elements: Vec::new() }
}
pub fn push(&mut self, item: T) {
self.elements.push(item);
}
pub fn pop(&mut self) -> Option<T> {
self.elements.pop()
}
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
}
使用 proptest 库进行随机输入测试:
rust复制use proptest::prelude::*;
proptest! {
#[test]
fn test_addition_commutative(a: i32, b: i32) {
assert_eq!(a + b, b + a);
}
}
典型原因:
排查方法:
--nocapture 查看输出:bash复制cargo test -- --nocapture
rust复制#[test]
#[timeout(1000)] // 1秒超时
fn test_timeout() {
// 长时间操作
}
症状:单独运行测试通过,整体运行失败
解决方案:
TestFixture 模式:rust复制struct TestFixture {
temp_dir: PathBuf,
}
impl TestFixture {
fn new() -> Self {
let dir = /* 创建临时目录 */;
Self { temp_dir: dir }
}
}
impl Drop for TestFixture {
fn drop(&mut self) {
// 清理资源
}
}
安装运行:
bash复制cargo install cargo-tarpaulin
cargo tarpaulin --ignore-tests
match 和 if 分支cargo-mutants 检测测试有效性我在实际项目中总结的覆盖率提升技巧:
toml复制# .cargo/config.toml
[test]
jobs = 4 # 根据CPU核心数调整
bash复制# 只运行单元测试
cargo test --lib
# 只运行集成测试
cargo test --test "*"
推荐方案:
tempfile 创建临时文件fake-rs 生成测试数据rust复制use fake::{Dummy, Fake, Faker};
#[derive(Debug, Dummy)]
struct User {
#[dummy(faker = "1000..9999")]
id: usize,
name: String,
}
let user: User = Faker.fake();
使用 mockall 创建模拟对象:
rust复制use mockall::automock;
#[automock]
trait MyTrait {
fn foo(&self, x: u32) -> u32;
}
let mut mock = MockMyTrait::new();
mock.expect_foo()
.with(predicate::eq(42))
.times(1)
.returning(|x| x + 1);
使用 pact 进行服务间契约测试:
rust复制use pact_consumer::*;
#[test]
fn test_api_contract() {
let pact = PactBuilder::new("Consumer", "Provider")
.interaction("get user", "", |mut i| {
i.request.path("/user/42");
i.response
.header("Content-Type", "application/json")
.json_body(json!({"id": 42}));
i
})
.build();
}
推荐使用 Docker 测试容器:
rust复制use testcontainers::{clients, images};
let docker = clients::Cli::default();
let mysql = docker.run(
images::mysql::MysqlImage::default()
.with_database("test")
.with_password("test")
);
let db_url = format!(
"mysql://root:test@localhost:{}/test",
mysql.get_host_port(3306)
);
使用 dotenv 管理测试环境变量:
rust复制#[test]
fn test_with_env() {
dotenv::from_filename(".env.test").ok();
let db_url = std::env::var("DATABASE_URL")
.expect("DATABASE_URL must be set");
// 测试逻辑
}
yaml复制name: Rust CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: test
args: --all-features --workspace
yaml复制jobs:
test:
strategy:
matrix:
platform: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.platform }}
steps:
# 测试步骤
在 Rustlings 第27关的基础上,我通常会建议团队成员进一步探索测试套件的组织策略。比如将长时间运行的测试标记为#[ignore],在CI中专门运行这些测试;或者为性能关键模块编写基准测试,确保每次提交不会引入性能回退。测试代码的质量往往决定了项目的长期可维护性,值得投入与生产代码同等的时间进行设计和优化。