RUST Test
Rust 程序设计语言 - Rust 程序设计语言 简体中文版 (kaisery.github.io)
Test Function
一个测试函数执行三个任务:
- 初始设置测试的数据和状态
- 执行需要测试的代码
- 判断代码执行结果是否与预期一致
定义一个测试函数时,需要在这个函数前用#[test]
注解,这样cargo test
执行时,就会运行这些测试函数,并汇报最终通过与否的结果。
简单测试例子
当创建一个rust的lib库工程时,一个测试模块会自动生成。
执行cargo new plus --lib
创建一个名称为plus的lib库。
默认生成的lib.rs
代码如下
1 | pub fn add(left: usize, right: usize) -> usize { |
与普通执行程序不同,这里执行cargo test
就会执行我们发的测试.
1 | running 2 tests |
输出结果说明有个一个测试被执行,结果为ok。总的测试结果也是Ok。通过cargo test
指定具体函数名字,可以控制执行匹配字串的测试用例,也可以控制过滤不执行哪些测试用例。measure用来性能测试,目前只在每日编译版本中支持。
rust可以编译程序api文档中的代码,Doc-tests
就是文档中的代码执行测试用例
使用断言assert!宏
当assert!
宏中的值为false时,会调用panic!
宏触发测试执行失败
assert!
用来简单判断一个值是否是true
assert_eq!
用来判断两个值是否相等,当不相等时,会打印出来两个值。 assert_ne!
用来判断两个值不相等。这两个宏使用传入参数的debug
格式化输出和使用==
和!=
进行比较,对于自定义的结构体或枚举,需要实现 PartialEq
和Debug
traits。由于这两个trait都是derivable 可获得的(编译器可以自动生成默认实现代码),所以可以在自定义的结构体前加上 #[derive(PartialEq, Debug)]
注解,就可以获得trait的默认实现。
添加自定义的失败信息
在assert!
、assert_eq!
、 assert_ne!
的比较结果的参数后还可以增加一一个 format!
宏格式化的字串来输出失败信息。
1 |
|
程序在执行失败时,附带其中的错误信息。
检查被测函数输出panic
除了检查被测函数有正确输出值,我们还要检查函数是否有正确处理错误异常,如果一个被测函数输出了panic,那么这个测试就通过。这时可以在测试函数上增加#[should_panic]
属性。并且还可以指定我们预期panic中输出的字串有一定有哪些信息。
1 | pub fn add(left: usize, right: usize) -> usize { |
最终会输出函数panic输出的信息中没有预期的字串
1 | thread 'tests::it_panic' panicked at src\lib.rs:3:9: |
使用 Result<T, E>
作为返回值
测试函数还可以使用 Result<T, E>
作为返回值,当测试通过时返回Ok,失败时返回Err。使用 Result<T, E>
作为测试函数的返回值时,不能再使用#[should_panic]
属性。
1 | pub fn add(left: usize, right: usize) -> usize { |
Test run
cargo test --
后面的选项是给cargot test
使用的,例如cargo test --hlep
是列出cargo test
的帮助信息
测试用例顺序执行
当执行多个测试时,默认这些测试是并发执行的,这样执行的更快。使用cargo test -- --test-threads=1
所有的测试都在一个线程中执行,不会因为并发导致互相影响结果
测试函数输出
当测试pass时,在测试函数以及被测函数中的println!()
都不会输出到标准输出,只有测试失败才会输出。
cargo test -- --show-output
可以在测试pass的时候,还能输出函数中的println!()
执行指定的测试函数
cargo test 测试函数名称
例如cargo test it_not_work
就只执行it_not_work
这个测试函数,其他的测试函数不执行。
cargo test 测试名称匹配字串
可以过滤执行多个测试函数,例如cargo test work
表示执行所有名称中有work
字串的测试函数。
忽略测试函数
在测试函数名称前加上#[ignore]
,就可以在默认执行cargo test
把它忽略不执行,这对于非常耗时的测试用例非常有用。
使用cargo test -- --ignored
来只执行标注了ignore的测试函数。
使用cargo test -- --include-ignored
可以执行所有的测试函数。
1 |
|
默认cargo test
执行时,会提示哪些函数被忽略了。
1 | running 3 tests |
Test Organization
单元测试用来测试每一个模块内部的接口包括私有的接口
集成测试是像外部应用使用库一样测试这个库的外部接口,它只测试公共接口,且同时可能测试多个模块。
单元测试
单元测试的测试代码可以和被测的模块代码在同一个文件中。通过在测试模块前加#[cfg(test)]
,告诉编译器只有执行cargo test
的时候才会编译这个测试模块,这样发布的程序中就不会包含测试的代码。
测试私有函数时对于C++应该很难实现,对于rust虽然测试模块是一个独立的作用域,通过测试模块中使用use super::*
,这样测试模块里面就可以使用它所在的父模块的所有成员。
1 | pub fn add_two(a: i32) -> i32 { |
集成测试
集成测试针库整体测试。
集成测试目录结构
和src
文件同级创建一个tests
目录,cargo会把这个tests
目录中的每一个rs
文件作为一个独立的crate。这个目录中的文件只有在执行cargo test
时候才会被编译执行。
1 | plus |
integration_test.rs中的内容如下,需要引用一下被测试的库。由于rust会自动把tests目录下的文件作为测试代码,所以不需要增加#[cfg(test)]
和测试模块,每一个文件都是一个独立的测试模块了。
1 | use plus; |
执行cargo test
后,会先执行库代码中的单元测试,再执行外层的集成测试。如果单元测试有用例执行失败,就不会执行外部的集成测试。
cargo test --test integration_test
表示只执行文件名称为integration_test
中的测试用例,库源代码中的单元测试也不会被执行。
如果工程只是一个二进制程序类型,且只有main.rs
,而没有lib.rs
,那么就不能使用tests
目录来创建集成测试,因为只有lib库类型的代码才会暴露模块接口给外部使用,而应用程序不会。一般一个项目会把逻辑和算法放在lib中,main中只是调用库的接口。
集成测试目录中使用公共子模块
一些多个测试模块都要使用的公共方法可以放在tests/common/mod.rs
文件中,这样编译器不会把mod.rs中的函数作为测试函数执行。
1 | ├── Cargo.lock |
例如tests/common/mod.rs中定义了一个公共准备测试的函数
1 | pub fn setup() { |
在测试文件中就可以使用common这个模块
1 | use plus; |
使用cargo test --test integration_test -- --show-output
只执行这个集成测试文件,并把测试函数中的输出也打印出来。第一个--test
是给cargo test
的参数,后面的参数相当于是给这个测试程序的参数。