简介
- 接上回书
- 测试的重要性不言而喻,可以看一些TDD的相关知识,不赘述。
- cargo + Rust 天生支持测试,这个特性还是非常好的,省得安装其他的测试组件了。
如何把函数变成测试函数
- 测试函数需要使用test属性(attribute)进行标注。
- 就是在函数上面加上
#[test]
,Rust就会把这个函数当成测试函数
运行测试
- 使用命令
cargo test
命令运行所有测试函数,Rust会构建一个Test Runner 可执行文件,它会运行标注了 #[test] 的函数,并报告其是否通过测试。 - 写一个独立的测试函数,cargo test 没问题:
#[test]
fn test_eq() {
assert_eq!(2+ 2 , 4)
}
- 标准一点可以写一个 #[cfg(test)] 模块
#[cfg(test)]
mod tests {
#[test]
fn test_eq() {
assert_eq!(2+ 2 , 4)
}
#[test]
fn test_eq2() {
assert_eq!(2+ 2 , 5)
}
}
- 测试失败可以使用 panic 函数,就表示失败。
- 每个测试运行在一个新线程里。
断言 assert!() 宏
-
assert!
宏来自标准库,用来确定某个状态是否为true,true表示通过,false就会调用panic!
测试失败。 - 用之前的代码举例,测试一下
Rectangle can_hold
- 新建文件
rectangle.rs
直接可以把相关的测试写到这个文件里,这是没有问题的,写文件里就不要加mod test
了没必要,举例如下:
// 定义一个矩形
pub struct Rectangle {
width: u32,
height: u32,
}
// 实现相关方法
impl Rectangle {
// 判断某个矩形是否可以放到当前矩形中
pub fn can_hold(&self, o: &Rectangle) -> bool {
if self.width >= o.width && self.height >= o.height {
true
} else {
false
}
}
}
// #[cfg(test)] // 如果放到模块文件中,那么就不推荐使用 mod test 了
// mod test {
// }
#[test]
fn can_hold() {
let a = Rectangle {
width: 10,
height: 20,
};
let b = Rectangle {
width: 5,
height: 20,
};
// 断言
assert!(a.can_hold(&b));
assert!(!b.can_hold(&a));
}
-
执行截图:
使用 assert_eq! 和 assert_ne! 测试
- 都来自标准库。
- 判断两个参数是否相等或者不等。
- 要求参数实现了PartialEq和Debug Traits
- 举例:
assert_eq!(4, 3);
assert_ne!(4, 3);
添加自定义的错误信息
- 可以向 assert!、assert_eq!、assert_ne! 添加自定义参数。
- 其实就是在后面加上字符串字面值。
用should_panic 检查恐慌
- 测试除了验证代码的返回值是否正确,还需验证代码是否如期处理发生错误的情况。
- 可验证代码在特定情况下是否发生panic
- 使用方式:
#[test] + #[should_panic]
#[test]
#[should_panic]
fn greater_than_100 () {
...
}
让 should_panic 更精准
- 很多种情况都可能包含
panic
,这是可以添加一个可选的 expected 参数:
#[test]
#[should_panic(expected = "Some word.")]
fn test_method() {
...
}
在测试中使用 Result
- 无需 panic,可以使用 Result
作为返回类型编写测试,如果返回 Ok 测试通过,返回 Err 测试失败。 - 不要在使用 Result
编写的测试上标注 #[should_panic] ,举例
#[test]
fn test_result ()->Result<(), String> {
if 2+1 == 4 {
Ok(())
} else {
Err(String::from("hello"))
}
}`
如何控制测试的运行方式
- cargo test 默认上并行运行测试,不显示所有输出,除非出现错误断言的时候,主要是为了方便读取。
- 如果不想并行运行测试,需要添加
--test-threads
- 例如:
cargo test -- --test-threads=1
- 如果想输出所有打印信息可以使用
--show-output
- 例如:
cargo test -- --show-output
按名称运行测试
- 选择运行的测试,将测试的名称(一个或多个),作为cargo test 的参数
- 例如:
#[test]
fn test_add_1 () {
assert!(2+2 == 4)
}
#[test]
fn test_add_2 () {
assert!(2+2 == 4)
}
#[test]
fn test_add_3 () {
assert!(2+2 == 4)
}
-
如果输入 cargo test
-
如果输入 cargo test add
-
如果输入 cargo test 1
所以就是 cargo test match_str 会进行模糊匹配
忽略某些测试
- 某些情况下,某些测试我们需要进行忽略,比如那些执行的非常慢的,我们不希望他们每次都运行。
#[test]
fn test_add_1 () {
assert!(2+2 == 4)
}
#[test]
fn test_add_2 () {
assert!(2+2 == 4)
}
#[test]
#[ignore]
fn test_add_3 () {
assert!(2+2 == 4)
}
- 执行
cargo test add
返回结果如下(注意黄线部分):
- 可以单独添加 --ignore 参数运行被忽略的测试。
- 命令行参数是:
cargo test -- --ignored
注意不要忘了中间的两个--
切记: -
返回结果如下:
测试组织形式
- Rust 对测试的分类
1、单元测试。
2、集成测试。
- 单元测试关注的小,且专注,一次对一个模块进行隔离测试,可以测试 private 的接口。
- 继承测试,在库的外部,和其他外部代码一样,但是只能测试public 的接口,可能在每个测试中使用多个模块。
单元测试,每个单元文件建立一个test 模块
- 单元测试的标准写法需要建立一个
tests
模块,并进行#[cfg(test)]
标注: - 上面的例子修改一下大致就是这个样子:
#[cfg(test)]
mod test {
#[test]
fn test_add_1() {
assert!(2 + 2 == 4)
}
#[test]
fn test_add_2() {
assert!(2 + 2 == 4)
}
#[test]
#[ignore]
fn test_add_3() {
assert!(2 + 2 == 4)
}
}
- 只有运行cargo test 才会编译和运行 #[cfg(test)] 。
集成测试
在Rust 里,集成测试完全位于被测试库的外部,目的是测试被测试库的多个部分是否能正确的一起工作。
集成测试的覆盖率很重要。
集成的是需要创建一个
tests
的目录。tests 目录下的每个测试文件都是单独的一个 carte ,需要将被测试库导入。
无需标注 #[cfg(test]) ,tests 目录被特殊对待,只要 cargo test 才会编译 tests 目录下的文件。
-
需要创建test目录,该目录和 src 目录平级即可。
为了让程序可以测试,首先,修改一下
rectangle.rs
,主要是新增 new 方法,让外部可以访问当(让 test 继承测试访问到),修改后的代码如下:
use std::sync::mpsc::Receiver;
// 定义一个矩形
pub struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
pub fn new(width:u32, height:u32) -> Rectangle {
Rectangle {
width,
height,
}
}
// 判断某个矩形是否可以放到当前矩形中
pub fn can_hold(&self, o: &Rectangle) -> bool {
if self.width >= o.width && self.height >= o.height {
true
} else {
false
}
}
}
- 然后修改 lib.rs 开放这个mod ,否则 tests 目录的测试代码可能达不到这个模块。
// 新增 rectangle 然后加上 pub 给他公开出去。
pub mod rectangle;
- 然后在 tests 目录下新增
demo_test.rs
随便写一写测试代码:
use hello::rectangle::Rectangle;
// }
#[test]
fn can_hold_integration_test() {
let a = Rectangle::new(10, 29);
let b = Rectangle::new(20, 30);
assert!(false == a.can_hold(&b));
assert!(true == b.can_hold(&a));
}
- 然后cargo test 试一下:
-
可以看到集成测试目录 test 被成功执行:
集成测试中的子模块
- tests 目录下的每个文件被编译成单独的cate,这些文件不共享行为,这与src 下的文件规则不同。
- 举例我们可以写一个 setup() 函数,并将其放到测试中的某个公用位置,比如
common/mod.rs
(注意 common 是一个目录的话里面必须要包含入口文件 mod.rs): -
mod.rs
:
pub fn setup() {}
-
之后修改一下大致如下图:
真对binary crate的继承测试
- 如果项目是 binary crate 只包含 src/main.rs 没有 src/lib.rs 是不能在tests目录下创建继承测试,因为没必要,如果项目比较大肯定是要拆成library crate 然后暴露 crate 的。
- binary crate 意味着独立运行。
结束
- 感谢阅读,下篇见。