14.编写自动化测试(上)

一、如何编写测试

1.1 一些概念

Rust 中的测试函数体通常执行如下三种操作:

  1. 设置任何所需的数据或状态;
  2. 运动需要测试的代码;
  3. 断言其结果是我们所期望的;

Rust提供的专门用来编写测试的功能:

  • test属性;
  • 一些宏;
  • should_panic属性;

1.2 测试函数剖析

  • Rust中的测试就是一个在fn行之间加上#[test]属性标注的函数;
  • cargo test命令执行时,会运行被标记了#[test]的函数,并报告是否运行通过;

创建新的库项目

cargo new adder --lib
  • 源码只有src/lib.rs文件,其内容如下
pub fn add(left: usize, right: usize) -> usize {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}
  • fn函数之前的#[test]表明这是一个测试函数;
  • tests是一个普通的内部模块,要测试外部的中的add函数,因此要用use引入;
  • 函数体通过使用assert_eq!宏来断言2加2等于4,这是测试用例的典型格式;
  • 使用cargo test运行测试用例;
    在这里插入图片描述
  • 结果显示:1 passed; 0 failed;…… 说明所有测试都通过了;
  • tests::it_works中的it_works是测试函数的名字;
  • ignored表示忽略的测试用例,filtered out表示过滤需要运行的测试,measured是针对性能测试的(目前只能用于Rust开发版(nightly Rust,详阅https://doc.rust-lang.org/unstable-book/library-features/test.html);
  • Doc-tests adder 是所有文档测试的结果;

再加一个失败的测试项

#[cfg(test)]
mod tests {
	……
	
    #[test]
    fn another() {
        panic!("Make this test fail");
    }
}

运行时会提示it_works运行成功而another运行失败
在这里插入图片描述

1.3 使用assert!宏检查结果

  • assert!宏由标准库提供,在希望确保测试中一些条件为true时非常有用;
  • 需要向assert!宏提供一个求值为布尔值的参数;
  • 如果值是true,则什么也不做,同时测试通过;
  • 如果值是false,assert!宏调用panic!宏,同时测试失败;
  • assert!最后附上错误提示字符串,以便更好定位 (可选)

为前面写的Rectangle结构体和一个can_hold添加一个测试用例,看看8*7的实例能否放下5*1的实例;

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn larger_can_hold_smaller() {
        let larger = Rectangle { width: 8, height: 7 };
        let smaller = Rectangle { width: 5, height: 1 };

        assert!(larger.can_hold(&smaller), "运行失败!");
    }
}

测试结果为通过
在这里插入图片描述

1.4 使用assert_eq!和assert_ne!宏来测试相等

  • 标准库提供assert_eq!assert_ne!宏比较相等或不相等;
  • 当断言失败时也会打印出这两个值具体是什么,以便于观察测试为什么失败;
  • 依然可以自定义断言失败时候的输出信息;
  • 宏参数中的左右两个值的顺序并不重要;

1) assert_eq!

fn add_two(a: i32) -> i32{
    a + 2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_adds_two() {
        let result = add_two(2);
        assert_eq!(4, result);
    }

    #[test]
    fn it_adds_two2() {
        let result = add_two(1);
        assert_eq!(4, result, "4 is not equal {}.", result);
    }
}

输出结果
在这里插入图片描述

  • it_add_two运行通过;
  • it_adds_two2运行失败,左值为4右值为3,因此不相等,断言失败;
  • 输出了我们自己定义的错误提示信息:4 is not equal 3

2) assert_ne!

  • assert_ne!宏在传递给它的两个值不相等时通过;
  • 在代码按预期运行,我们不确定值会是什么,不过能确定值绝对不会是什么的时候,这个宏最有用处;

TIPS:

  • 如果一个函数保证会以某种方式改变其输出,不过这种改变方式是由运行测试时是星期几来决定的,这时最好的断言可能就是函数的输出不等于其输入。
  • assert_eq! 和 assert_ne! 宏在底层分别使用了 == 和 !=。
  • 当断言失败时,这些宏会使用调试格式打印出其参数,这意味着被比较的值必需实现了PartialEq 和 Debug trait
  • 对于自定义的结构体和枚举,需要实现PartialEq才能断言他们的值是否相等。
  • 需要实现Debug才能在断言失败时打印他们的值。
  • 由于这两个 trait 都是派生 trait,因此通常可以直接在结构体或枚举上添加#[derive(PartialEq, Debug)]标注。
  • 更多关于这些和其他派生 trait 的详细信息请参阅 这里

1.5 使用 should_panic 检查 panic

  • 可以对测试程序添加#[should_panic]属性;
  • 即使满足条件也不会触发程序中的panic!宏;
fn add_two(a: i32) -> i32{

    if a < 0 {
        panic!("Par a must larger 0 but a is {}", a);
    }
    a + 2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic]
    fn it_adds() {
        let result = add_two(-5);
        assert_eq!(4, result);
    }
}
  • 当add_two传入的值小于0时会触发panic!宏;
  • 但是给it_adds额外添加一个#[should_panic]属性时依然会通过测试;

缺点

  • 添加should_panic属性之后的测试结果只是告诉我们代码产生了 panic;
  • should_panic在一些不是我们期望的原因而导致panic时也会通过 ;
  • 可以添加expected参数来关注我们所期待导致的panic;
fn add_two(a: i32) -> i32{

    if a < 0 {
        panic!("Par a must larger than 0 but a is {}", a);
    }else if a > 100{
        panic!("Par a must less than 100 but a is {}", a);
    }
    a + 2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic(expected = "Par a must larger than 0 but a is")]
    fn it_adds() {
        let result = add_two(101);
        assert_eq!(4, result);
    }
}
  • 为it_adds添加的[should_panic(expected = "Par a must larger than 0 but a is")]属性代表只有当panic!宏提示的字符串不包含Par a must larger than 0 but a is时会出错。
  • 传给add_two函数的值是101,因此走的是else if分支,panic!宏提示的错误是Par a must less than 100 but a is,这不包含expected中的子串,因此会报错。

在这里插入图片描述

二、将 Result<T, E> 用于测试

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() -> Result<(), String> {
        if 2 + 2 == 4 {
            Ok(())
        } else {
            Err(String::from("two plus two does not equal four"))
        }
    }
}
  • it_works 函数的返回值类型为Result<(), String>
  • 测试通过时返回 Ok(()),在测试失败时返回带有 String 的 Err;
  • 这样编写测试来返回 Result<T, E> 就可以在函数体中使用问号运算符,如此可以方便地编写测试,如果其中的任何操作返回 Err 值,则测试将失败;
  • 不能使用#[should_panic],要断言操作返回 Err 值,需要使用assert!(value.is_err())
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贱贱的剑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值