首页 > 其他分享 >10.编写自动化测试

10.编写自动化测试

时间:2022-12-28 17:13:42浏览次数:35  
标签:10 value assert 测试 自动化 test 编写 panic fn

一、如何编写测试

测试函数的函数体中一般包含3个部分:

  1. 准备所需的数据或状态;
  2. 调用需要测试的代码;
  3. 断言运行结果与我们所期望的一致;

1、测试函数的构成

在最简单的情形下,Rust中的测试说就是一个标注有test属性的函数。属性(attribute)是一种用于修饰Rust代码的元数据。
只需要将#[test]添加到关键字fn的上一行便可将函数转变为测试函数。
当测试函数编写完成后,可以使用cargo test命令来运行测试。这个命令会构建并执行一个用于测试的可执行文件,该文件在执行的过程中会逐一调用所有标注test属性的函数,并生成统计测试运行成功或失败的相关报告。
当我们使用cargo新建一个库项目时,它会自动为我们创建一个带有测试函数的测试模块。

cargo new adder --lib //新建adder的库项目

在这个库项目下会自动生成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);
    }
}

先忽略最上面八行代码。#[test]标注:它将当前的函数标记为一个测试,并使该函数可以在测试运行的过程中被识别出来。

Cargo成功编译并运行了这段代码后,输出了running 1 test,表示当前正在执行1个测试,接下来显式的所生成的测试函数名称it_works,以及相应的测试结果。

  • test result: ok.:表示该结合中的所有测试均成功通过;
  • 1 passed:成功的测试总数;
  • 0 failed:失败的测试总数;
  • 0 ignored:忽略的测试总数;
  • 0 measured:测量性能的测试数量;
  • 0 filtered out:过滤运行的测试数量;
  • finished in 0.00s:完成时间;
    Doc-tests adder开头的部分表示文档测试的结果。
    我们通过panic!宏故意导致程序运行失败。因为在Rust中,一旦测试函数触发panic,该测试就会被视作执行失败。
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);
    }

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

再次运行cargo test运行测试,输出如下结果:

2、使用assert!宏检查结果

assert!宏由标准库提供,它可以确保测试中某些条件的值为trueassert!宏可以接收一个能够被计算为布尔类型的值作为参数。当这个值为true时,assert!宏什么都不用做并正常通过测试,而当值为false时,assert!宏就会调用panic!宏,进而导致测试失败。

3、使用assert_eq!宏和assert_ne!宏判断相等性

assert_eq!assert_ne!这两个宏分别用于比较并断言两个参数相等或不相等。在断言失败时,他们还可以自动打印出两个参数的值,从而方便我们观察测试失败的原因;相反,使用assert!宏则只能得知==判断表达式失败的事实,而无法知晓被用于比较的值。
从本质上看,assert_eq!assert_ne!宏分别使用了==!=运算符来进行判断,并在断言失败时使用调试输出格式({:?})将参数值打印出来,这意味着它们的参数必须同时实现PartialEqDebug这两个trait。
当我们自定义的结构体和枚举时,需要自动实现PartialEq来判断两个值是否相等,并实现Debug来保证值可以在断言失败时被打印出来。由于这两个trait都是可派生trait,所以它们一般可以通过自定义的结构体或枚举定义的上方添加#[derive(PartialEq, Debug)]标注来自动实现这两个traiit。

4、添加自定义的错误提示信息

实际上,任何在assert!assert_eq!assert_ne!宏的必要参数之后出现的参数都会一起被传递给format!宏。因此,你甚至可以将一个包含{}占位符的格式化字符串及相对应的填充值作为参数一起传递给这些宏。自定义的错误提示信息可以很方便地记录当前断言的含义。

pub fn greeting(name: &str) -> String {
    String::from("Hello!")
}

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

    #[test]
   fn greeting_contains_name() {
    let result = greeting("Carol");
    assert!(
        result.contains("Carol"),
        "Greeting did not contain name, value was `{}`", result
    )
    
   }
}


这次的测试输出了中包含了实际的值,它能帮助我们观察程序真正发生的行为,并循序定位与预期产生差异的地方。

5、使用should_panic检查panic

should_panic属性:这个属性的测试函数会在代码发生panic时顺利通过,而在代码不发生panic时执行失败。

pub struct Guess {
    value: u32,
}

impl Guess {
    pub fn new(value: u32) -> Guess {
        if value < 1 || value > 100 {
            panic!("Guess value must be between 1 and 100, got {}.", value);
        }  
        Guess {
            value
        }
    }
}

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

    #[test]
    #[should_panic]
    fn greater_than_100() {
        Guess::new(200);
    }
}

再将#[should_panic]属性放在#[test]属性之后、对应的测试函数之前。测试顺利通过。

我们在代码中引入bug,删除new函数中值大于100时发生panic的判断条件:

impl Guess {
    pub fn new(value: u32) -> Guess {
        if value < 1 {
            panic!("Guess value must be between 1 and 100, got {}.", value);
        }  
        Guess {
            value
        }
    }
}

再次运行测试,则会输出测试失败的结果:

为了让should_panic测试更加精确一些,我们可以在should_panic属性中添加可选参数expected,它会检查panic发生时输出的错误提示新是否包含了指定文字。

pub struct Guess {
    value: i32,
}

// --snip--
impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 {
            panic!(
                "Guess value must be greater than or equal to 1, got {}.",
                value
            );
        } else if value > 100 {
            panic!(
                "Guess value must be less than or equal to 100, got {}.",
                value
            );
        }

        Guess { value }
    }
}

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

    #[test]
    #[should_panic(expected = "Guess value must be less than or equal to 100")]
    fn greater_than_100() {
        Guess::new(200);
    }
}

因为Guess::new函数在发生panic的输出消息中包含了should_panic属性的expected参数指定的文本,所以该示例中的测试会顺利通过。一般来说,expected参数中的内容取决于panic信息是明确的还是易变,也取决于测试本身需要准确到何种程度。

6、使用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>类型的值,在函数体中,我么不再使用assert_eq!宏,而是在测试通过时返回Ok(()),在失败时返回一个带有String的Err值。不要在使用Result<T, E>编写的测试上标注#[should_panic]。在测试运行失败时,我们迎丹直接返回一个Err值。

二、控制测试的运行方式

1、并行或串行地进行测试

当你尝试运行多个测试时,Rust会默认使用多线程来并行执行它们。如果你不想并行运行测试,或希望精确地控制测试时所启动地线程数量,那么可通过给测试二进制文件传入--test=threads标记及期望地具体线程数量来控制这一行为。

cargo test -- --test-threads=1

2、显式函数输出

默认情况下,Rust的测试库会在测试通过是捕获所有被打印值标准输出中的消息。例如,我们在测试中调用了println!,但hi要测试顺利通过,它所打印的内容就无法显式在 终端上。
如果你希望在测试通过时也将值打印出来,那么可以传入一个nocapture标记来禁用输出截获功能:

cargo test -- --nocapture

3、只运行部分特定名称的测试

执行全部的测试用例有时比较耗费时间,我们可以通过cargo test中传递测试名称来指定需要运行的测试。

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

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

    #[test]
    fn add_two_and_two() {
        assert_eq!(4, add_two(2));
    }

    #[test]
    fn add_three_and_two() {
        assert_eq!(5, add_two(3));
    }

    #[test]
    fn one_hundred() {
        assert_eq!(102, add_two(100));
    }
}

运行单个测试:给cargo test传递一个测试函数的名称来单独运行测试。

cargo test one_hundred

只有名为one_hundred的测试得到了运行,因为其余两个测试的名称无法匹配我们传入的参数。同时,测试输出还通过摘要信息一行中的2 filtered out表明部分测试被过滤了。

需要注意的是,我们不能指定多个参数来运行多个测试:只有传递给cargo test的第一个参数才会生效。
通过过滤名称来运行多个测试:

cargo test add

这个命令运行了所有名称中带有add的测试,并将名为one_hundred的测试过滤。

4、通过显式指定来忽略某些测试

除了手动将想要运行的测试列举出来,你还可以使用ignore属性来标记耗时的测试,并将这些测试排除在正常的测试运行之外。

#[test]
fn it_works() {
    assert_eq!(2 + 2, 4);
}

#[test]
#[ignore]
fn expensive_test() {
    // 需要运行一个小时的代码
}


expensive test函数被放到了ignored类别下,我们可以使用cargp test -- --ignored来单独运行这些被忽略的测试。

通过控制测试的运行,我们可以保证每次执行cargo test都能迅速得到结果。

三、测试的组织结构

Rust社区主要从以下两种分类来讨论测试:单元测试(unit test)和集成测试(integration test)。单元测试小而专注,每次只单独测试一个模块或私有结构;而集成测试完全位于代码库之外,和正常从外部嗲用代码库一样使用外部代码,只能访问公共结构,并且在一次测试中可能会联用多个模块。

标签:10,value,assert,测试,自动化,test,编写,panic,fn
From: https://www.cnblogs.com/ColoFly/p/17010555.html

相关文章

  • P1036 [NOIP2002 普及组] 选数(DFS + 不降原则)
    P1036[NOIP2002普及组]选数题意​ 在n个数里选k个数,有多少中选法,使得选出来的数的和为素数。不能重复选。思路​ n很小,直接爆搜,但是如果不使用不降原则的话,就......
  • 二、PO模型之注册界面组装成完整的自动化case-5
    base层:基础层。获取配置文件中的元素值。page层:页面层。页面就是元素。将读取页面信息放在page中,page给handle提供页面元素。handle层:定义方法,调用page层。即处理page。......
  • win10优化
    文章目录​​1.设置cpu处理器个数为最大​​​​2.设置电源方案为卓越性能​​​​3.设置网速(Qos)​​​​4.禁用win10的某些服务​​​​5.关闭系统防火墙​​​​6......
  • Win10系统如何修改hosts文件
    Win10系统如何修改hosts文件?win10系统在修改hosts文件的时候都遇到了权限不足,而不会像​​XP​​​系统和​​Win7​​系统可以直接修改hosts文件。下面就让我们来看看Win......
  • APP自动化测试总结一:Appium连接参数与第一个脚本
    1fromappium.webdriverimportRemote234caps={5"platformName":"Android",6"appPackage":"com.lemon.lemonban",7"appActivity":......
  • 1024 我敢送,你敢参与吗?
    喜迎1024大家好,一年一度的1024程序员节,又双叒叕来啦!在这喜大普奔的日子里,祝愿所有的程序员猿媛节日快乐!1024咱们能不能不不不加班~好啦,无需太多前奏,直奔主题。为了感谢......
  • selenium自动化测试-元素定位及元素操作
    selenium元素定位​ selenium定位元素的方式有8种。fild_element(by,value):by表示使用的定位方式,定位方式可以参见By类。value表示值,例如:根据id定位By.ID,value=id属性的......
  • 推荐 10 款 C++ 在线编译器
    1CodingGroundUrl:​https://www.tutorialspoint.com/compile_cpp_online.php​​CodingGround是Tutorialspoint(很强大的免费教程网站)的一个编码平台。它提供的编译器支......
  • 华普物联HP-AIOCAT-244电力配网自动化无线传输系统 CAT1/4G网络远程IO控制器RS485转C
    电网建设繁杂庞大,时间跨度大,各种新老设备共存,很多电网架构已经不适用当今社会发展的需求。现如今改善电能质量从源头上来说就是将电网改造与新电网建设结合起来。对于新建......
  • 修改内核源码绕过反调试检测(Android10)
    一、Android反调试     反调试在代码保护中扮演着非常重要的角色,虽然不能完全阻止攻击者,但是能加大攻击者的分析时间成本。目前绝大多数Androidapp都加固了,为了防......