参考章节《Rust 程序设计语言》第11章 编写自动化测试
为你的程序编写测试是一个良好的习惯(尽管大部分人不愿意这么做,包括我自己,囧)
我们来看一个简单的测试用例
|
|
#[cfg(test)]
注解告诉 Rust 只在执行 cargo test
时才编译和运行测试代码,而在运行 cargo build
时不这么做。
为了将一个函数变成测试函数
,还需要在 fn
行之前加上 #[test]
注解。
assert!
判断测试的条件是否为 true
。需要向 assert!
宏提供一个求值为布尔值的参数。
assert_eq!
宏用于测试表达式的值
是否与期望值
相等,可以通过向 assert!
宏传递一个使用 ==
运算符的表达式来做到。
assert_ne!
宏用于测试表达式的值
是否与期望值
不等,可以通过向 assert!
宏传递一个使用 ==
运算符的表达式来做到。
测试什么时候算失败?
- 当测试函数中出现
panic
时测试就算失败了。每一个测试都在一个新线程中运行,当主线程发现测试线程异常了,就将对应测试标记为失败。
|
|
- 当测试函数返回
Result<T, E>
并且返回的成员是Err
时测试也算失败
|
|
注意: 不能对这些使用 Result<T, E>
的测试使用 #[should_panic]
注解。
自定义失败信息
你也可以向 assert!
、assert_eq!
和 assert_ne!
宏传递一个可选的失败信息参数,可以在测试失败时将自定义失败信息一同打印出来。
在 assert!
、assert_eq!
、assert_ne!
的必需参数之后指定的参数都会传递给 format!
宏,所以可以传递一个包含 {}
占位符的格式字符串和需要放入占位符的值。
自定义信息有助于记录断言的意义;当测试失败时就能更好的理解代码出了什么问题。
|
|
cargo test
运行上面的代码
|
|
使用 should_panic 检查 panic
除了检查代码是否返回期望的正确的值之外,检查代码是否按照期望处理错误也是很重要。
|
|
然而 should_panic
测试结果可能会非常含糊不清,因为它只是告诉我们代码并没有产生 panic
。
should_panic
甚至在一些不是我们期望的原因而导致 panic
时也会通过。
为了使 should_panic
测试结果更精确,我们可以给 should_panic
属性增加一个可选的 expected
参数。测试工具会确保错误信息中包含其提供的文本。
|
|
控制测试运行
关于参数的一些说明
可以将一部分命令行参数传递给
cargo test
,而将另外一部分传递给生成的测试二进制文件。
为了分隔这两种参数,需要先列出传递给cargo test
的参数,接着是分隔符--
,再之后是传递给测试二进制文件的参数。
运行cargo test --help
会提示cargo test
的有关参数,而运行cargo test -- --help
可以提示在分隔符--
之后使用的有关参数。
- 如果你不希望测试并行运行,或者想要更加精确的控制线程的数量,可以传递
--test-threads
参数和希望使用线程的数量给测试二进制文件。
|
|
- 默认情况下,当测试通过时,Rust 会截获打印到标准输出的所有内容。可以在结尾加上
--show-output
告诉 Rust 显示成功测试的输出。
|
|
- 只运行部分测试
有时运行整个测试集会耗费很长时间。如果你负责特定位置的代码,你可能会希望只运行与这些代码相关的测试。
你可以向 cargo test
传递所希望运行的测试名称的参数来选择运行哪些测试。
|
|
你还可以指定部分测试的名称,任何名称匹配这个名称的测试会被运行。
|
|
也可以使用 cargo test
的 --test
后跟文件的名称来运行某个特定集成测试文件中的所有测试
|
|
- 忽略某些测试
有时一些特定的测试执行起来是非常耗费时间的,所以在大多数运行 cargo test
的时候希望能排除他们。可以使用 ignore
属性来标记耗时的测试并排除他们
|
|
如果我们只希望运行被忽略的测试
|
|
如果你希望不管是否忽略都要运行全部测试
|
|
测试的组织结构
单元测试与集成测试
单元测试倾向于更小而更集中,在隔离的环境中一次测试一个模块,或者是测试私有接口。
而集成测试对于你的库来说则完全是外部的。它们与其他外部代码一样,通过相同的方式使用你的代码,只测试公有接口而且每个测试都有可能会测试多个模块。
- 单元测试
我们之前的所有测试用例都是单元测试
单元测试与他们要测试的代码共同存放在位于 src
目录下相同的文件中。
规范是在每个文件中创建包含测试函数的 tests
模块,并使用 cfg(test)
标注模块。
- 集成测试
为了编写集成测试,需要在项目根目录创建一个 tests
目录,与 src
同级。Cargo
知道如何去寻找这个目录中的集成测试文件。
接着可以随意在这个目录中创建任意多的测试文件,Cargo
会将每一个文件当作单独的 crate
来编译。
集成测试不需要 cfg(test)
举个例子,我们新建一个库项目 adder
有如下代码 src/lib.rs
|
|
然后在 src
同级目录创建一个 tests
目录,新建一个文件 tests/integration_test.rs
|
|
与单元测试不同,我们需要在文件顶部添加 use adder
。这是因为每一个 tests
目录中的测试文件都是完全独立的 crate
,所以需要在每一个文件中导入库。
并不需要将 tests/integration_test.rs
中的任何代码标注为 #[cfg(test)]
。
tests
文件夹在 Cargo
中是一个特殊的文件夹, Cargo
只会在运行 cargo test
时编译这个目录中的文件。
- 创建测试公共函数
当你有一些在多个集成测试文件都会用到的函数,你就可以将他们提取到一个通用的模块中,然后在测试文件中调用即可
我们将创建 tests/common/mod.rs
,而不是创建 tests/common.rs
。这是一种 Rust 的命名规范,这样命名告诉 Rust 不要将 common 看作一个集成测试文件。
tests 目录中的子目录不会被作为单独的 crate 编译或作为一个测试结果部分出现在测试输出中。
|
|
在测试文件中调用这个公共函数 tests/integration_test.rs
|
|
- Rust 允许你测试父模块中的私有函数
|
|
但不能测试别的模块中的私有函数
|
|
这章的内容很多,但是并不难,建议多敲几遍即可,千万不要只读,不写!读了!=会了