Rust - Test
- Rust 中一个测试就是一个函数, 需要使用 test 属性进行标注
- 使用
cargo test
命令运行所有测试函数
- Rust 会构建一个 Test Runner 可执行文件,它会运行标注了 test 的函数,并报告其运行是否成功
如何运行测试
1
2
3
4
5
6
7
8
9
10
11
| # 并行运行所有测试函数
# - 测试成功时: 不显示所有输出(如 println!),使读取与测试结果相关的输出更容易
# - 测试失败时会显示如 println! 这样的输出
cargo test
# 命令行参数分为以下两大类
# 1. 针对 cargo test 的参数: 紧跟 cargo test 之后
cargo test --help # 输出 cargo test 的可用的参数
# 2. 针对测试可执行程序: 放在 -- 之后
cargo test -- --help # 输出 -- 之后的所有可用参数
|
- 并行运行测试: 默认使用多个线程并行运行
- 速度快
- 要考虑并发影响,需要确保测试之间不会互相依赖,并且步依赖于某个共享状态(环境、工作目录、环境变量等等)
1
2
3
4
5
| # --test-threads 参数
# - 传递给二进制文件
# - 🙅以并行方式运行测试,或向对线程数进行细粒度控制
# - 可使用该参数,后面跟线程的数量
cargo test -- --test-threads=1 # 单线程运行测试
|
- 显示输出
- 默认情况下, 测试通过,Rust 的 test 库会捕获所有打印到标准输出的内容 (如
println!
)
1
2
| # 即使测试通过,也显示标准输出
cargo test -- --show-output
|
1
2
3
4
| # 根据测试函数的名称指定运行的测试
cargo test test_one_fn # 运行单个测试
# 可指定测试名的一部分 (模块名也可以) 来匹配并运行多个测试
cargo test test_ # 会运行测试函数名字中带有 test_ 的测试
|
如何编写测试
1
2
| #[test]
fn test_fn() {}
|
assert!
宏,来自标准库,用来确定某个状态是否为 true
- true: 测试通过
- false: 调用
panic!
,测试失败
- 使用
assert_eq!
和 assert_ne!
测试相等性
- 判断两个参数是否相等/不等
- 实际上,它们使用的就是
==
和 !=
运算符
- 断言失败: 自动打印出两个参数的值 (要求参数实现了 PartialEq 和 Debug Traits)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| #[derive(Debug)]
pub struct Rectangle {
length: u32,
width: u32,
}
impl Rectangle {
pub fn can_hold(&self, other: &Rectangle) -> bool {
self.length > other.length && self.width > other.width
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn larger_can_hold_smaller() {
let larger = Rectangle { length: 8, width: 7 };
let smaller = Rectangle {
length: 5,
width: 1,
};
assert!(larger.can_hold(&smaller)); // 二参可传自定义信息
// assert!(something, "{} {}", formart_param1, formart_param2);
}
}
|
- 使用
should_panic
属性测试是否发生了恐慌 (验证代码是否发生了 panic)
1
2
3
4
5
6
7
8
9
10
11
| // 函数 panic: 测试通过,否则失败
#[cfg(test)]
mod tests {
use super::*;
// should_panic 修饰的函数 panic 了测试才会通过
// 可 should_panic(expected = "xxxx"),验证 panic 时,错误信息是否包含了 expected 注明的字符串
#[test]
#[should_panic]
fn test_fn() {...}
}
|
- 测试中使用
Result<T, E>
- 无需 panic, 可使用
Result<T, E>
作为返回类型编写测试
- 返回 Ok 测试通过,返回 Err 测试失败
1
2
3
4
5
6
7
8
9
10
11
| #[cfg(test)]
mod tests {
#[test]
fn it_works() -> Result<(), String> {
if 2 + 2 == 4 {
Ok(())
} else {
Err(panic!(String::from("two plus two does not equal four")))
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
| // 运行 cargo test,只会运行 fn1 这个测试
// "cargo test -- --ignored": 只运行被标记 ignore 的测试
#[cfg(test)]
mod tests {
#[test]
fn fn1() {...}
#[test]
#[ignore]
fn fn2() {...}
}
|
如何组织测试
单元测试
- 一次对一个模块进行隔离的测试
- 可测试 private 接口
- 一般单元测试和被测试的代码都放在 src 目录下的同一个文件中
- 约定每个源代码文件都建立 tests 模块来放测试函数,并使用
#[cfg(test)]
标注 tests 模块
- 使用
#[cfg(test)]
标注后,只有运行 cargo test
才编译和运行代码,而 cargo build
则不会
- cfg: Configuration, 告诉 Rust 下面的条目只有在指定的配置选项下才被包含
1
2
3
4
5
6
7
8
9
10
11
12
13
| // 只在 cargo test 才会把以下代码拉入编译范围
fn fn1() {...}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
fn1(); // 可调用私有函数
assert_eq!(4, 2 + 2);
}
}
|
集成测试
- 在库外部,和其他外部代码一样使用你的代码
- 只能访问 public 接口
- 可能在每个测试中使用到多个模块
- 集成测试和被测试文件在不同的目录,不需要
#[cfg(test)]
标注
- 集成测试放在 src 同级的 tests 目录下,tests 目录下的每个测试文件都是单独的一个 crate
- 这些文件步共享行为 (与 src 下的文件规则不同)
- 如果需要在 tests 文件下共享逻辑,可以建立子目录,在其中编写通用逻辑 (tests 下的子目录不会被当成测试文件运行)
1
2
3
4
5
6
7
8
| // adder/tests/integration_test.rs
use adder; // 项目的名字
// 由于 tests 目录只会在执行 cargo test 命令的时候运行,所以不需要使用 #[cfg(test)] 标注
#[test]
fn it_adds_two() {
assert_eq!(4, adder::add_two(2))
}
|
- 运行指定的集成测试
cargo test 测试函数名
- 运行某个测试文件内的所有测试:
cargo test --test 文件名
针对 binary crate 的集成测试
- 如果项目时 binary crate,只含有
src/main.rs
没有 src/lib.rs
- 不能在 tests 目录下创建集成测试
- tests 无法把
main.rs
的函数导入作用域
- 只有 library crate 才能暴露函数给其他 crate 用
- binary crate 意味着独立运行
- 所以通常 binary crate,都会把逻辑放在
lib.rs
里面,方便集成测试,main.rs
只有少量的调用逻辑