Rust单元测试、集成测试

单元测试、集成测试

在了解了如何在 Rust 中写测试用例后,本章节我们将学习如何实现单元测试、集成测试,其实它们用到的技术还是上一章节中的测试技术,只不过对如何组织测试代码提出了新的要求。

单元测试

单元测试目标是测试某一个代码单元(一般都是函数),验证该单元是否能按照预期进行工作,例如测试一个 add 函数,验证当给予两个输入时,最终返回的和是否符合预期。

在 Rust 中,单元测试的惯例是将测试代码的模块跟待测试的正常代码放入同一个文件中,例如 src/lib.rs 文件中有如下代码:

pub fn add_two(a: i32) -> i32 {a + 2
}#[cfg(test)]
mod tests {use super::*;#[test]fn it_works() {assert_eq!(add_two(2), 4);}
}

add_two 是我们的项目代码,为了对它进行测试,我们在同一个文件中编写了测试模块 tests,并使用 #[cfg(test)] 进行了标注。

条件编译 #[cfg(test)]

上面代码中的 #[cfg(test)] 标注可以告诉 Rust 只有在 cargo test 时才编译和运行模块 tests,其它时候当这段代码是空气即可,例如在 cargo build 时。这么做有几个好处:

  • 节省构建代码时的编译时间
  • 减小编译出的可执行文件的体积

其实集成测试就不需要这个标注,因为它们被放入单独的目录文件中,而单元测试是跟正常的逻辑代码在同一个文件,因此必须对其进行特殊的标注,以便 Rust 可以识别。

#[cfg(test)] 中,cfg 是配置 configuration 的缩写,它告诉 Rust :当 test 配置项存在时,才运行下面的代码,而 cargo test 在运行时,就会将 test 这个配置项传入进来,因此后面的 tests 模块会被包含进来。

大家看出来了吗?这是典型的条件编译,Cargo 会根据指定的配置来选择是否编译指定的代码,事实上关于条件编译 Rust 能做的不仅仅是这些,在 Cargo 专题中我们会进行更为详细的介绍。

测试私有函数

关于私有函数能否被直接测试,编程社区里一直争论不休,甚至于部分语言可能都不支持对私有函数进行测试或者难以测试。无论你的立场如何,反正 Rust 是支持对私有函数进行测试的:

pub fn add_two(a: i32) -> i32 {internal_adder(a, 2)
}fn internal_adder(a: i32, b: i32) -> i32 {a + b
}#[cfg(test)]
mod tests {use super::*;#[test]fn internal() {assert_eq!(4, internal_adder(2, 2));}
}

internal_adder 并没有使用 pub 进行声明,因此它是一个私有函数。根据我们之前学过的内容,tests 作为另一个模块,是绝对无法对它进行调用的,因为它们根本不在同一个模块中!

但是在上述代码中,我们使用 use super::*;tests 的父模块中的所有内容引入到当前作用域中,这样就可以非常简单的实现对私有函数的测试。

集成测试

与单元测试的同吃同住不同,集成测试的代码是在一个单独的目录下的。由于它们使用跟其它模块一样的方式去调用你想要测试的代码,因此只能调用通过 pub 定义的 API,这一点与单元测试有很大的不同。

如果说单元测试是对代码单元进行测试,那集成测试则是对某一个功能或者接口进行测试,因此单元测试的通过,并不意味着集成测试就能通过:局部上反映不出的问题,在全局上很可能会暴露出来。

tests 目录

一个标准的 Rust 项目,在它的根目录下会有一个 tests 目录,大名鼎鼎的 ripgrep 也不能免俗。

没错,该目录就是用来存放集成测试的,Cargo 会自动来此目录下寻找集成测试文件。我们可以在该目录下创建任何文件,Cargo 会对每个文件都进行自动编译,但友情提示下,最好按照合适的逻辑来组织你的测试代码。

首先来创建一个集成测试文件 tests/integration_test.rs ,注意,tests 目录一般来说需要手动创建,该目录在项目的根目录下,跟 src 目录同级。然后在文件中填入如下测试代码:

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

这段测试代码是对之前私有函数中的示例进行测试,该示例代码在 src/lib.rs 中。

首先与单元测试有所不同,我们并没有创建测试模块。其次,tests 目录下的每个文件都是一个单独的包,我们需要将待测试的包引入到当前包的作用域后: use adder,才能进行测试 。大家应该还记得包和模块章节中讲过的内容吧?在创建项目后,src/lib.rs 自动创建一个与项目同名的 lib 类型的包,由于我们的项目名是 adder,因此包名也是 adder

因为 tests 目录本身就说明了它的特殊用途,因此我们无需再使用 #[cfg(test)] 来取悦 Cargo。后者会在运行 cargo test 时,对 tests 目录中的每个文件都进行编译运行。

$ cargo testRunning unittests (target/debug/deps/adder-8a400aa2b5212836)running 1 test
test tests::it_works ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sRunning tests/integration_test.rs (target/debug/deps/integration_test-2d3aeee6f15d1f20)running 1 test
test it_adds_two ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sDoc-tests adderrunning 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

运行 cargo test ,可以看到上述输出。测试内容有三个部分:单元测试,集成测试和文档测试。

首先是单元测试被运行 Running unittests ,其次就是我们的主角集成测试的运行 Running tests/integration_test.rs,可以看出,集成测试的输出内容与单元测试并没有大的区别。最后运行的是文档测试 Doc-tests adder

与单元测试类似,我们可以通过指定名称的方式来运行特定的集成测试用例:

$ cargo test --test integration_testRunning tests/integration_test.rs (target/debug/deps/integration_test-82e7799c1bc62298)running 1 test
test it_adds_two ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

这次,单元测试、文档测试啥的都没有运行,只有集成测试目录下的 integration_test 文件被顺利执行。

大家可以尝试下在同一个测试文件中添加更多的测试用例或者添加更多的测试文件,并观察测试输出会如何变化。

共享模块

在集成测试的 tests 目录下,每一个文件都是一个独立的包,这种组织方式可以很好的帮助我们理清测试代码的关系,但是如果大家想要在多个文件中共享同一个功能该怎么做?例如函数 setup 可以用于状态初始化,然后多个测试包都需要使用该函数进行状态的初始化。

也许你会想要创建一个 tests/common.rs 文件,然后将 setup 函数放入其中:

pub fn setup() {// 初始化一些测试状态// ...
}

但是当我们运行 cargo test 后,会发现该函数被当作集成测试函数运行了,即使它并没有包含任何测试功能,也没有被其它测试文件所调用:

$ cargo testRunning tests/common.rs (target/debug/deps/common-5c21f4f2c87696fb)running 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

显然,这个结果并不是我们想要的。为了避免这种输出,我们不能创建 tests/common.rs,而是要创建 tests/common/mod.rs,此时再运行 cargo test 就不会再看到相应的输出。 原因是通过这种文件组织和命名方式, Rust 不再将 common 模块看作是集成测试文件。

总结来说,tests 目录下的子目录中的文件不会被当作独立的包,也不会有测试输出

use adder;mod common;#[test]
fn it_adds_two() {common::setup();assert_eq!(4, adder::add_two(2));
}

此时,就可以在测试中调用 common 中的共享函数了,不过还有一点值得注意,为了使用 common,这里使用了 mod common 的方式来声明该模块。

二进制包的集成测试

目前来说,Rust 只支持对 lib 类型的包进行集成测试,对于二进制包例如 src/main.rs 是无能为力的。原因在于,我们无法在其它包中使用 use 引入二进制包,而只有 lib 类型的包才能被引入,例如 src/lib.rs

这就是为何我们需要将代码逻辑从 src/main.rs 剥离出去放入 lib 包中,例如很多 Rust 项目中都同时有 src/main.rssrc/lib.rs ,前者中只保留代码的主体脉络部分,而具体的实现通通放在类似后者的 lib 包中。

这样,我们就可以对 lib 包中的具体实现进行集成测试,由于 main.rs 中的主体脉络足够简单,当集成测试通过时,意味着 main.rs 中相应的调用代码也将正常运行。

总结

Rust 提供了单元测试和集成测试两种方式来帮助我们组织测试代码以解决代码正确性问题。

单元测试针对的是具体的代码单元,例如函数,而集成测试往往针对的是一个功能或接口 API,正因为目标上的不同,导致了两者在组织方式上的不同:

  • 单元测试的模块和待测试的代码在同一个文件中,且可以很方便地对私有函数进行测试
  • 集成测试文件放在项目根目录下的 tests 目录中,由于该目录下每个文件都是一个包,我们必须要引入待测试的代码到当前包的作用域中,才能进行测试,正因为此,集成测试只能对声明为 pub 的 API 进行测试

下个章节,我们再来看看该如何使用 GitHub Actions 对 Rust 项目进行持续集成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/34193.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

细说QT表格类复杂控件用法(TableView/TreeView/ListView)

文章目录 表格类控件样式控制设置表格类控件指示器样式表格类控件嵌入自定义控件表格控件禁用列拖动设置列宽和行高表格选中操作数据库+表格控件选中操作和选中样式Qt提供了几种基于模型/视图(Model/View)架构的表格类控件,主要包括QTableView、QTreeView和QListView。这些控件…

Makefile中lastword的用法

Makefile中lastword的用法 在 Makefile 中,lastword 函数用于返回参数列表中的最后一个单词(以空格或制表符分隔)。它的基本语法如下: lastword words其中,words 是一个包含多个单词的参数列表,可以是变量…

一次DC1靶机的渗透测试

确定目标IP: nmap -sP 192.168.11.1/24 发现目标机器 扫描开放的端口: nmap -T4 -A -v 192.168.11.145 发现开放了一个80端口,并且给出了是Drupal的管理系统 浏览器访问这个服务: 因为这是一个Drupal的管理系统,那么…

Pandas基础应用:数据处理与分析的利器

概括 在数据科学领域,Pandas无疑是一个强大的数据处理和分析工具。它提供了高效、灵活的数据结构和数据分析功能,使得数据清洗、转换、聚合和可视化等操作变得轻而易举。本文将介绍Pandas的基础应用,帮助读者快速上手并掌握其核心功能。 一…

openlayers禁用鼠标滚轮放大、拖拽事件并设置为只有按住Ctrl键才可以执行放大拖拽操作

禁用openlayers鼠标滚轮放大、拖拽移动地图设置这两行代码即可: new Map({interactions: defaults({ dragPan: false, mouseWheelZoom: false }), }) 或者设置只允许按住Ctrl键才可以操作 import { DragPan, MouseWheelZoom, defaults } from "ol/interact…

利用第三方服务对目标进行被动信息收集防止被发现(web安全白帽子)

利用第三方服务对目标进行被动信息收集防止被发现(web安全白帽子) 1 被动信息收集1.1 信息收集内容1.2 信息用途 2 信息收集-DNS2.1 DNS信息收集NSLOOKUP2.1.1 ping2.1.2 nslookup 2.2 DNS信息收集-DIG(此命令查到的结果更复杂些,…

算法训练营day19--530.二叉搜索树的最小绝对差+501.二叉搜索树中的众数+236. 二叉树的最近公共祖先

一、530.二叉搜索树的最小绝对差 题目链接:https://leetcode.cn/problems/minimum-absolute-difference-in-bst/ 文章讲解:https://programmercarl.com/0530.%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E7%BB%9D%E5%AF…

shell脚本中sudo密码自动输入

在shell脚本中, 我们可能需要用到sudo权限, 如果没有免密设置, 是需要输入密码的, 但是在自动化的脚本中,我们不希望进行手动密码输入,这时就需要用到密码自动输入。 方法如下: echo "my_passwd&quo…

【ubuntu noble】给 apt 添加代理

修改apt.conf sudo vi /etc/apt/apt.conf添加以下内容 Acquire::http::Proxy "http://proxy.example.com:8080"; Acquire::https::Proxy "http://proxy.example.com:8080";出现公钥问题 去我上一篇博客,有解决方案

Ext JS+Spring Boot 使用Ajax方式上传文件

实现方式 使用 Ext JS 进行 AJAX 调用以传递文件通常涉及到创建一个 FormData 对象,将文件附加到这个对象中,然后通过 Ext JS 的 AJAX API 发送这个对象。 基本步骤 以下是使用 Ext JS 发送文件的基本步骤: 准备文件和数据: 首先需要获取到要传递的文件 创建 FormData 对…

红队内网攻防渗透:内网渗透之内网对抗:横向移动篇NTLM中继Relay重放SMBEWSLADP协议强制认证钓鱼监听

NTLM Relay其实严格意义上并不能叫NTLM Relay,而是应该叫 Net-NTLM Relay。它是发生在NTLM认证的第三步,在 Type3 Response消息中存在Net-NTLM Hash,当攻击者获得了Net-NTLM Hash后,可以进行中间人攻击,重放Net-NTLM Hash,这种攻击手法也就是大家所说的NTLM Relay(NTLM 中…

MAB规范(3):Chapter6 Glossary 术语表

第6章 - 术语表 此章不做过多的批注,都是些简单的术语解释。

学分制系统 GetCalendarContentById SQL注入致RCE漏洞复现

0x01 产品简介 学分制系统由上海鹏达计算机系统开发有限公司研发,是基于对职业教育特点和需求的深入理解,结合教育部相关文件精神,并广泛吸纳专家、学者意见而开发的一款综合性管理系统。系统采用模块化的设计方法,方便学校根据自身教学改革特点、信息化建设进程情况选择、…

某某商场对账返款单,table

好久不写原生html&#xff0c;今天写了个&#xff0c;快忘完了 。。。 Double Header Table ***商场统一收银结算商户对账返款单 商场&#xff08;盖章有效&#xff09; 铺位名称&#xff1a; 铺位号&#xff1a; 制单人&#xff1a; 制单日期&#xff1a; </tr><tr&…

前端微应用打开新的窗口时,如何防止matched数据丢失?

本文章基于vueqiankun微前端架构 1、qiankun 与 matched &#xff08;1&#xff09;在 qiankun 微前端架构中&#xff0c;matched 数据通常与路由状态相关&#xff0c;是路由库&#xff08;如 vue-router、react-router 等&#xff09;内部使用的一个状态 &#xff08;2&…

Lesson 39 Don‘t drop it!

Lesson 39 Don’t drop it! 词汇 front n. 前面 搭配&#xff1a;in front of … 在……前面&#xff08;外部&#xff09;    in the front of … 在……前面&#xff08;内部&#xff09; 例句&#xff1a;Bobby坐在Sam的前面。    Bobby is sitting in front of Sam…

嵌入式C语言中常见寄存器的控制方法

使用C语言对寄存器赋值时,常常需要用到C语言的位操作方法。 把寄存器某位清零 假设a代表寄存器,且其中本来已有值。如果要把其中某一位清零且其它位不变,代码如下。 //定义一个变量 a = 1001 1111 b (二进制数)unsigned char a = 0x9f;//对 bit2 清零a &= ~(1<<…

深度学习之近端策略优化(Proximal Policy Optimization,PPO)

PPO(Proximal Policy Optimization,近端策略优化)是深度强化学习中的一种算法,属于策略梯度方法中的一种。PPO通过优化策略来最大化累积奖励,具有稳定性好、易于调参等优点,是目前广泛应用的一种深度强化学习算法。下面介绍PPO的基本原理和流程。 PPO基本原理 PPO算法的…

C++系统相关操作7 - 判断系统大小端大小端的数据转换

1. 关键词2. sysutil.h3. sysutil.cpp4. 测试代码5. 运行结果6. 源码地址 1. 关键词 关键词&#xff1a; C 大端 小端 数据转换 跨平台 大小端的定义&#xff1a; 大端&#xff08;Big Endian&#xff09;和小端&#xff08;Little Endian&#xff09;是指在计算机内存中存…

C++哈希表、哈希桶的实现以及模拟实现封装unordered_map 和 unordered_set 位图 布隆过滤器 哈希切割相关

文章目录 unordered系列关联式容器unordered_mapunordered_map的接口说明 unordered_setset 与 unordered_set的效率比较 底层结构哈希概念哈希冲突哈希函数常见哈希函数哈希冲突解决闭散列 —— 开放定址法哈希表的插入线性探测二次探测 哈希表的闭散列实现哈希表的结构插入代…