Rust通过编写测试函数验证代码功能是否和预期一致。
介绍
Rust 测试函数通常有三部分组成:
定义
- 测试函数需要使用
#[test]
属性(attribute)将函数变成测试函数 cargo test
命令运行 Rust 项目所有的测试函数- cargo 创建 library 项目时,会生成一个test module,里面包含一个 test 函数
- 创建命令:
cargo new <lib-name> --lib
,src/lib.rs
示例:
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
- 测试失败:每个测试运行一个新线程,测试函数
panic!
(触发现场挂掉)表示测试失败
断言 Assert
Rust 使用标准库中的:
assert!([true | false])
宏- 测试结果
- true:测试通过
- false:调用
panic!
,测试失败
- 可以通过第2个参数自定义错误消息
assert_eq!(k1, k2)
宏相等(==)判断- 可以通过第3个参数自定义错误消息,
assert_eq!(k1, k2, "msg")
assert_ne!(k1, k2)
宏不相等(!=)判断- 可以通过第3个参数自定义错误消息,
assert_ne!(k1, k2, "msg")
assert_eq!
和 assert_ne!
失败自动使用 debug 格式打印出两个参数的值,调用 PartialEq
和 Debug Traits
实现输出
检测恐慌 should_panic
#[should_panic]
属性(attribute)用来检测代码是否发生恐慌:
- 函数发生 panic:测试通过
- 函数未发生 panic:测试不通过
fn abc(value: i32) -> i32 {
if value > 1 {
panic!("{} > 1", value);
}
value
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn it_works1() {
abc(2);
}
#[test]
#[should_panic(expected="xxxx")]
fn it_works2() {
abc(2);
}
}
说明:
#[should_panic(expected="xxx")]
属性(attribute)添加 expected
参数检测失败消息是否包含特定的内容
测试结果 Result<T, E>
测试结果也可以使用 Result<T, E>
作为返回类型:
#[cfg(test)]
mod tests {
#[test]
fn it_works() -> Result<(), String> {
if 2 + 2 == 4 {
Ok(())
} else {
Err(String::from("2+2!=4"))
}
}
}
说明:
Result<T, E>
结果的测试,不需要添加 #[should_panic]
属性,因为 Err 已经处理 panic
cargo test
cargo test
默认行为:
cargo test
命令行参数:
- 帮助:
cargo test --help
- 可执行程序参数:
cargo test -- xxx
- 测试程序二进制帮助(有编译过程):
cargo test -- --help
并行运行测试
多个线程并行运行:
- 特点:速度快
- 要求:
- 测试用例间不相互依赖
- 不依赖某个共享状态(如环境、工作目录、环境变量等)
- 线程控制:
cargo test -- --test-threads n
显示函数输出
默认,测试通过不会显示 println!
的输出,失败会显示。
显示所有(成功+失败)输出:cargo test -- --show-output
按名称运行测试
- 按名称运行测试子集:将测试的名称(一个或多个)作为
cargo test
的参数- 单个测试:
cargo test <测试函数名>
- 多个测试:指定测试名称的一部分(模块名称也可以)
cargo test <common-prefix>
忽略某些测试
- 使用
#[ignore]
属性注释的测试用例使用 cargo test
会自动忽略 - 使用
cargo test -- --ignored
执行忽略的测试用例
#[ignore]
fn test_xxx() {}
测试的分类
- 单元测试
- 小、专注
- 一次对一个模块进行隔离测试
- 可测试 private 接口/函数(和使用public函数相同)
- 集成测试
- 在库外部
- 只能测试 public 接口
- 可能在每个测试中使用到多个模块
单元测试
#[cfg(test)]
标注- 仅当运行
cargo test
时才编译和运行代码 - 运行
cargo build
不编译和运行 - cfg(configuration)
- 配置项 test:由 Rust 提供,用来编译和运行测试,只有
cargo test
才编译,运行 #[test]
标注的函数
集成测试
在 Rust 中,集成测试完全位于被测试库的外部
目的:测试多个部分集成在一起工作是否正常
重要指标:覆盖率
目录:与 src
目录并列的 tests
目录
- tests 目录下的每一个测试文件都是单独的一个
crate
tests/common/mod.rs
类似子目录不会被单独编译,可以在测试文件中使用 mod 导入该模块- 该目录仅当执行
cargo test
时编译
tests/xxx_test.rs
// 导入库
use abc;
#[test]
fn it_xxx() {
assert_eq!(4, abc::add(2, 2));
}
- 运行指定的集成测试
- 运行一个特定的集成测试:
cargo test 函数名
- 运行某个测试文件内的所有测试:
cargo test --test 文件名
其他说明:
- library crate 才能暴露函数给其他 crate 用
- 针对 binary crate 的集成测试,只包含 src/main.rs 没有 src/lib.rs
- 不能在 tests 目录下创建集成测试
- 无法把 main.rs 的函数导入作用域