Cargo 测试 #

测试概述 #

Cargo 内置了强大的测试框架,支持单元测试、集成测试和文档测试。本节将详细介绍如何使用 Cargo 编写和运行测试。

测试类型 #

text
测试类型
├── 单元测试
│   ├── 与代码放在同一文件
│   ├── 使用 #[test] 属性标记
│   └── 可以测试私有函数
├── 集成测试
│   ├── 放在 tests/ 目录
│   ├── 测试公共 API
│   └── 可以使用外部依赖
└── 文档测试
    ├── 在文档注释中的代码示例
    ├── 自动编译和运行
    └── 确保文档与代码同步

单元测试 #

基本语法 #

rust
// src/lib.rs 或 src/main.rs

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }

    #[test]
    fn another_test() {
        assert!(true);
    }
}

断言宏 #

assert! 宏 #

rust
#[test]
fn test_assert() {
    let value = true;
    assert!(value, "value should be true");
}

assert_eq! 和 assert_ne! 宏 #

rust
#[test]
fn test_assert_eq() {
    let result = 2 + 2;
    assert_eq!(result, 4, "2 + 2 should equal 4");
}

#[test]
fn test_assert_ne() {
    let result = 2 + 2;
    assert_ne!(result, 5, "2 + 2 should not equal 5");
}

自定义错误消息 #

rust
#[test]
fn test_with_message() {
    let a = 1;
    let b = 2;
    assert_eq!(
        a + b,
        3,
        "a ({}) + b ({}) should equal 3",
        a,
        b
    );
}

测试 Result #

rust
#[test]
fn test_result() -> Result<(), String> {
    let result = some_function()?;
    assert!(result.is_ok());
    Ok(())
}

fn some_function() -> Result<bool, String> {
    Ok(true)
}

测试 panic #

rust
#[test]
#[should_panic]
fn test_panic() {
    panic!("This should panic");
}

#[test]
#[should_panic(expected = "division by zero")]
fn test_panic_with_message() {
    divide(1, 0);
}

fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("division by zero");
    }
    a / b
}

忽略测试 #

rust
#[test]
#[ignore]
fn expensive_test() {
    // 耗时较长的测试
}

测试私有函数 #

rust
// src/lib.rs
fn internal_function(x: i32) -> i32 {
    x * 2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_internal() {
        assert_eq!(internal_function(5), 10);
    }
}

集成测试 #

基本结构 #

text
my_project/
├── Cargo.toml
├── src/
│   └── lib.rs
└── tests/
    ├── integration_test.rs
    └── common/
        └── mod.rs

创建集成测试 #

rust
// tests/integration_test.rs
use my_lib;

#[test]
fn test_add() {
    assert_eq!(my_lib::add(2, 3), 5);
}

#[test]
fn test_public_api() {
    // 测试公共 API
}

共享模块 #

rust
// tests/common/mod.rs
pub fn setup() {
    // 测试前设置
}

pub fn teardown() {
    // 测试后清理
}
rust
// tests/integration_test.rs
mod common;

#[test]
fn test_with_setup() {
    common::setup();
    // 测试代码
}

多个测试文件 #

text
tests/
├── api_test.rs       # API 测试
├── db_test.rs        # 数据库测试
├── integration.rs    # 集成测试
└── common/
    └── mod.rs        # 共享模块

文档测试 #

基本语法 #

rust
/// Adds two numbers.
///
/// # Examples
///
/// ```
/// use my_lib::add;
///
/// let result = add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

忽略文档测试 #

rust
/// ```ignore
/// use my_lib::some_function;
///
/// some_function(); // 这个测试会被忽略
/// ```
pub fn some_function() {}

编译失败测试 #

rust
/// ```compile_fail
/// use my_lib::must_fail;
///
/// must_fail(); // 这个应该编译失败
/// ```
pub fn must_fail() -> ! {
    unimplemented!()
}

无运行测试 #

rust
/// ```no_run
/// use my_lib::server;
///
/// server::run(); // 编译但不运行
/// ```
pub fn run() {}

添加依赖 #

rust
/// ```rust
/// use my_lib::add;
/// use serde_json::json;
///
/// let result = add(1, 2);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

测试命令 #

基本命令 #

bash
# 运行所有测试
cargo test

# 运行特定测试
cargo test test_name

# 运行匹配模式的测试
cargo test test_prefix_

# 运行特定模块的测试
cargo test tests::module_name

测试类型选择 #

bash
# 只运行单元测试
cargo test --lib

# 只运行集成测试
cargo test --test integration_test

# 只运行文档测试
cargo test --doc

# 运行所有测试
cargo test --all-targets

并行控制 #

bash
# 并行运行(默认)
cargo test

# 指定线程数
cargo test -- --test-threads=4

# 串行运行
cargo test -- --test-threads=1

输出控制 #

bash
# 显示测试输出
cargo test -- --nocapture

# 显示成功测试的输出
cargo test -- --show-output

# 静默模式
cargo test -- --quiet

忽略测试 #

bash
# 运行被忽略的测试
cargo test -- --ignored

# 运行所有测试(包括被忽略的)
cargo test -- --include-ignored

发布模式测试 #

bash
# 发布模式测试
cargo test --release

工作区测试 #

bash
# 测试所有工作区成员
cargo test --workspace

# 测试特定包
cargo test -p my_lib

测试组织 #

测试目录结构 #

text
my_project/
├── Cargo.toml
├── src/
│   ├── lib.rs
│   ├── main.rs
│   └── module/
│       └── mod.rs
└── tests/
    ├── integration_test.rs
    ├── api/
    │   └── user_test.rs
    └── common/
        └── mod.rs

测试模块组织 #

rust
// src/lib.rs
#[cfg(test)]
mod tests {
    mod unit_tests {
        use super::*;

        #[test]
        fn test_something() {}
    }

    mod integration_tests {
        use super::*;

        #[test]
        fn test_integration() {}
    }
}

测试辅助函数 #

rust
#[cfg(test)]
mod test_utils {
    pub fn create_test_data() -> TestData {
        TestData::default()
    }

    pub fn assert_approx_eq(a: f64, b: f64, epsilon: f64) {
        assert!(
            (a - b).abs() < epsilon,
            "{} is not approximately equal to {}",
            a,
            b
        );
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use super::test_utils::*;

    #[test]
    fn test_with_helper() {
        let data = create_test_data();
        assert_approx_eq(data.value, 1.0, 0.001);
    }
}

测试夹具 #

setup 和 teardown #

rust
#[cfg(test)]
mod tests {
    use super::*;

    struct TestContext {
        // 测试上下文
    }

    impl TestContext {
        fn new() -> Self {
            // setup
            TestContext {}
        }
    }

    impl Drop for TestContext {
        fn drop(&mut self) {
            // teardown
        }
    }

    #[test]
    fn test_with_context() {
        let _ctx = TestContext::new();
        // 测试代码
    }
}

使用 tempfile #

toml
[dev-dependencies]
tempfile = "3.0"
rust
#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    #[test]
    fn test_with_temp_dir() {
        let temp_dir = TempDir::new().unwrap();
        let path = temp_dir.path();
        // 使用临时目录
    }
}

性能测试 #

基准测试 #

rust
// benches/my_bench.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);

配置 Cargo.toml #

toml
[dev-dependencies]
criterion = "0.5"

[[bench]]
name = "my_bench"
harness = false

运行基准测试 #

bash
cargo bench

测试覆盖率 #

使用 tarpaulin #

bash
# 安装
cargo install cargo-tarpaulin

# 运行
cargo tarpaulin

# 生成 HTML 报告
cargo tarpaulin --out Html

使用 llvm-cov #

bash
# 安装
cargo install cargo-llvm-cov

# 运行
cargo llvm-cov

# 生成 HTML 报告
cargo llvm-cov --html

测试最佳实践 #

1. 测试命名 #

rust
#[test]
fn test_add_returns_sum() {}  // ✅ 描述性命名

#[test]
fn test1() {}  // ❌ 不清晰

2. 一个测试一个断言 #

rust
#[test]
fn test_add_positive_numbers() {
    assert_eq!(add(1, 2), 3);
}

#[test]
fn test_add_negative_numbers() {
    assert_eq!(add(-1, -2), -3);
}

3. 使用测试辅助函数 #

rust
#[cfg(test)]
mod test_utils {
    pub fn create_user(name: &str) -> User {
        User::new(name, "test@example.com")
    }
}

4. 测试边界条件 #

rust
#[test]
fn test_empty_input() {}

#[test]
fn test_single_element() {}

#[test]
fn test_large_input() {}

#[test]
fn test_boundary_values() {}

5. 测试错误情况 #

rust
#[test]
fn test_error_handling() {
    let result = function_that_may_fail();
    assert!(result.is_err());
}

常见问题 #

1. 测试超时 #

bash
# 设置超时时间
cargo test -- --test-timeout 60

2. 测试顺序 #

bash
# 并行运行(默认)
cargo test

# 串行运行
cargo test -- --test-threads=1

3. 测试输出 #

bash
# 显示 println! 输出
cargo test -- --nocapture

4. 测试依赖 #

toml
[dev-dependencies]
tempfile = "3.0"
pretty_assertions = "1.0"
mockall = "0.12"

下一步 #

掌握测试后,继续学习 发布包 了解如何将包发布到 crates.io

最后更新:2026-03-28