测试
[TOC]
1. 编写测试的基本操作
Rust中使用函数进行代码的测试,用以验证非测试代码是否按照期望的方式运行,一般具有以下操作:
设置所需要的数据或者状态
运行测试代码
断言结果是否为所期望的
1.1 test 属性的注释函数
Rust 中测试函数是一个带有test
属性注释的函数。属性attribute是关于Rust代码片段的元数据,derive
就是一个典型的例子。为了将一个函数设置为测试函数,可以在函数名前加上一个#[test]
.当执行cargo test
时,Rust会构建一个测试程序来调用所有标记了该属性注释的函数。
对于一个成本的测试样例,得到的运行结果:
输出了所有的失败样例,以及测试结果总结,
通常在一个测试函数中,使用assert!
类的宏进行测试的第三项工作,该宏接受一个bool
类型的值,通常使用的宏还有assert_eq!
, assert_ne!
,接受两个类型相同的值,断言相等或者不等。需要注意的是,在一些语言和测试框架中,断言两个值相等的函数的参数叫做 expected
和 actual
,而且指定参数的顺序是很关键的。然而在 Rust 中,他们则叫做 left
和 right
,同时指定期望的值和被测试代码产生的值的顺序并不重要。这个测试中的断言也可以写成 assert_eq!(add_two(2), 4)
,这时失败信息会变成 assertion failed:
(left == right)` 其中
left是
5而
right是
4`。
这两个宏底层实现使用了== !=
, 同时如果断言失败会使用Debug
模式输出错误信息。所以传入的类型必须是实现了PartialEq
以及Debug
trait 的类型。又因为这两个trait都是派生trait,通常可以在结构体上加#[derive(PartialEq + Debug)]
属性注释。
1.2 自定义失败信息
可以向assert!
等宏中添加一个可选的默认失败信息,在测试失败时将这些信息打印出来。获取更加详细的测试信息。
例如,对于一个尚未完成的函数进行测试,由于可能Hello
可变,只对参数进行测试:
如果测试失败,显示的结果只有失败的函数以及行号:
可以添加更多信息:
1.3 panic 测试
对于代码中的panic进行测试时,可以使用should_panic
属性注释进行操作:
但是使用这种方式有一个很大的问题,如果是程序的其他部分panic那么该测试样例也是可以通过的,是的测试结果十分含糊不清,所以一般需要加上一个expeted
参数,从而获取所期望的panic信息。
1.4 用Result<T, E>
进行测试
Result<T, E>
进行测试可以在测试函数返回一个Result<T, E>
枚举类型,可以达到同样的效果:
这里我们将 it_works
改为返回 Result。同时在函数体中,在成功时返回 Ok(())
而不是 assert_eq!
,而失败时返回带有 String
的 Err
。跟之前一样,这个测试可能成功或失败,不过不再通过 panic,可以通过 Result<T, E>
来判断结果。为此不能在对这些函数使用 #[should_panic]
;而是应该返回 Err
!
2. 测试控制
cargo test
命令会生成可执行的二进制文件,默认进行的是并行测试,并且截获所有的输出,阻止其输出到console,从而获得更为清晰地测试结果。可以将一部分命令行参数传递给cargo test
,而另一部分传递给生成的二进制文件,先列出cargo test
的参数,然后是二进制文件参数。
2.1 并行或者串行执行测试
并执行测试时必须保证测试程序间不会生竞争条件,所以测试间不可以相互依赖,或者依赖共享的状态。依赖共享的环境。为了避免测试相互干扰,可以指定单线程运行:
2.2 显示函数输出
2.3 运行单个测试
可以通过指定一个测试函数或者测试模块,运行一部分测试,节省测试时间。这种方式称为filter
2.4 忽略某些测试
对于一些较为耗时的测试,可以添加#[ignore]
属性注释进行忽略,从而加快测试进度。
3. 测试的组织结构
本章一开始就提到,测试是一个复杂的概念,而且不同的开发者也采用不同的技术和组织。Rust 社区倾向于根据测试的两个主要分类来考虑问题:单元测试(unit tests)与 集成测试(integration tests)。单元测试倾向于更小而更集中,在隔离的环境中一次测试一个模块,或者是测试私有接口。而集成测试对于你的库来说则完全是外部的。它们与其他外部代码一样,通过相同的方式使用你的代码,只测试公有接口而且每个测试都有可能会测试多个模块。
为了保证你的库能够按照你的预期运行,从独立和整体的角度编写这两类测试都是非常重要的。
3.1 单元测试
单元测试的主要目的是在与其他部分隔离的环境中进行代码的测试。以便于快速而准确地确定某个部分的代码是否符合预期。单元测试与需要测试的代码都存放在src
目录下,规范是包含测试函数的tests
模块,需要使用#cfg(test)
属性标注。
通过使用#cfg(test)
保证了测试代码只在cargo test
时才编译运行,在cargo build
时不需要这么做。可以在构建库时节省时间。同时由于单元测试代码和库文件在同一个文件夹中,所以需要这个属性进行标示,指定他们不可以包含在库文件中。
3.2 集成测试
在 Rust 中,集成测试对于所需要测试的库是完全外部的。同其他使用库的代码一样使用库文件,也就是说只可以调用库中的pub
API。集成测试的目的是为了保证多个部分是否可以协同工作。集成测试在tests
文件夹内进行。
tests
目录与scr
目录同级,不需要加#cfg(test)
属性 tests
文件夹在 Cargo 中是一个特殊的文件夹, Cargo 只会在运行 cargo test
时编译这个目录中的文件。
现在有了三个部分的输出:单元测试、集成测试和文档测试。仍然可以通过指定测试函数的名称作为 cargo test
的参数来运行特定集成测试。也可以使用 cargo test
的 --test
后跟文件的名称来运行某个特定集成测试文件中的所有测试:
Last updated