Rust 学习笔记 - 测试
在 Rust 中一个测试就是一个函数,他被用于验证非测试代码的功能是否和预期一致。
在测试的函数体里面通常会执行 3 个操作(简称 3A 操作):
- 准备(Arrange)数据/状态
- 运行(Act)被测试的代码
- 断言(Assert)结果
测试函数
测试函数本质也是函数,只是需要使用 test
属性(attribute)进行标注或者叫修饰。
Attribute 就是一段 Rust 代码的元数据,在函数上加 #[test]
,可以把函数变成测试函数。
运行测试
使用 cargo test
命令运行所有测试函数,Rust 会构建一个 Test Runner 可执行文件,它会运行标注了 test 的函数,并报告其运行是否成功。
使用 cargo 创建 library 项目的时候,会生成一个 test module,里面有一个 test 函数,你可以添加任意数量的 test module 或函数。
例子
创建一个 library 项目:
cargo new adder --lib
在其 src/lib.rs
中有一段默认的测试代码:
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
进入目录
cd adder
运行测试
cargo test
断言(Assert)
assert!
宏,来自标准库,用来确定某个状态是否为 true,true 为通过测试,false 会调用 panic!
,测试失败。
assert_eq!
宏,来自标准库,判断两个参数是否相等,类似 ==
。
assert_ne!
宏,来自标准库,判断两个参数是否不等,类似 !=
。
assert_eq!
和 assert_ne!
在断言失败的时候会自动打印出两个参数的值,使用 debug 格式打印参数要求参数实现了 PartialEq
和 Debug
Traits(所有的基本类型和标准库里大部分类型都实现了,不过对于自定义的枚举和结构体就需要自己实现这两个 Trait 了)。
添加自定义错误消息
assert!
的第二参数,assert_eq!
和 assert_ne!
的第三参数是可选的,可以传入自定义消息,失败的时候会打印出来,自定义消息参数会被传递给 format!
宏,可以使用 {}
占位符。
验证错误处理的情况
测试除了验证代码返回是否正确,还需验证代码是否如预期的处理了发生错误的情况。可验证代码在特定情况下是否发生了 panic。
需要添加额外的 attribute should_panic
。添加 expected
参数可以更加精准的检查出失败信息中包含的文字。
#[cfg(test)]
mod tests {
#[test]
#[should_panic(expected = "message")]
fn it_works() {
// ...
}
}
在测试中使用 Result<T, E>
无需 panic,可使用 Result<T, E>
作为返回类型编写测试,返回 Ok 测试通过,返回 Err 测试失败。
#[cfg(test)]
mod tests {
#[test]
fn it_works() -> Result<(), String> {
if 2 + 2 == 4 {
Ok(())
} else {
Err(String::from("err"))
}
}
}
控制测试如何运行
添加参数可以改变 cargo test
的行为。
其默认行为是:并行运行所有测试,捕获(不显示)所有输出,使读取与测试结果相关的输出更容易。
命令行参数:
# 显示能跟哪些命令行参数
cargo test --help
# 显示可以用在 -- 之后的命令行参数
cargo test -- --help
#一个线程测试
cargo test -- --test-threads=1
并行运行测试的时候虽然运行快,但是要注意确保测试之间不会相互依赖,不依赖某个共同状态(环境、工作目录、环境变量等)。
显示函数输出:默认如果测试通过,Rust 的 test 库会捕获所有打印到标准输出的内容。例如:如果被测试代码中用到了 println!
,测试通过就不会在终端看到 println!
打印的内容,测试失败就可以看到这些信息。
如果想在成功的测试用例中看到打印的内容就需要加入 --show-output
。
按名称运行测试
将测试名称作为 cargo test
的参数选择运行的测试:
cargo test fn_name
将测试名称的一部分(或者模块名)作为 cargo test
的参数选择运行的测试,可以就可以匹配多个测试:
cargo test part_of_fn_name
忽略测试
忽略某些测试,运行剩余测试,可以使用 ignore
属性来标记。
#[cfg(test)]
mod tests {
#[test]
#[ignore]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 5);
}
}
如果要单独运行被忽略的测试可以使用:
cargo test -- --ignored
测试的组织
Rust 将测试分为单元测试和集成测试两类。
单元测试:小、专注,一次对一个模块进行隔离测试,可以测试 private 接口。
集成测试:在库外部。和其它外部代码一样使用你的代码,只能使用 public 接口。可能在每个测试中使用到多个模块。
单元测试
tests 模块上的 #[cfg(test)]
就是对单元测试的标注。
标注之后的代码只有运行 cargo test
才编译和运行代码,运行 cargo build
则不会。
测试私有函数:
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 it_works() {
assert_eq!(4, internal_adder(2, 2));
}
}
集成测试
目的是测试被测试库的多个部分是否能正确的一起工作。集成测试的覆盖率很重要。
创建集成测试目录:tests
,这个目录会被特殊对待,它与 src
并列。
tests
目录下的每个测试文件都是单独的一个 crate
。
// adder/tests/integration_test.rs
use adder; // 导入被测试库
#[test]
fn it_adds_two() {
assert_eq!(4, adder::add_two(2));
}
运行
cargo test
运行指定的集成测试
cargo test 函数名
# 或
cargo test --test 文件名
如果现在集成测试目录下搞一些共享的代码,需要把他们写到 tests/common
目录下,否则会被当成测试代码直接执行。
针对 binary crate
的集成测试有一些不同,如果项目是 binary crate
,只含有 src/main.rs
没有 src/lib.rs
就不能在 tests
目录下创建集成测试。即使有测试文件也无法把 main.rs
的函数导入作用域,因为只有 library crate
才能暴露函数给其它 crate
用。
binary crate
意味着要独立运行。
转载自:https://juejin.cn/post/7037043782948225055