文章目录
前言
这一篇将简单介绍如何通过cargo进行测试~
因为这涉及到cargo命令比较多的参数配置,本文只会介绍最基础的用法,更多测试方法可以参考官方文档,或 https://doc.rust-lang.org/rust-by-example/testing.html。
10.1 Rust测试的基本要素
- rust通过执行在项目根目录下执行
cargo test
来运行项目中的所有测试用例。执行cargo test --help
可以获得相关说明。- 默认情况下会执行所有使用属性
#[test]
修饰的函数,但是可以使用其他的属性或者指令控制每次执行的测试函数。具体可以通过cargo test --help
查看相关用法。
- 默认情况下会执行所有使用属性
- 在执行
cargo test
后,会显示每一个测试函数的运行状况,并在最后显示一个总结信息,如多少个测试函数通过/失败/忽略没执行等。passed
:通过failed
:失败ignored
:忽略measured
:性能测试filtered
:被过滤,通常只运行指定名称的测试函数时,输出没有被执行的测试函数
- 当使用cargo创建library项目的时候,会生成一个test module,里面有一个test函数,开发者可以添加任意数量的test module或函数。
- 一般情况下,当测试函数抛出
panic!
,则测试函数会失败。
10.1.1 常用测试断言宏和属性
断言宏
宏 | 语法 | 提供方 | 结果 |
---|---|---|---|
assert! | assert!(布尔值表达式[, "格式化提示信息", 格式化值]); | 标准库 | true :测试通过;false :测试失败,调用panic! |
assert_eq! | assert_eq!(val1, val2[, "格式化提示信息", 格式化值]); | 标准库 | 检查传入的两个值是否相等,如果不相等则调用panic! |
assert_ne! | assert_ne!(val1, val2[, "格式化提示信息", 格式化值]); | 标准库 | 检查传入的两个值是否不相等,如果相等则调用panic! |
assert_eq!
和assert_ne!
说明:底层会使用==
判断两个值是否相等,并在断言失败时打印其参数,因此参与比较的值需要实现PartialEq
和Debug
两个trait,所有的基本类型和大部分标准库类型都实现了这些 trait。
should_panic属性
在测试函数上,#[test]
后添加#[should_panic]
,表示该测试函数只有发生panic!
才算执行成功。可用于检查panic功能是否正常。
很多时候并不是说发生任何panic都算是执行成功,只有带有指定信息的panic才算是执行成功,因此可以指定测试函数抛出的错误信息。
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!("Guess value must be greater than or equal to 1, got {}.",
value);
} else if value > 100 {
panic!("Guess value must be less than or equal to 100, got {}.",
value);
}
Guess {
value
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "Guess value must be less than or equal to 100")]
fn greater_than_100() {
Guess::new(200);
}
}
expected
参数为选填参数,当使用时表示panic!的信息必须包含expected
指定的内容(包含即可)。
10.1.2 使用Result<T, E>测试
- 测试函数中可以直接返回一个
Result<T, E>
枚举值,当返回的是Ok(())
表示测试用过,返回Err
测表明测试不通过。 - 不能对这些使用
Result<T, E>
的测试使用#[should_panic]
注解,相反应该在测试失败时直接返回Err
值。
10.2 控制测试的启动
直接调用cargo test
的默认行为:
- 并行运行所有测试函数。
- 捕获(不显示)所有输出,使读取与测试结果相关的输出更容易。
命令行参数:
- 针对
cargo test
的参数:紧跟cargo test
后。cargo test --help
- 针对 测试可执行程序 的参数,通常是一些影响测试的配置:放在
--
之后。cargo test -- --help
控制同时运行的测试函数数目:cargo test -- --test-threads=1
。
- 由于并行运行测试虽然会快,但是多个不同线程执行的测试函数有可能会依赖某个共同的状态,或者互相依赖,此时就需要将并行线程数设置为1,避免互相影响。
控制输出错误摘要信息到终端:cargo test -- --nocapture
- 如果测试通过,则标准输出信息依然被捕获(不会输出);
- 如果测试失败,则标准输出信息会输出到终端(不会被捕获);
控制即使测试样例运行成功也要输出信息到终端:cargo test -- --show-output
通过指定名字运行部分测试:
# 运行所有名字包含one_hundred的测试函数
cargo test one_hundred
# 运行所有名字包含add_的测试函数
cargo test add_
- 支持指定测试名的一部分(模块名也可以),运行多个测试;
忽略某些测试:
- 在测试函数上加入
#[ignore]
属性,可在一般运行cargo test
时跳过这些函数(通常可用于修饰消耗资源较高的测试函数); - 运行被忽略的测试:
cargo test -- --ignored
;
10.3 测试的组织结构
10.3.1 单元测试
- 单元测试的目的是在与其他部分隔离的环境中测试每一个单元的代码,以便于快速而准确的某个单元的代码功能是否符合预期。
- 单元测试与他们要测试的代码共同存放在位于
src
目录下相同的文件中。规范是在每个文件中创建包含测试函数的tests
模块,并使用#[cfg(test)]
标注模块。- 上述模块中的代码会在运行
cargo test
时才会编译和运行测试代码,否则并不会编译这一部分代码。
- 上述模块中的代码会在运行
- 私有函数允许被测试。
- cfg标注:表示configuration(配置),cfg标注的作用是告诉Rust编译器被标注的条目只有在指定的配置选项下才被包含。
- 比如
#[cfg(test)
中test
选项由Rust提供,用来编译和运行测试。只有cargo test
才会编译代码,包括模块中的helper函数和#[test]
标注的函数。
- 比如
10.3.2 集成测试
- 集成测试对于你需要测试的库来说完全是外部的。同其他使用库的代码一样使用库文件,也就是说它们只能调用一部分库中的公有 API。集成测试的目的是测试库的多个部分能否一起正常工作。一些单独能正确运行的代码单元集成在一起也可能会出现问题,所以集成测试的覆盖率也是很重要的。
- 集成测试只能使用公有函数。
- 为了编写集成测试,需要在项目根目录创建一个
tests
目录,与src
同级。Cargo 知道如何去寻找这个目录中的集成测试文件。接着可以随意在这个目录中创建任意多的测试文件,Cargo 会将每一个文件当作单独的 crate 来编译(这些文件不会共享行为)。 - 与单元测试不同,每一个
tests
目录中的测试文件都是完全独立的 crate,所以需要在每一个文件中导入开发的项目库。 - 不需要再在集成测试中标注任何代码为
#[cfg(test)]
,但需要执行的函数还是加上#[test]
标注。 - 同样是执行
cargo test
进行集成测试。cargo test --test 某一个tests目录下的文件名(不用后缀)
:只执行某一个文件的集成测试样例。
- 可以在
tests
目录下创建一些测试时才用到的公用函数,并在每一个测试文件中使用(直接use carte名
即可)。
10.3.3 针对binary crate的集成测试
- 如果项目是binary crate,只包含
src/main.rs
,没有src/lib.rs
,那么不能在tests
目录下创建集成测试,同时无法把main.rs
的函数导入作用域。 - 只有library crate才能暴露函数给其它crate用。
- binary crate意味着独立运行,因此同行会把binary crate的核心代码放在
src/lib.rs
中,而src/main.rs
只去调用lib.rs
的代码,从而可以进行集成测试。
10.3.4 单元测试和集成测试对比
对比项 | 单元测试 | 集成测试 |
---|---|---|
测试代码写在哪里 | 位于待测试项同一个文件下的test 模块下 | 位于项目根目录下的tests 目录下,通常文件名会命名成xxx_test.rs |
是否需要添加#[cfg(test)] 标注 | 需要,在模块上标注 | 不需要 |
是否需要添加#[test] 标注 | 需要,在需要执行的函数上 | 需要,在需要执行的函数上 |