自动化测试
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