【Rust】Rust学习 第十一章编写自动化测试

这篇具有很好参考价值的文章主要介绍了【Rust】Rust学习 第十一章编写自动化测试。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Rust 是一个相当注重正确性的编程语言,不过正确性是一个难以证明的复杂主题。Rust 的类型系统在此问题上下了很大的功夫,不过它不可能捕获所有种类的错误。为此,Rust 也在语言本身包含了编写软件测试的支持。

编写一个叫做 add_two 的将传递给它的值加二的函数。它的签名有一个整型参数并返回一个整型值。当实现和编译这个函数时,Rust 会进行所有目前我们已经见过的类型检查和借用检查,例如,这些检查会确保我们不会传递 String 或无效的引用给这个函数。Rust 所 不能 检查的是这个函数是否会准确的完成我们期望的工作:返回参数加二后的值,而不是比如说参数加 10 或减 50 的值!这也就是测试出场的地方。

可以编写测试断言,比如说,当传递 3 给 add_two 函数时,返回值是 5。无论何时对代码进行修改,都可以运行测试来确保任何现存的正确行为没有被改变。

11.1 编写测试

如何编写测试

Rust 中的测试函数是用来验证非测试代码是否按照期望的方式运行的。测试函数体通常执行如下三种操作:

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

测试函数剖析

作为最简单例子,Rust 中的测试就是一个带有 test 属性注解的函数。属性(attribute)是关于 Rust 代码片段的元数据;第五章中结构体中用到的 derive 属性就是一个例子。为了将一个函数变成测试函数,需要在 fn 行之前加上 #[test]当使用 cargo test 命令运行测试时,Rust 会构建一个测试执行程序用来调用标记了 test 属性的函数,并报告每一个测试是通过还是失败。

创建一个新的库项目 adder

$ cargo new adder --lib
     Created library `adder` project
$ cd adder

新建后的默认代码是,判断加法

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);
    }
}

使用

cargo test

结果

【Rust】Rust学习 第十一章编写自动化测试,Rust,rust,学习,开发语言,keep studying,后端

Cargo 编译并运行了测试。在 CompilingFinished 和 Running 这几行之后,可以看到 running 1 test 这一行。下一行显示了生成的测试函数的名称,它是 it_works,以及测试的运行结果,ok。接着可以看到全体测试运行结果的摘要:test result: ok. 意味着所有测试都通过了。1 passed; 0 failed 表示通过或失败的测试数量。

因为之前我们并没有将任何测试标记为忽略,所以摘要中会显示 0 ignored。我们也没有过滤需要运行的测试,所以摘要中会显示0 filtered out

0 measured 统计是针对性能测试的。性能测试(benchmark tests)在编写本书时,仍只能用于 Rust 开发版(nightly Rust)。

测试输出中的以 Doc-tests adder 开头的这一部分是所有文档测试的结果。我们现在并没有任何文档测试,不过 Rust 会编译任何在 API 文档中的代码示例。这个功能帮助我们使文档和代码保持同步!

改变测试的名称并看看这如何改变测试的输出。修改测名称

pub fn add(left: usize, right: usize) -> usize {
    left + right
}

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

    #[test]
    // 这里修改了测试名称
    fn exploration() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}

结果

【Rust】Rust学习 第十一章编写自动化测试,Rust,rust,学习,开发语言,keep studying,后端

让我们增加另一个测试,不过这一次是一个会失败的测试!当测试函数中出现 panic 时测试就失败了。每一个测试都在一个新线程中运行,当主线程发现测试线程异常了,就将对应测试标记为失败。第九章讲到了最简单的造成 panic 的方法:调用 panic! 宏。

pub fn add(left: usize, right: usize) -> usize {
    left + right
}

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

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

    // 新增错误测试
    #[test]
    fn another() {
        panic!("Make this test fail");
    }


}

结果

【Rust】Rust学习 第十一章编写自动化测试,Rust,rust,学习,开发语言,keep studying,后端

再次 cargo test 运行测试。它表明 exploration 测试通过了而 another 失败了

test tests::another 这一行是 FAILED 而不是 ok 了。在单独测试结果和摘要之间多了两个新的部分:第一个部分显示了测试失败的详细原因。在这个例子中,another 因为在src/lib.rs 的第 10 行 panicked at 'Make this test fail' 而失败。下一部分列出了所有失败的测试,这在有很多测试和很多失败测试的详细输出时很有帮助。

最后是摘要行:总体上讲,测试结果是 FAILED。有一个测试通过和一个测试失败。

使用assert!宏来检查结果

assert! 宏由标准库提供,在希望确保测试中一些条件为 true 时非常有用。需要向 assert! 宏提供一个求值为布尔值的参数。如果值是 trueassert! 什么也不做,同时测试会通过。如果值为 falseassert! 调用 panic! 宏,这会导致测试失败。assert! 宏帮助我们检查代码是否以期望的方式运行。


// 结构体
struct Rectangle {
    width: u32,
    height: u32,
}

// 结构体实现了can_hold方法
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));
    }
}

注意在 tests 模块中新增加了一行:use super::*;

我们将测试命名为 larger_can_hold_smaller,并创建所需的两个 Rectangle 实例。接着调用 assert! 宏并传递 larger.can_hold(&smaller) 调用的结果作为参数。这个表达式预期会返回 true,所以测试应该通过。

结果

【Rust】Rust学习 第十一章编写自动化测试,Rust,rust,学习,开发语言,keep studying,后端

再来增加另一个测试,这一回断言一个更小的矩形不能放下一个更大的矩形:

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

    #[test]
    fn larger_can_hold_smaller() {
        // --snip--
    }

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

        assert!(!smaller.can_hold(&larger));
    }
}

 也通过了

【Rust】Rust学习 第十一章编写自动化测试,Rust,rust,学习,开发语言,keep studying,后端

如果引入一个 bug 的话测试结果会发生什么。将 can_hold 方法中比较长度时本应使用大于号的地方改成小于号:

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

结果

【Rust】Rust学习 第十一章编写自动化测试,Rust,rust,学习,开发语言,keep studying,后端

我们的测试捕获了 bug!因为 larger.length 是 8 而 smaller.length 是 5,can_hold 中的长度比较现在因为 8 不小于 5 而返回 false

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

测试功能的一个常用方法是将需要测试代码的值与期望值做比较,并检查是否相等。可以通过向 assert! 宏传递一个使用 == 运算符的表达式来做到。不过这个操作实在是太常见了,以至于标准库提供了一对宏来更方便的处理这些操作 —— assert_eq! 和 assert_ne!。这两个宏分别比较两个值是相等还是不相等。当断言失败时他们也会打印出这两个值具体是什么,以便于观察测试 为什么 失败,而 assert! 只会打印出它从 == 表达式中得到了 false 值,而不是导致 false 的两个值。

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

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

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

传递给 assert_eq! 宏的第一个参数 4 ,等于调用 add_two(2) 的结果。测试中的这一行 test tests::it_adds_two ... ok 中 ok 表明测试通过!

在代码中引入一个 bug 来看看使用 assert_eq! 的测试失败是什么样的。

pub fn add_two(a: i32) -> i32 {
    a + 3      // 这里修改了
}

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

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

结果

【Rust】Rust学习 第十一章编写自动化测试,Rust,rust,学习,开发语言,keep studying,后端

测试捕获到了 bug!it_adds_two 测试失败,显示信息 assertion failed: `(left == right)` 并表明 left 是 4 而 right 是 5。这个信息有助于我们开始调试:它说 assert_eq! 的 left 参数是 4,而 right 参数,也就是 add_two(2) 的结果,是 5

需要注意的是,在一些语言和测试框架中,断言两个值相等的函数的参数叫做 expected 和 actual,而且指定参数的顺序是很关键的。然而在 Rust 中,他们则叫做 left 和 right,同时指定期望的值和被测试代码产生的值的顺序并不重要。这个测试中的断言也可以写成 assert_eq!(add_two(2), 4),这时失败信息会变成 assertion failed: `(left == right)` 其中 left 是 5 而 right 是 4

assert_ne! 宏在传递给它的两个值不相等时通过,而在相等时失败。

自定义失败信息

也可以向 assert!assert_eq! 和 assert_ne! 宏传递一个可选的失败信息参数,可以在测试失败时将自定义失败信息一同打印出来。任何在 assert! 的一个必需参数和 assert_eq! 和 assert_ne! 的两个必需参数之后指定的参数都会传递给 format! 宏,所以可以传递一个包含 {} 占位符的格式字符串和需要放入占位符的值。自定义信息有助于记录断言的意义;当测试失败时就能更好的理解代码出了什么问题。

例如,比如说有一个根据人名进行问候的函数,而我们希望测试将传递给函数的人名显示在输出中:

 

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

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

    #[test]
    fn greeting_contains_name() {
        let result = greeting("Carol");
        assert!(result.contains("Carol"));
    }
}

结果

【Rust】Rust学习 第十一章编写自动化测试,Rust,rust,学习,开发语言,keep studying,后端

这个程序的需求还没有被确定,因此问候文本开头的 Hello 文本很可能会改变。然而我们并不想在需求改变时不得不更新测试,所以相比检查 greeting 函数返回的确切值,我们将仅仅断言输出的文本中包含输入参数。

让我们通过将 greeting 改为不包含 name 来在代码中引入一个 bug 来测试失败时是怎样的:

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

结果

【Rust】Rust学习 第十一章编写自动化测试,Rust,rust,学习,开发语言,keep studying,后端

结果仅仅告诉了我们断言失败了和失败的行号。一个更有用的失败信息应该打印出 greeting 函数的值。让我们为测试函数增加一个自定义失败信息参数:带占位符的格式字符串,以及 greeting 函数的值:

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

结果

【Rust】Rust学习 第十一章编写自动化测试,Rust,rust,学习,开发语言,keep studying,后端

使用should_panic检查panic

除了检查代码是否返回期望的正确的值之外,检查代码是否按照期望处理错误也是很重要的。

可以通过对函数增加另一个属性 should_panic 来实现这些。这个属性在函数中的代码 panic 时会通过,而在其中的代码没有 panic 时失败。

pub struct Guess {
    value: i32,
}

impl Guess {
    pub fn new(value: i32) -> 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);
    }
}

结果

【Rust】Rust学习 第十一章编写自动化测试,Rust,rust,学习,开发语言,keep studying,后端看起来不错!现在在代码中引入 bug,移除 new 函数在值大于 100 时会 panic 的条件:

fn main() {}
pub struct Guess {
    value: i32,
}

// --snip--

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

        Guess {
            value
        }
    }
}

结果

【Rust】Rust学习 第十一章编写自动化测试,Rust,rust,学习,开发语言,keep studying,后端

这回并没有得到非常有用的信息,不过一旦我们观察测试函数,会发现它标注了 #[should_panic]。这个错误意味着代码中测试函数 Guess::new(200) 并没有产生 panic。

将Result<T,E>用于测试

也可以使用 Result<T, E> 编写测试!这里是第一个例子采用了 Result:


#![allow(unused_variables)]
fn main() {
#[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> 就可以在函数体中使用问号运算符,如此可以方便的编写任何运算符会返回 Err 成员的测试。

不能对这些使用 Result<T, E> 的测试使用 #[should_panic] 注解。相反应该在测试失败时直接返回 Err 值。

11.2 运行测试

11.3 测试的组织结构

用到再学

参考: 测试 - Rust 程序设计语言 简体中文版 (bootcss.com)文章来源地址https://www.toymoban.com/news/detail-647211.html

到了这里,关于【Rust】Rust学习 第十一章编写自动化测试的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 西瓜书读书笔记整理(十一) —— 第十一章 特征选择与稀疏学习

    11.1.1 基本概念 特征(feature) :在机器学习中, 特征 是指从数据中提取的用于描述样本的属性或信息。 相关特征(relevant feature) :对当前学习任务 有用 的属性称为 “ 相关特征 ”。 无关特征(inrelevant feature) :对当前学习任务 无用 的属性称为 “ 无关特征 ”。 冗余特

    2024年01月19日
    浏览(57)
  • Go学习第十一章——协程goroutine与管道channel

    1 协程goroutine 1.1 基本介绍 前置知识:“进程和线程”,“并发与并行” 协程的概念 协程(Coroutine)是一种用户态的轻量级线程,不同于操作系统线程,协程能够在单个线程中实现多任务并发,使用更少的系统资源。协程的运行由程序控制,不需要操作系统介入,因此协程之

    2024年02月08日
    浏览(40)
  • 第一章 Web自动化入门

    1、概念 由机器设备代替人工自动完成指定目标的过程 2、优点 减少人工劳动力 提高工作效率 产品规格统一标准 规格化(批量生产) 概念:让程序代替人工去验证系统功能的过程 解决-回归测试(重点) 解决-压力测试 解决-兼容性测试(浏览器、分辨率、操作系统) 提高测

    2024年02月07日
    浏览(48)
  • 【iOS自动化测试】第一章:方案调研

    目前Android端已完成了相应的框架搭建,并实际落地产出了,由于Android使用的是Unittest+HtmlTestRunner产出报告,需要增加新功能的话需要改动到底层框架,所以目前在负责的iOS端打算采用Pytest+Allure方式来进行,优点是更好的插件支持,报告也会更好看(装逼)点 PS:Android端自动

    2024年02月09日
    浏览(48)
  • python+pytest接口自动化(12)-自动化用例编写思路 (使用pytest编写一个测试脚本)

    经过之前的学习铺垫,我们尝试着利用pytest框架编写一条接口自动化测试用例,来厘清接口自动化用例编写的思路。 我们在百度搜索 天气查询 ,会出现如下图所示结果: 接下来,我们以该天气查询接口为例,编写接口测试用例脚本。 针对某个功能做接口测试,首先我们需

    2024年02月04日
    浏览(55)
  • 从0到1精通自动化测试,pytest自动化测试框架,使用自定义标记mark(十一)

    pytest可以支持自定义标记,自定义标记可以把一个web项目划分多个模块,然后指定模块名称执行 app自动化的时候,如果想android和ios公用一套代码时,也可以使用标记功能,标明哪些是ios用例,哪些是android的,运行代码时候指定mark名称运行就可以 1.以下用例,标记test_send_h

    2024年02月11日
    浏览(104)
  • Appium+python自动化(十一)- 元素定位- 下卷超详解)

    List故名思义就是一个列表,在python里面也有list这一个说法,如果你不是很理解什么是list,这里暂且理解为一个数组或者说一个集合。首先一个list是一个集合,那么他的个数也就成了不确定性,所以这里需要用复数,所以在我们定位时我们不能够接着用find_element_by_id等等定位

    2024年02月17日
    浏览(42)
  • ChatGPT辅助编写自动化测试

    大家好,我是洋子,ChatGPT已经越来越火爆,国内百度、阿里等互联网大厂也纷纷投入大模型研究,OpenAI官网中提供了许多ChatGPT应用场景,例如SQL翻译、语言翻译、代码解释等 作为一名QA,我更关注ChatGPT生成的自动化测试脚本质量如何,借助ChatGPT能否提升自动化测试编写效率

    2024年02月10日
    浏览(41)
  • 自动化测试脚本编写(超详细)

    🍅 视频学习: 文末有免费的配套视频可观看 🍅  关注公众号【互联网杂货铺】,回复 1  ,免费获取软件测试全套资料,资料在手,涨薪更快 什么是自动化测试? 自动化测试是验证和验证软件是否满足所有用户需求,并使用自动化工具按预期运行。它检查在产品开发阶段

    2024年04月26日
    浏览(38)
  • 自动化用例编写思路 (使用pytest编写一个测试脚本)

    目录 一,明确测试对象 二,编写测试用例 构造请求数据 封装测试代码 断言设置 三,执行脚本获取测试结果 四,总结 经过之前的学习铺垫,我们尝试着利用pytest框架编写一条接口自动化测试用例,来厘清接口自动化用例编写的思路。 我们在百度搜索天气查询,会出现如下

    2024年02月16日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包