【Rust】Rust学习 第十一章编写自动化测试
Rust 是一个相当注重正确性的编程语言,不过正确性是一个难以证明的复杂主题。Rust 的类型系统在此问题上下了很大的功夫,不过它不可能捕获所有种类的错误。为此,Rust 也在语言本身包含了编写软件测试的支持。
编写一个叫做 add_two
的将传递给它的值加二的函数。它的签名有一个整型参数并返回一个整型值。当实现和编译这个函数时,Rust 会进行所有目前我们已经见过的类型检查和借用检查,例如,这些检查会确保我们不会传递 String
或无效的引用给这个函数。Rust 所 不能 检查的是这个函数是否会准确的完成我们期望的工作:返回参数加二后的值,而不是比如说参数加 10 或减 50 的值!这也就是测试出场的地方。
可以编写测试断言,比如说,当传递 3
给 add_two
函数时,返回值是 5
。无论何时对代码进行修改,都可以运行测试来确保任何现存的正确行为没有被改变。
11.1 编写测试
如何编写测试
Rust 中的测试函数是用来验证非测试代码是否按照期望的方式运行的。测试函数体通常执行如下三种操作:
- 设置任何所需的数据或状态
- 运行需要测试的代码
- 断言其结果是我们所期望的
测试函数剖析
作为最简单例子,Rust 中的测试就是一个带有 test
属性注解的函数。属性(attribute)是关于 Rust 代码片段的元数据;第五章中结构体中用到的 derive
属性就是一个例子。为了将一个函数变成测试函数,需要在 fn
行之前加上 #[test]
。当使用 cargo test
命令运行测试时,Rust 会构建一个测试执行程序用来调用标记了 test
属性的函数,并报告每一个测试是通过还是失败。
创建一个新的库项目 adder
:
$ cargo new adder --libCreated 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
结果
Cargo 编译并运行了测试。在 Compiling
、Finished
和 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);}
}
结果
让我们增加另一个测试,不过这一次是一个会失败的测试!当测试函数中出现 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");}}
结果
再次 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!
宏提供一个求值为布尔值的参数。如果值是 true
,assert!
什么也不做,同时测试会通过。如果值为 false
,assert!
调用 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
,所以测试应该通过。
结果
再来增加另一个测试,这一回断言一个更小的矩形不能放下一个更大的矩形:
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));}
}
也通过了
如果引入一个 bug 的话测试结果会发生什么。将 can_hold
方法中比较长度时本应使用大于号的地方改成小于号:
impl Rectangle {fn can_hold(&self, other: &Rectangle) -> bool {self.width < other.width && self.height > other.height}
}
结果
我们的测试捕获了 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));}
}
结果
测试捕获到了 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"));}
}
结果
这个程序的需求还没有被确定,因此问候文本开头的 Hello
文本很可能会改变。然而我们并不想在需求改变时不得不更新测试,所以相比检查 greeting
函数返回的确切值,我们将仅仅断言输出的文本中包含输入参数。
让我们通过将 greeting
改为不包含 name
来在代码中引入一个 bug 来测试失败时是怎样的:
pub fn greeting(name: &str) -> String {String::from("Hello!")
}
结果
结果仅仅告诉了我们断言失败了和失败的行号。一个更有用的失败信息应该打印出 greeting
函数的值。让我们为测试函数增加一个自定义失败信息参数:带占位符的格式字符串,以及 greeting
函数的值:
#[test]
fn greeting_contains_name() {let result = greeting("Carol");assert!(result.contains("Carol"),"Greeting did not contain name, value was `{}`", result);
}
结果
使用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);}
}
结果
看起来不错!现在在代码中引入 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}}
}
结果
这回并没有得到非常有用的信息,不过一旦我们观察测试函数,会发现它标注了 #[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)
相关文章:

【Rust】Rust学习 第十一章编写自动化测试
Rust 是一个相当注重正确性的编程语言,不过正确性是一个难以证明的复杂主题。Rust 的类型系统在此问题上下了很大的功夫,不过它不可能捕获所有种类的错误。为此,Rust 也在语言本身包含了编写软件测试的支持。 编写一个叫做 add_two 的将传递…...

关于使用pycharm遇到只能使用unittest方式运行,无法直接选择Run
相信大家可能都遇到过这个问题,使用pycharm直接运行脚本的时候,只能选择unittest的方式,能愁死个人 经过几次各种尝试无果之后,博主就放弃死磕了,原谅博主是个菜鸟 后来遇到这样的问题,往往也就直接使用cm…...

Docker+rancher部署SkyWalking8.5并应用在springboot服务中
1.Skywalking介绍 Skywalking是一个国产的开源框架,2015年有吴晟个人开源,2017年加入Apache孵化器,国人开源的产品,主要开发人员来自于华为,2019年4月17日Apache董事会批准SkyWalking成为顶级项目,支持Jav…...
代码随想录第45天 | 322. 零钱兑换、279. 完全平方数
322. 零钱兑换 动规五部曲分析如下: 确定dp数组以及下标的含义 dp[j]:凑足总额为j所需钱币的最少个数为dp[j] 确定递推公式 凑足总额为j - coins[i]的最少个数为dp[j - coins[i]],那么只需要加上一个钱币coins[i]即dp[j - coins[i]] 1就是…...
怎么加入Microsoft Cloud Partner Program?
目录 前言 加入Microsoft Cloud Partner Program 1、注册成为微软合作伙伴 2、完成合作伙伴资格要求...

LNMP简易搭建
目录 前言 一、拓扑图 二、NGINX配置 三、配置MySQL 四、配置php环境 五、部署应用 总结 前言 LNMP平台指的是将Linux、Nginx、MySQL和PHP(或者其他的编程语言,如Python、Perl等)集成在一起的一种Web服务器环境。它是一种常用的开发和部署网…...

CClink IE转Modbus TCP网关连接三菱FX5U PLC
捷米JM-CCLKIE-TCP 是自主研发的一款 CCLINK IE FIELD BASIC 从站功能的通讯网关。该产品主要功能是将各种 MODBUS-TCP 设备接入到 CCLINK IE FIELD BASIC 网络中。 捷米JM-CCLKIE-TCP网关连接到 CCLINK IE FIELD BASIC 总线中做为从站使用,连接到 MODBUS-TCP 总线…...

PyTorch 微调终极指南:第 1 部分 — 预训练模型及其配置
一、说明 如今,在训练深度学习模型时,通过在自己的数据上微调预训练模型来迁移学习已成为首选方法。通过微调这些模型,我们可以利用他们的专业知识并使其适应我们的特定任务,从而节省宝贵的时间和计算资源。本文分为四个部分&…...

GO学习之 微框架(Gin)
GO系列 1、GO学习之Hello World 2、GO学习之入门语法 3、GO学习之切片操作 4、GO学习之 Map 操作 5、GO学习之 结构体 操作 6、GO学习之 通道(Channel) 7、GO学习之 多线程(goroutine) 8、GO学习之 函数(Function) 9、GO学习之 接口(Interface) 10、GO学习之 网络通信(Net/Htt…...

C语言 字符指针
1、介绍 概念: 字符指针,就是字符类型的指针,同整型指针,指针指向的元素表示整型一样,字符指针指向的元素表示的是字符。 假设: char ch a;char * pc &ch; pc 就是字符指针变量,字符指…...
Springboot所有的依赖
<properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><!-- 声明springboot的版本号 -->…...

Flutter BottomSheet 三段式拖拽
BottomSheetBehavior 追踪 BottomSheet系统默认实现效果准备要实现的功能点:定义三段式状态:BottomSheetBehavoir阀值定义1. 未达到滚动阀值,恢复状态2. 达到滚动阀值,更新状态 前面倒是有讲过Android原生的BottomSheetBehavior&a…...

php后端实现调用高德地图进行POI搜索
对于当前位置或者选定省市位置进行查询 接口实现 /*** 查询地址* ApiTitle (查询地址)* ApiSummary (查询地址)* ApiMethod (POST)* ApiRoute (/api/demo/address)* ApiParams (name"dart", type"integer", requiredtrue, description"省…...

uniapp 实现滑动视图切换 顶部滚动导航栏
无论小程序的时候一般有这个功能,在页面处于首页时候,滑动视图,切换视图顶部滚动导航也跟着切换 1.想要实现这个功能就需要实现顶部导航栏,首先实现顶部滚导航栏 点击高亮颜色显示 模板代码 <scroll-view scroll-x"true" class"scroll-content" > …...
ArcGIS API for JavaScript 调用自定义地图模板总结
ArcGIS API for JavaScript 调用自定义地图模板总结 3.9版本4.24版本 3.9版本 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>Hello World</title><link rel"stylesheet" href&qu…...

QGraphicsView实现简易地图5『经纬网格』
前文链接:QGraphicsView实现简易地图4『局部加载-地图漫游』 由于GCJ02 Web 墨卡托投影 纬度并不随像素等分,且两极跨度较大,因此本次演示采用的经纬网等分逻辑为等分像素。同等像素跨度之间,两级纬度变化较小,越靠近赤…...

RestTemplate 请求转发异常 ERR_CONTENT_DECODING_FAILED 200 (OK)
#1 问题描述 在基于Spring Boot的项目中实现了请求转发(使用 RestTemplate 的 exchange 方法)的功能,忽然在前端报net::ERR_CONTENT_DECODING_FAILED 200 (OK)的错误,后端及上游系统日志均显示请求已完成。 #2 原因探寻 上述错…...
用python实现一个异或计算器
有这样一条需求:计算某个文件中的数组每一行元素的最后一个参数,异或输出。 因为元素比较多,十几行,通过人工去计算异或值非常困难。 而在线异或的计算器,也需要人为输入这些数值,每次计算一个最终结果需…...
Sketch打不开AI文件?转换方法在这里
1、对比设计软件 Sketch 与 AI 软件功能 Sketch 与 Illustrator 都是行业内优秀的矢量图形设计软件,各有千秋。Sketch 从 2010 年面世,专注 APP 界面设计,深受初学者与专业人士喜爱。Illustrator 拥有更悠久的历史,是处理复杂图标…...

小游戏扫雷实现教学(详解)
目录 【前言】 一、模块化程序设计(多文件编程)介绍 1.概述 2.传统编程的方式 3.模块化程序设计的方法 二、扫雷代码设计思路 三、扫雷代码设计 1.创建菜单函数 2.实现9x9扫雷 3.初始化棋盘 4.打印棋盘 5.随机布置雷的位置 6.排查雷的信息 7.回…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
Caliper 负载(Workload)详细解析
Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...
高防服务器价格高原因分析
高防服务器的价格较高,主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因: 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器,因此…...
React核心概念:State是什么?如何用useState管理组件自己的数据?
系列回顾: 在上一篇《React入门第一步》中,我们已经成功创建并运行了第一个React项目。我们学会了用Vite初始化项目,并修改了App.jsx组件,让页面显示出我们想要的文字。但是,那个页面是“死”的,它只是静态…...
【深尚想】TPS54618CQRTERQ1汽车级同步降压转换器电源芯片全面解析
1. 元器件定义与技术特点 TPS54618CQRTERQ1 是德州仪器(TI)推出的一款 汽车级同步降压转换器(DC-DC开关稳压器),属于高性能电源管理芯片。核心特性包括: 输入电压范围:2.95V–6V,输…...
算法250609 高精度
加法 #include<stdio.h> #include<iostream> #include<string.h> #include<math.h> #include<algorithm> using namespace std; char input1[205]; char input2[205]; int main(){while(scanf("%s%s",input1,input2)!EOF){int a[205]…...

C# WPF 左右布局实现学习笔记(1)
开发流程视频: https://www.youtube.com/watch?vCkHyDYeImjY&ab_channelC%23DesignPro Git源码: GitHub - CSharpDesignPro/Page-Navigation-using-MVVM: WPF - Page Navigation using MVVM 1. 新建工程 新建WPF应用(.NET Framework) 2.…...

【Qt】控件 QWidget
控件 QWidget 一. 控件概述二. QWidget 的核心属性可用状态:enabled几何:geometrywindows frame 窗口框架的影响 窗口标题:windowTitle窗口图标:windowIconqrc 机制 窗口不透明度:windowOpacity光标:cursor…...