Skip to main content

自动化测试

Rust 中的测试方法为使用 #[test] 标记的函数. 当执行 cargo test 时, Rust 构造一个 test runner 二进制文件来运行这些测试函数.

比如当我们新建 library 项目时, 会自动生成包含一个测试方法的模块:

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

还可用 #[ignore] 标记某些测试是被忽略的, 这样只有在显式请求时才会运行这些测试.

在测试函数中, 通过 assert! 族的宏来编写断言, 比如 assert_eq, assert_ne 等.

如果要检测 panic, 可以写 should_panic:

#[test]
#[should_panic]
fn greater_than_100() {
Guess::new(200);
}

同样在测试函数中可以返回 Result<T, E>, 这样可以在测试过程中使用 ? 操作符来简化写法. 如果是 Result 返回值的测试函数, 则不能标记为 should_panic. 如果想测试被测内容是返回错误的, 则不需要使用 ?, 而是写 assert!(value.is_err()) 即可.

控制测试的运行

cargo test 将代码编译到 test mode 运行, 就和 cargo run 将代码编译为二进制文件类似.

cargo test 编译的二进制文件默认行为就是并行运行所有的测试函数, 并捕捉所有的输出. 不过可以手动改变这些行为.

在手动运行 cargo test 时, 参数有两类, 一类是为 cargo test 指定的参数, 另外一类是为生成的二进制程序指定的参数. 这两类参数通过 -- 进行分隔.

比如让测试顺序执行, 可以这样启动测试:

$ cargo test -- --test-threads=1

默认情况下, Rust 的 test 库会捕捉所有测试函数中的标准输出信息(比如 println! 打印的信息), 而不是直接让它打印到终端. 如果要关闭这个行为, 可以使用:

$ cargo test -- --show-output

还可以控制想要运行的某组测试, 方式是在 cargo test 时传入测试函数名称:

# 执行单个测试函数
cargo test one_hundred
# 执行多个测试函数(只要在名字中包含该字符串): 这样 add_1 add_2 add_some_xxx 都会被执行
# 在测试时, 测试函数名称实际包含了它所在模块的名称, 因此还可以传入 module name:
cargo test module_name

# 只运行被 ignored 标记的测试函数:
cargo test -- --ignored

# 执行所有测试函数, 包括标记为 ignored 的:
cargo test -- --include-ignored

测试代码的组织

Rust 的习惯是将测试分为单元测试集成测试.

对于单元测试: 将测试代码和被测代码放到一个文件中, 测试函数应放到单独的 mod, 且 mod 的命名约定是 tests, 并且测试 mod 需要标记为 #[cfg(test)].

#[cfg(test)] 注解用于告知 Rust 编译器只在 test 模式下才编译这些代码, 即 cargo build 不会编译测试代码.

cfg 表示配置, 通过它告诉编译器仅在指定的编译配置下才编译 mod 中的代码.

Rust 的模块访问控制规则中子 mod 可以使用父中的任何符号, 因此在测试时能够测试私有成员:

pub fn add_two(a: i32) -> i32 {
internal_adder(a, 2)
}

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

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

#[test]
fn internal() {
assert_eq!(4, internal_adder(2, 2));
}
}

集成测试编写时, 可以将集成测试理解为本 package 的一个外部客户. 首先需要建立 tests 目录(和 src 目录同级):

adder
├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
└── integration_test.rs

Cargo 会自动找到集成测试中的所有内容, 并将每个测试文件编译为单独的 crate.

比如 tests/integration_test.rs 文件内容:

use adder;

#[test]
fn it_adds_two() {
assert_eq!(4, adder::add_two(2));
}

因为 Cargo 将 tests 目录特殊处理, 只会在 cargo test 时才编译其中代码, 故无需在 tests 目录中的集成测试模块上标记 #[cfg(test)].

在运行 cargo test 时的三部分输出分别是: 单元测试 集成测试 文档测试.

和单元测试类似, 也可以在 cargo test 时指定想执行的测试:

# 仅执行集成测试中的对应测试.
cargo test --test integration_test