FORMA

第八章:测试、文档与质量

Rust 将测试和文档视为语言的一等公民。通过内置的测试框架,你可以在项目里编写单元测试、集成测试以及随文档一起运行的示例测试,三者共用 cargo test 命令。配合 cargo doc 生成文档,形成了一套确保代码质量与可维护性的完整工作流。

8.1 单元测试

单元测试检验代码的局部功能,通常与被测试代码放在同一文件中,用条件编译隔离。

#[cfg(test)] 模块与 #[test] 属性

单元测试通常放在源码文件底部、一个标有 #[cfg(test)] 的模块里。这保证测试代码只在 cargo test 时编译,发布时完全排除。

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

#[test] 属性标记测试函数。Cargo 会自动发现并运行所有带此属性的函数。

断言

Rust 提供多种断言宏来验证程序状态:

  • assert!(condition):断言条件为 true,否则 panic。
  • assert_eq!(left, right):断言左右值相等(要求实现 PartialEqDebug,以便打印差异)。
  • assert_ne!(left, right):断言两个值不相等。
rust
#[test]
fn test_add() {
    assert_eq!(add(2, 3), 5);
    assert!(add(1, -1) == 0);
}

断言失败时会自动打印出文件和行号,以及具体数值(对于 _eq 变体)。

期望 panic:#[should_panic]

当需要验证某段代码确实按预期 panic 时,使用 #[should_panic] 属性。还可以添加 expected 参数,限定 panic 消息必须包含指定文本,避免因意外的 panic 而误判通过。

rust
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_out_of_bounds() {
    let v = vec![1, 2, 3];
    let _ = v[99];
}

测试私有函数

因为 #[cfg(test)] 模块可以访问其父模块(通过 super::),所以能直接测试私有函数,无需破坏封装向外暴露接口。

rust
fn private_add(a: i32, b: i32) -> i32 {
    a + b
}

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

    #[test]
    fn test_private_add() {
        assert_eq!(private_add(2, 3), 5);
    }
}

这是 Rust 提倡的“内部测试”风格:私有逻辑也可以充分覆盖。

控制测试执行

cargo test 支持多种选项:

  • cargo test test_name 只运行名称中包含 test_name 的测试。
  • cargo test -- --test-threads=1 单线程运行,避免并行干扰。
  • cargo test -- --nocapture 显示被测试函数里的 println! 输出。
  • cargo test -- --ignored 只运行被 #[ignore] 标记的测试。

8.2 集成测试

集成测试模拟外部使用者的场景,只测试公开 API。它们在项目根目录的 tests/ 目录中组织,每个 .rs 文件被编译为一个独立的 crate。

目录结构

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

每个测试文件都像是一个全新 crate,需要通过外部 crate 的方式引入被测库:

rust
// tests/integration_test.rs
use my_project;

#[test]
fn test_public_api() {
    assert!(my_project::some_function(42));
}

cargo test 会运行单元测试、集成测试和文档测试,并分别报告三部分的结果。

只能测试公有 API

集成测试文件独立于库 crate,不能直接访问私有模块或私有函数。这强迫开发者验证公开接口是否稳健可用。

公用工具函数

如果多个集成测试需要共享辅助函数,可以建立 tests/common/mod.rs。Cargo 把 tests 目录下每个文件当作独立测试 crate,但 common 本身不是测试文件——只有作为模块被其他测试引用时,它才被编译。

text
tests/
├── common/
│   └── mod.rs
├── integration_test.rs

integration_test.rs 中引用:

rust
mod common;

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

这样 common 不会作为测试单独运行,同时可以做好配置管理。

8.3 文档测试

Rust 的文档注释 /// 不仅能生成 HTML,其中的代码块还会被 cargo test 当作测试执行,确保示例代码长期有效、准确。

基本用法

在文档注释中写入 ``` 代码块,它们默认会被编译并运行:

rust
/// 将两个数相加。
///
/// ```
/// let result = my_crate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

运行 cargo test 时,会提取这些代码编译成独立测试并执行,断言失败则测试失败。

隐藏代码行

有时需要展示部分前置代码,但不希望它出现在文档中,可在行首添加 #

rust
/// ```
/// # use my_crate::prelude::*;
/// # let conn = establish_connection();
/// let users = conn.query("SELECT * FROM users")?;
/// ```

前置的 # 行在文档渲染时被隐藏,但测试时仍会执行,保证代码完整性。

控制代码块行为

你可以通过属性控制某个代码块是否被编译或运行:

  • ```ignore:不编译该代码块(适用于无法通过编译的示例,但不推荐滥用)。
  • ```no_run:编译但不运行(示例需要外部环境或会耗时)。
  • ```should_panic:预期运行时 panic,与 #[should_panic] 类似。
  • ```compile_fail:预期编译失败(用于演示类型错误等)。
rust
/// ```compile_fail
/// let x: i32 = "hello"; // 编译错误演示
/// ```

这些标记让文档示例能够灵活演示正确与错误用法。

8.4 文档生成

Rust 使用 rustdoc 工具从文档注释生成 HTML 网页。运行 cargo doc 会为当前 crate 及所有依赖生成离线文档,放置在 target/doc 目录。

文档注释语法

  • ///:条目级注释,用于函数、结构体、模块等(Markdown 格式)。
  • //!:模块级注释,用于文件或模块的整体说明,写在模块最顶部。
rust
//! # My Crate
//!
//! 提供高性能数学计算功能。

/// 计算阶乘。
///
/// # Examples
///
/// ```
/// let n = my_crate::factorial(5);
/// assert_eq!(n, 120);
/// ```
pub fn factorial(n: u32) -> u32 {
    (2..=n).product()
}

常用文档章节

Rust 社区约定了一些标准章节,有助于文档的一致性:

  • # Examples:展示用法示例。
  • # Panics:描述函数可能 panic 的条件。
  • # Errors:如果返回 Result,说明可能返回的错误种类。
  • # Safety:对于 unsafe 函数,阐明调用者必须遵守的约束。
rust
/// 除以两个数。
///
/// # Panics
///
/// 如果除数为零,会 panic。
///
/// # Examples
///
/// ```
/// let result = my_crate::div(10, 2);
/// assert_eq!(result, 5);
/// ```
pub fn div(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("除数不能为零");
    }
    a / b
}

使用这些章节可帮助读者快速查找所需信息。

隐藏条目与链接

有时你希望某个条目在文档中被隐藏,不暴露给用户,可使用 #[doc(hidden)] 属性:

rust
#[doc(hidden)]
pub fn internal_api() { }

文档注释中还支持 Markdown 链接语法,可以链接到其他类型或模块,如 [MyStruct] 自动解析为本 crate 中的类型,或使用 [text][path::to::item] 精确跳转。

浏览生成的文档

执行 cargo doc --open 会生成文档并直接在浏览器中打开首页。所有依赖的文档也会被生成,形成一个完整的可搜索知识库。

通过单元测试保证内部逻辑正确,集成测试验证公开 API 的可用性,文档测试让代码示例永久保鲜,再配合 cargo doc 自动生成的精美文档,Rust 的质量保障体系在每个环节都提供坚实支持。接下来请阅读 并发与异步进阶特性

参考文献

资料说明
Testing官方书第 11 章
Documentationcargo doc
rustdoc文档生成工具

相关文章