【Rust】——编写自动化测试(一)
🎃个人专栏:
🐬 算法设计与分析:算法设计与分析_IT闫的博客-CSDN博客
🐳Java基础:Java基础_IT闫的博客-CSDN博客
🐋c语言:c语言_IT闫的博客-CSDN博客
🐟MySQL:数据结构_IT闫的博客-CSDN博客
🐠数据结构:数据结构_IT闫的博客-CSDN博客
💎C++:C++_IT闫的博客-CSDN博客
🥽C51单片机:C51单片机(STC89C516)_IT闫的博客-CSDN博客
💻基于HTML5的网页设计及应用:基于HTML5的网页设计及应用_IT闫的博客-CSDN博客
🥏python:python_IT闫的博客-CSDN博客
🐠离散数学:离散数学_IT闫的博客-CSDN博客
🥽Linux:Linux_Y小夜的博客-CSDN博客
🚝Rust:Rust_Y小夜的博客-CSDN博客
欢迎收看,希望对大家有用!
目录
🎯编写和运行测试
🎃测试(函数)
🎃解剖测试函数
🎯 断言(Assert)
🎃使用assert!宏检查测试结果
🎃使用assert_eq!和assert_ne!测试相等性
🎯自定义错误信息
🎯使用should_panic检查恐慌
🎃让should_panic更加精准
🎯在测试中使用Result,e>
🎯编写和运行测试
🎃测试(函数)
Rust 中的测试函数是用来验证非测试代码是否是按照期望的方式运行的。测试函数体通常执行如下三种操作:
- 设置任何所需的数据或状态
- 运行需要测试的代码
- 断言其结果是我们所期望的
🎃解剖测试函数
测试成功:
Rust 中的测试就是一个带有
test
属性注解的函数。属性(attribute)是关于 Rust 代码片段的元数据;第五章中结构体中用到的derive
属性就是一个例子。为了将一个函数变成测试函数,需要在fn
行之前加上#[test]
。当使用cargo test
命令运行测试时,Rust 会构建一个测试执行程序用来调用被标注的函数,并报告每一个测试是通过还是失败。每次使用 Cargo 新建一个库项目时,它会自动为我们生成一个测试模块和一个测试函数。这个模块提供了一个编写测试的模板,为此每次开始新项目时不必去查找测试函数的具体结构和语法了。因为这样当然你也可以额外增加任意多的测试函数以及测试模块。
实际编写测试代码之前,让我们先通过尝试那些自动生成的测试模版来探索测试是如何工作的。接着,我们会写一些真正的测试,调用我们编写的代码并断言它们的行为的正确性。
$ cargo new adder --libCreated library `adder` project $ cd adder
#[cfg(test)] mod tests {#[test]fn it_works() {let result = 2 + 2;assert_eq!(result, 4);} }
现在让我们暂时忽略
tests
模块和#[cfg(test)]
注解并只关注函数本身。注意fn
行之前的#[test]
:这个属性表明这是一个测试函数,这样测试执行者就知道将其作为测试处理。tests
模块中也可以有非测试的函数来帮助我们建立通用场景或进行常见操作,必须每次都标明哪些函数是测试。测试失败:
当测试函数中出现 panic 时测试就失败了。每一个测试都在一个新线程中运行,当主线程发现测试线程异常了,就将对应测试标记为失败。
#[cfg(test)] mod tests {#[test]fn exploration() {assert_eq!(2 + 2, 4);}#[test]fn another() {panic!("Make this test fail");} }
🎯 断言(Assert)
🎃使用assert!宏检查测试结果
assert!
宏由标准库提供,在希望确保测试中一些条件为true
时非常有用。需要向assert!
宏提供一个求值为布尔值的参数。如果值是true
,assert!
什么也不做,同时测试会通过。如果值为false
,assert!
调用panic!
宏,这会导致测试失败。assert!
宏帮助我们检查代码是否以期望的方式运行。#[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} }
🎃使用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!
的测试失败是什么样的。修改add_two
函数的实现使其加3
:pub fn add_two(a: i32) -> i32 {a + 3 }
$ cargo testCompiling adder v0.1.0 (file:///projects/adder)Finished test [unoptimized + debuginfo] target(s) in 0.61sRunning unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)running 1 test test tests::it_adds_two ... FAILEDfailures:---- tests::it_adds_two stdout ---- thread 'main' panicked at 'assertion failed: `(left == right)`left: `4`,right: `5`', src/lib.rs:11:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtracefailures:tests::it_adds_twotest result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00serror: test failed, to rerun pass `--lib`
测试捕获到了 bug!
it_adds_two
测试失败,错误信息告诉我们断言失败了,它告诉我们assertion failed: `(left == right)`
以及left
和right
的值是什么。这个错误信息有助于我们开始调试:它说assert_eq!
的left
参数是4
,而right
参数,也就是add_two(2)
的结果,是5
。可以想象当有很多测试在运行时这些信息是多么的有用。需要注意的是,在一些语言和测试框架中,断言两个值相等的函数的参数被称为
expected
和actual
,而且指定参数的顺序非常重要。然而在 Rust 中,它们则叫做left
和right
,同时指定期望的值和被测试代码产生的值的顺序并不重要。这个测试中的断言也可以写成assert_eq!(add_two(2), 4)
,这时失败信息仍同样是assertion failed: `(left == right)`
。
assert_ne!
宏在传递给它的两个值不相等时通过,而在相等时失败。在代码按预期运行,我们不确定值 会 是什么,不过能确定值绝对 不会 是什么的时候,这个宏最有用处。
assert_eq!
和assert_ne!
宏在底层分别使用了==
和!=
。当断言失败时,这些宏会使用调试格式打印出其参数,这意味着被比较的值必须实现了PartialEq
和Debug
trait。所有的基本类型和大部分标准库类型都实现了这些 trait。对于自定义的结构体和枚举,需要实现PartialEq
才能断言它们的值是否相等。需要实现Debug
才能在断言失败时打印它们的值。因为这两个 trait 都是派生 trait。
🎯自定义错误信息
也可以向
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检查恐慌
除了检查返回值之外,检查代码是否按照期望处理错误也是很重要的。
可以通过对函数增加另一个属性
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);} }
🎃让should_panic更加精准
然而
should_panic
测试结果可能会非常含糊不清。should_panic
甚至在一些不是我们期望的原因而导致 panic 时也会通过。为了使should_panic
测试结果更精确,我们可以给should_panic
属性增加一个可选的expected
参数。测试工具会确保错误信息中包含其提供的文本。// --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 = "less than or equal to 100")]fn greater_than_100() {Guess::new(200);} }
这个测试会通过,因为
should_panic
属性中expected
参数提供的值是Guess::new
函数 panic 信息的子串。我们可以指定期望的整个 panic 信息,在这个例子中是Guess value must be less than or equal to 100, got 200.
。expected
信息的选择取决于 panic 信息有多独特或动态,和你希望测试有多准确。在这个例子中,错误信息的子字符串足以确保函数在else if value > 100
的情况下运行。
🎯在测试中使用Result<T,E>
目前为止,我们编写的测试在失败时都会 panic。我们也可以使用
Result<T, E>
编写测试!这是一个延伸自示例 11-1 的测试,使用Result<T, E>
重写,并在失败时返回Err
而非 panic:#[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
成员,不要使用对Result<T, E>
值使用问号表达式(?
)。而是使用assert!(value.is_err())
。
相关文章:
【Rust】——编写自动化测试(一)
🎃个人专栏: 🐬 算法设计与分析:算法设计与分析_IT闫的博客-CSDN博客 🐳Java基础:Java基础_IT闫的博客-CSDN博客 🐋c语言:c语言_IT闫的博客-CSDN博客 🐟MySQL:…...

第十二章 微服务核心(一)
一、Spring Boot 1.1 SpringBoot 构建方式 1.1.1 通过官网自动生成 进入官网:https://spring.io/,点击 Projects --> Spring Framework; 拖动滚动条到中间位置,点击 Spring Initializr 或者直接通过 https://start.spring…...

MySQL索引18连问,谁能顶住
前言 过完这个节,就要进入金银季,准备了 18 道 MySQL 索引题,一定用得上。 作者:感谢每一个支持: github 1. 索引是什么 索引是一种数据结构,用来帮助提升查询和检索数据速度。可以理解为一本书的目录&…...

[flink 实时流基础系列]揭开flink的什么面纱基础一
Apache Flink 是一个框架和分布式处理引擎,用于在无边界和有边界数据流上进行有状态的计算。Flink 能在所有常见集群环境中运行,并能以内存速度和任意规模进行计算。 文章目录 0. 处理无界和有界数据无界流有界流 1. Flink程序和数据流图2. 为什么一定要…...

开放平台 - 互动玩法演进之路
本期作者 1. 背景 随着直播业务和用户规模日益壮大,如何丰富直播间内容、增强直播间内用户互动效果,提升营收数据变得更加关键。为此,直播互动玩法应运而生。通过弹幕、礼物、点赞、大航海等方式,用户可以参与主播的直播内容。B站…...

Linux之进程控制进程终止进程等待进程的程序替换替换函数实现简易shell
文章目录 一、进程创建1.1 fork的使用 二、进程终止2.1 终止是在做什么?2.2 终止的3种情况&&退出码的理解2.3 进程常见退出方法 三、进程等待3.1 为什么要进行进程等待?3.2 取子进程退出信息status3.3 宏WIFEXITED和WEXITSTATUS(获取…...

RegSeg 学习笔记(待完善)
论文阅读 解决的问题 引用别的论文的内容 可以用 controlf 寻找想要的内容 PPM 空间金字塔池化改进 SPP / SPPF / SimSPPF / ASPP / RFB / SPPCSPC / SPPFCSPC / SPPELAN  ASPP STDC:short-term dense concatenate module 和 DDRNet SE-ResNeXt …...
Qt中常用宏定义
Qt中常用宏定义 一、Q_DECLARE_PRIVATE(Class)二、Q_DECLARE_PRIVATE_D(Dptr, Class)三、Q_DECLARE_PUBLIC(Class)四、Q_D(Class) 和 Q_Q(Class) 一、Q_DECLARE_PRIVATE(Class) #define Q_DECLARE_PRIVATE(Class) inline Class##Private* d_func() { # 此处的 d_ptr 是属于QOb…...

【计算机网络】第 9 问:四种信道划分介质访问控制?
目录 正文什么是信道划分介质访问控制?什么是多路复用技术?四种信道划分介质访问控制1. 频分多路复用 FDM2. 时分多路复用 TDM3. 波分多路复用 WDM4. 码分多路复用 CDM 正文 什么是信道划分介质访问控制? 信道划分介质访问控制(…...

Rust编程(五)终章:查漏补缺
闭包 & 迭代器 闭包(Closure)通常是指词法闭包,是一个持有外部环境变量的函数。外部环境是指闭包定义时所在的词法作用域。外部环境变量,在函数式编程范式中也被称为自由变量,是指并不是在闭包内定义的变量。将自…...

LLM漫谈(五)| 从q star视角解密OpenAI 2027年实现AGI计划
最近,网上疯传OpenAI2027年关于AGI的计划。在本文,我们将针对部分细节以第一人称进行分享。 摘要:OpenAI于2022年8月开始训练一个125万亿参数的多模态模型。第一个阶段是Arrakis,也叫Q*,该模型于2023年12月完成训练&…...
【echart】数据可视化+vue+vite遇到问题
1、vue3使用echars图表报错:"Initialize failed:invalid dom" 原因是因为:Dom没有完成加载时,echarts.init() 就已经开始执行了,获取不到Dom,无法进行操作 解决:加个延时 onMounted(async () …...
mac m1安装和使用nvm的问题
mac m1安装和使用nvm的问题 使用nvm管理多版本node 每个项目可能用的node版本不同,所以需要多个版本node来回切换 但是最近遇到安装v14.19.0时一直安装失败的问题。 首先说明一下,用的电脑是mac M1芯片 Downloading and installing node v14.19.0... …...
git泄露
git泄露 CTFHub技能树-Web-信息泄露-备份文件下载 当前大量开发人员使用git进行版本控制,对站点自动部署。如果配置不当,可能会将.git文件夹直接部署到线上环境。这就引起了git泄露漏洞。 工具GitHack使用:python2 GitHack.py URL地址/.git/ git命令…...

Java项目:78 springboot学生宿舍管理系统的设计与开发
作者主页:源码空间codegym 简介:Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 系统的角色:管理员、宿管、学生 管理员管理宿管员,管理学生,修改密码,维护个人信息。 宿管员…...
ArcGis Pro Python工具箱教程 03 工具箱中工具自定义
ArcGis Pro Python工具箱教程 03 工具箱中工具自定义 用于定义工作工具类的方法 工具方法必选或可选描述__ init __必需项right-aligned 初始化工具类。getParameterInfo可选定义工具的参数。isLicensed可选返回工具是否获得执行许可。updateParameters可选在用户每次在工具对…...

【C++初阶】之类和对象(中)
【C初阶】之类和对象(中) ✍ 类的六个默认成员函数✍ 构造函数🏄 为什么需要构造函数🏄 默认构造函数🏄 为什么编译器能自动调用默认构造函数🏄 自己写的构造函数🏄 构造函数的特性 ✍ 拷贝构造…...

Vue2(十一):脚手架配置代理、github案例、插槽
一、脚手架配置代理 1.回顾常用的ajax发送方式: (1)xhr 比较麻烦,不常用 (2)jQuery 核心是封装dom操作,所以也不常用 (3)axios 优势:体积小、是promis…...

在宝塔面板中,为自己的云服务器安装SSL证书,为所搭建的网站启用https(主要部分攻略)
前提条件 My HTTP website is running Nginx on Debian 10(或者11) 时间:2024-3-28 16:25:52 你的网站部署在Debain 10(或者11)的 Nginx上 安装单域名证书(默认)(非泛域名…...

学习JavaEE的日子 Day32 线程池
Day32 线程池 1.引入 一个线程完成一项任务所需时间为: 创建线程时间 - Time1线程中执行任务的时间 - Time2销毁线程时间 - Time3 2.为什么需要线程池(重要) 线程池技术正是关注如何缩短或调整Time1和Time3的时间,从而提高程序的性能。项目中可以把Time…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...

ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...

Spring AOP代理对象生成原理
代理对象生成的关键类是【AnnotationAwareAspectJAutoProxyCreator】,这个类继承了【BeanPostProcessor】是一个后置处理器 在bean对象生命周期中初始化时执行【org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization】方法时…...
文件上传漏洞防御全攻略
要全面防范文件上传漏洞,需构建多层防御体系,结合技术验证、存储隔离与权限控制: 🔒 一、基础防护层 前端校验(仅辅助) 通过JavaScript限制文件后缀名(白名单)和大小,提…...
2025.6.9总结(利与弊)
凡事都有两面性。在大厂上班也不例外。今天找开发定位问题,从一个接口人不断溯源到另一个 接口人。有时候,不知道是谁的责任填。将工作内容分的很细,每个人负责其中的一小块。我清楚的意识到,自己就是个可以随时替换的螺丝钉&…...