Skip to content

编写自动化测试

测试函数剖析

  • #[cfg(test)]: 作用于整个模块或代码块,控制是否进行编译.
  • #[test]: 作用于具体的函数,告诉测试执行器这是一个需要被运行的测试用例
  • #[ignore]: #[test] 之后增加了 #[ignore] 行,执行 cargo test 将跳过该测试.可以通过 cargo test -- --ignored 单独运行被忽略的测试

#[cfg(test)] 只有在运行 cargo test 时才编译这段代码.这可以减小最终发布包(cargo build --release)的体积,并加快正常编译速度

检查结果

  • assert!(bool, str): 为 true 时什么都不做,为 false 调用 panic! 宏,可以增加错误信息
  • assert_eq!(a, b): 比较两个值是否相等
  • assert_ne!(a, b): 比较两个値是否不相等

eq表示"equal"(相等),ne表示"not equal"(不相等)

使用 should_panic 检查 panic

  • #[should_panic] 属性位于 #[test] 之后
  • #[should_panic(expected = "错误信息")]: 可以精确匹配错误信息中包含的文本
  • 确保当程序接收到非法输入或进入非法状态时,能够正确地触发 panic!
  • 添加此属性后,只有当函数发生 panic 时,测试才会被视为通过(Passed)

在测试中使用 Result<T, E>

  • 不能对这些使用 Result<T, E> 的测试使用 #[should_panic] 注解
  • 为了断言一个操作返回 Err 成员,不要对 Result<T, E> 值使用问号表达式(?).而是使用 assert!(value.is_err())
  • 使用 Result 的优势是可以在测试中使用 ? 操作符,使代码更简洁

综合例子

rust
// 被测试函数
fn add(x: i32, y: i32) -> i32 {
    x + y
}

#[test]
fn it_works() {
    let result = add(1, 1);
    assert_eq!(result, 2); // 断言结果等于2
}

#[test]
fn it_fails() {
    let result = add(1, 1);
    assert!(result == 2, "结果不相等: {}", result); // 为false时打印错误信息
}

// 会panic的函数
fn will_panic() {
    panic!("崩溃了")
}

#[test]
#[should_panic(expected = "崩溃了")]
fn test_panic() {
    will_panic();
}

// 返回Result的测试函数
#[test]
fn result() -> Result<(), String> {
    let x = add(1, 1);
    if x == 2 {
        Ok(())
    } else {
        Err(format!("结果不等于2,实际值: {}", x))
    }
}

代码说明:

  • 第一个测试使用 assert_eq! 直接比较值
  • 第二个测试使用 assert! 配合错误信息
  • #[should_panic]expected 参数应与实际 panic 信息匹配
  • 使用 Result<T, E> 的测试函数无需 #[should_panic] 属性

运行测试

测试命令

命令针对对象典型功能
cargo test --helpCargo 编译管理器选择测试目标(lib/bin/doc)、条件编译、依赖处理
cargo test -- --help测试运行程序并行度控制、输出捕获、运行被忽略的测试

并行或顺序运行测试

  • 当运行多个测试时,Rust 默认使用线程来并行运行
  • 如果不希望测试并行运行,或想要精确控制线程数量,可以传递 --test-threads 参数
bash
$ cargo test -- --test-threads=1 # 只使用一个线程,顺序运行测试

显示函数输出

  • 默认情况下,通过的测试中的打印值会被捕获.使用 --show-output 显示成功测试的输出
bash
$ cargo test -- --show-output

通过名称运行部分测试

运行单个测试

bash
$ cargo test 函数名

过滤运行多个测试

bash
$ cargo test 函数名的一部分
  • 任何名称匹配这个部分的测试都会被运行

运行被忽略的测试

bash
$ cargo test -- --ignored
  • 只运行标记为 #[ignore] 的测试
bash
$ cargo test -- --include-ignored
  • 同时运行所有测试(包括被忽略的)

测试的组织架构

单元测试(unit tests)

单元测试的目标是隔离地验证每一段代码(通常是一个函数或模块)的逻辑

  • 存放位置: 直接写在源文件(.rs)中.通常放在文件的末尾,并包含在一个带有 #[cfg(test)] 属性的模块内
  • 私有访问: 单元测试可以访问所属模块中的私有函数(由于 use super::*;)
  • 编译行为: 只在运行 cargo test 时编译,不影响发布包体积

单元测试示例:

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

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

    #[test]
    fn test_add() {
        assert_eq!(add(2, 2), 4);
    }
}

集成测试(integration tests)

集成测试的目标是验证多个库部分是否协同工作.它像"外部用户"一样调用你的代码

  • 存放位置: 存放在项目根目录下的 tests/ 目录中(与 src 平级).每个 .rs 文件都会被编译为一个独立的 crate
  • 私有访问: 不能访问私有函数,只能测试库导出的 pub(公共)API
  • 适用范围:仅适用于库项目(Library).纯二进制项目(只有 main.rs)无法直接使用 tests/ 目录进行集成测试
  • 编译行为: 编译为独立的可执行二进制文件

集成测试示例:

rust
// tests/integration_test.rs
use my_crate;

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

对比

特性单元测试集成测试
存放位置src/ 内部文件末尾tests/*.rs 独立文件
属性标记#[cfg(test)]无需标记(文件夹特性)
访问权限可访问 pub 和私有成员仅能访问 pub 成员
测试范围细粒度、单个函数/模块粗粒度、功能流、对外接口
编译行为与源码一起编译编译为独立的二进制文件
适用项目库和二进制项目仅库项目

文档测试

  • 文档测试 (Doc-tests) 是直接写在代码注释中的代码示例,它们会被 cargo test 像真正的测试用例一样执行
  • 其核心思想是: "文档即测试",确保你给用户的示例代码永远不会因为 API 的变动而失效

基本用法

  1. 书写位置: 位于 /// 类型的文档注释中,通常在 # Examples 标题下,并包裹在 Markdown 的代码块(三个反引号)里
  2. 验证一致性: 如果代码改了名,而文档里的例子没改,cargo test 会直接报错,防止误导用户
  3. 库项目专用: 主要用于 Library 项目,Binary 项目(main.rs)通常不支持文档测试

示例

rust
/// 将两个数字相加.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}

详细说明

1. 文档注释标记 ///

  • 与普通的 // 不同,/// 是文档注释
  • Rust 编译器(rustdoc)会提取这些内容,生成 HTML 格式的项目 API 文档
  • 必须写在要说明的项(如函数)之前

2. Markdown 格式

  • 注释内部完全支持 Markdown 语法
  • # Examples 是二级标题,在生成的 HTML 文档中会显示为独立栏目

3. 代码块与隐式 Main

  • 代码块中的代码会被当作独立程序运行
  • Rust 在后台自动将代码包裹进虚拟的 fn main() { ... }
  • my_crate::add_one: 需要像外部用户一样引用包名

文档测试的控制

  • 用于包裹文档测试的代码块
语法作用
```rust标准的可运行文档测试
```ignore显示代码但不运行
```no_run编译但不运行(用于耗时操作)
```should_panic测试应该 panic 的代码
```compile_fail测试代码应该编译失败

相关命令

bash
$ cargo test --doc                           # 只运行文档测试
$ cargo doc --open                           # 生成并打开 API 文档
$ cargo doc --open --document-private-items  # 包含私有项的文档

lib.rs 中的函数默认是 pub 的,无需 --document-private-items 参数

main.rs 中的函数通常是私有的,需要该参数查看文档

基于 MIT 协议发布