第八章:测试、文档与质量
Rust 将测试和文档视为语言的一等公民。通过内置的测试框架,你可以在项目里编写单元测试、集成测试以及随文档一起运行的示例测试,三者共用 cargo test 命令。配合 cargo doc 生成文档,形成了一套确保代码质量与可维护性的完整工作流。
8.1 单元测试
单元测试检验代码的局部功能,通常与被测试代码放在同一文件中,用条件编译隔离。
#[cfg(test)] 模块与 #[test] 属性
单元测试通常放在源码文件底部、一个标有 #[cfg(test)] 的模块里。这保证测试代码只在 cargo test 时编译,发布时完全排除。
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
#[test] 属性标记测试函数。Cargo 会自动发现并运行所有带此属性的函数。
断言
Rust 提供多种断言宏来验证程序状态:
assert!(condition):断言条件为true,否则 panic。assert_eq!(left, right):断言左右值相等(要求实现PartialEq和Debug,以便打印差异)。assert_ne!(left, right):断言两个值不相等。
#[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 而误判通过。
#[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::),所以能直接测试私有函数,无需破坏封装向外暴露接口。
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。
目录结构
my_project/
├── src/
│ └── lib.rs
├── tests/
│ ├── integration_test.rs
│ └── another_test.rs
└── Cargo.toml
每个测试文件都像是一个全新 crate,需要通过外部 crate 的方式引入被测库:
// 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 本身不是测试文件——只有作为模块被其他测试引用时,它才被编译。
tests/
├── common/
│ └── mod.rs
├── integration_test.rs
在 integration_test.rs 中引用:
mod common;
#[test]
fn test_with_setup() {
common::setup();
// 进行测试
}
这样 common 不会作为测试单独运行,同时可以做好配置管理。
8.3 文档测试
Rust 的文档注释 /// 不仅能生成 HTML,其中的代码块还会被 cargo test 当作测试执行,确保示例代码长期有效、准确。
基本用法
在文档注释中写入 ``` 代码块,它们默认会被编译并运行:
/// 将两个数相加。
///
/// ```
/// let result = my_crate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
运行 cargo test 时,会提取这些代码编译成独立测试并执行,断言失败则测试失败。
隐藏代码行
有时需要展示部分前置代码,但不希望它出现在文档中,可在行首添加 # :
/// ```
/// # 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:预期编译失败(用于演示类型错误等)。
/// ```compile_fail
/// let x: i32 = "hello"; // 编译错误演示
/// ```
这些标记让文档示例能够灵活演示正确与错误用法。
8.4 文档生成
Rust 使用 rustdoc 工具从文档注释生成 HTML 网页。运行 cargo doc 会为当前 crate 及所有依赖生成离线文档,放置在 target/doc 目录。
文档注释语法
///:条目级注释,用于函数、结构体、模块等(Markdown 格式)。//!:模块级注释,用于文件或模块的整体说明,写在模块最顶部。
//! # 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函数,阐明调用者必须遵守的约束。
/// 除以两个数。
///
/// # 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)] 属性:
#[doc(hidden)]
pub fn internal_api() { }
文档注释中还支持 Markdown 链接语法,可以链接到其他类型或模块,如 [MyStruct] 自动解析为本 crate 中的类型,或使用 [text][path::to::item] 精确跳转。
浏览生成的文档
执行 cargo doc --open 会生成文档并直接在浏览器中打开首页。所有依赖的文档也会被生成,形成一个完整的可搜索知识库。
通过单元测试保证内部逻辑正确,集成测试验证公开 API 的可用性,文档测试让代码示例永久保鲜,再配合 cargo doc 自动生成的精美文档,Rust 的质量保障体系在每个环节都提供坚实支持。接下来请阅读 并发与异步 与 进阶特性。
参考文献
| 资料 | 说明 |
|---|---|
| Testing | 官方书第 11 章 |
| Documentation | cargo doc |
| rustdoc | 文档生成工具 |
相关文章
泛型与 Trait
泛型和 trait 是 Rust 实现代码复用与多态的两大支柱。前置:Rust 基础。泛型让代码可以工作在多种类型上而不牺牲性能,trait 定义了类型间的共享行为,二者结合形成了零成本抽象的强大表达能力。本章将深入泛型定义、trai…
第二章:基础语法与类型
Rust 的类型系统和语法设计处处体现着“安全”与“显式”的理念。这一章你将掌握变量绑定、基本类型、复合类型、函数定义以及所有基础控制流结构,它们是你写出任何 Rust 程序的基石。
第七章:模块系统与包管理
Rust 的模块系统为代码组织、封装和复用提供了一套严谨但灵活的机制。包、crate、模块以及 use 路径相互配合,让你能够把项目拆解成清晰的功能单元,同时精确控制哪些对外可见。本章将带你系统掌握这些构建大型 Rust 项目所必需的…
第十章:进阶特性与模式
Rust 的核心安全保证覆盖了绝大多数日常编程场景。但当你需要打破常规——无论是编写极致通用的抽象、与 C 库交互,还是内联优化——本章将带你进入 Rust 的深层能力:声明宏与过程宏、unsafe 的超能力与封装、高级类型系统技巧…
第五章:错误处理
Rust 将错误明确分为两类:不可恢复的错误与可恢复的错误。通过 panic! 处理前一种,Result 处理后一种,这让程序的错误路径不再是隐式的控制流,而是强类型、必须处理的代码分支。配合 Option 对缺失值的处理以及丰富的组…
第三章:所有权、借用与生命周期(核心)
所有权系统是 Rust 最独特、最核心的语言特性。它让 Rust 无需垃圾回收器就能保证内存安全,并在编译期消除数据竞争。本章你将深入理解所有权如何运转、引用和借用如何被检查、生命周期如何标注,以及常见智能指针如何扩展所有权模型。
Series
rust
5 / 10