Rust单元测试、集成测试

news2024/11/26 20:44:08

单元测试、集成测试

在了解了如何在 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 test
     Running unittests (target/debug/deps/adder-8a400aa2b5212836)

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/integration_test.rs (target/debug/deps/integration_test-2d3aeee6f15d1f20)

running 1 test
test it_adds_two ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests adder

running 0 tests

test 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_test
     Running tests/integration_test.rs (target/debug/deps/integration_test-82e7799c1bc62298)

running 1 test
test it_adds_two ... ok

test 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 test
     Running tests/common.rs (target/debug/deps/common-5c21f4f2c87696fb)

running 0 tests

test 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 项目进行持续集成。

推荐几款学习编程的免费平台

免费在线开发平台(https://docs.ltpp.vip/LTPP/)

       探索编程世界的新天地,为学生和开发者精心打造的编程平台,现已盛大开启!这个平台汇集了近4000道精心设计的编程题目,覆盖了C、C++、JavaScript、TypeScript、Go、Rust、PHP、Java、Ruby、Python3以及C#等众多编程语言,为您的编程学习之旅提供了一个全面而丰富的实践环境。       
      在这里,您不仅可以查看自己的代码记录,还能轻松地在云端保存和运行代码,让编程变得更加便捷。平台还提供了私聊和群聊功能,让您可以与同行们无障碍交流,分享文件,共同进步。不仅如此,您还可以通过阅读文章、参与问答板块和在线商店,进一步拓展您的知识边界。
       为了提升您的编程技能,平台还设有每日一题、精选题单以及激动人心的编程竞赛,这些都是备考编程考试的绝佳资源。更令人兴奋的是,您还可以自定义系统UI,选择视频或图片作为背景,打造一个完全个性化的编码环境,让您的编程之旅既有趣又充满挑战。

免费公益服务器(https://docs.ltpp.vip/LTPP-SHARE/linux.html)

       作为开发者或学生,您是否经常因为搭建和维护编程环境而感到头疼?现在,您不必再为此烦恼,因为一款全新的免费公共服务器已经为您解决了所有问题。这款服务器内置了多种编程语言的编程环境,并且配备了功能强大的在线版VS Code,让您可以随时随地在线编写代码,无需进行任何复杂的配置。
随时随地,云端编码
       无论您身在何处,只要有网络连接,就可以通过浏览器访问这款公共服务器,开始您的编程之旅。这种云端编码的便利性,让您的学习或开发工作不再受限于特定的设备或环境。
丰富的编程语言支持
       服务器支持包括C、C++、JavaScript、TypeScript、Go、Rust、PHP、Java、Ruby、Python3以及C#等在内的多种主流编程语言,满足不同开发者和学生的需求。无论您是初学者还是资深开发者,都能找到适合自己的编程环境。
在线版VS Code,高效开发
       内置的在线版VS Code提供了与本地VS Code相似的编辑体验,包括代码高亮、智能提示、代码调试等功能,让您即使在云端也能享受到高效的开发体验。
数据隐私和安全提醒
       虽然服务器是免费的,但为了保护您的数据隐私和安全,我们建议您不要上传任何敏感或重要的数据。这款服务器更适合用于学习和实验,而非存储重要信息。

免费公益MYSQL(https://docs.ltpp.vip/LTPP-SHARE/mysql.html)

       作为一名开发者或学生,数据库环境的搭建和维护往往是一个复杂且耗时的过程。但不用担心,现在有一款免费的MySQL服务器,专为解决您的烦恼而设计,让数据库的使用变得简单而高效。
性能卓越,满足需求
       虽然它是免费的,但性能绝不打折。服务器提供了稳定且高效的数据库服务,能够满足大多数开发和学习场景的需求。
在线phpMyAdmin,管理更便捷
       内置的在线phpMyAdmin管理面板,提供了一个直观且功能强大的用户界面,让您可以轻松地查看、编辑和管理数据库。
数据隐私提醒,安全第一
       正如您所知,这是一项公共资源,因此我们强烈建议不要上传任何敏感或重要的数据。请将此服务器仅用于学习和实验目的,以确保您的数据安全。

免费在线WEB代码编辑器(https://docs.ltpp.vip/LTPP-WEB-IDE/)

       无论你是开发者还是学生,编程环境的搭建和管理可能会占用你宝贵的时间和精力。现在,有一款强大的免费在线代码编辑器,支持多种编程语言,让您可以随时随地编写和运行代码,提升编程效率,专注于创意和开发。
多语言支持,无缝切换
       这款在线代码编辑器支持包括C、C++、JavaScript、TypeScript、Go、Rust、PHP、Java、Ruby、Python3以及C#在内的多种编程语言,无论您的项目需要哪种语言,都能在这里找到支持。
在线运行,快速定位问题
       您可以在编写代码的同时,即时运行并查看结果,快速定位并解决问题,提高开发效率。
代码高亮与智能提示
       编辑器提供代码高亮和智能提示功能,帮助您更快地编写代码,减少错误,提升编码质量。

免费二维码生成器(https://docs.ltpp.vip/LTPP-QRCODE/)

       二维码(QR Code)是一种二维条码,能够存储更多信息,并且可以通过智能手机等设备快速扫描识别。它广泛应用于各种场景,如:
企业宣传
       企业可以通过二维码分享公司网站、产品信息、服务介绍等。
活动推广
       活动组织者可以创建二维码,参与者扫描后可以直接访问活动详情、报名链接或获取电子门票。
个人信息分享
       个人可以生成包含联系方式、社交媒体链接、个人简历等信息的二维码。
电子商务
       商家使用二维码进行商品追踪、促销活动、在线支付等。
教育
       教师可以创建二维码,学生扫描后可以直接访问学习资料或在线课程。
交通出行
       二维码用于公共交通的票务系统,乘客扫描二维码即可进出站或支付车费。        功能强大的二维码生成器通常具备用户界面友好,操作简单,即使是初学者也能快速上手和生成的二维码可以在各种设备和操作系统上扫描识别的特点。

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

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

相关文章

算法金 | K-均值、层次、DBSCAN聚类方法解析

大侠幸会,在下全网同名「算法金」 0 基础转 AI 上岸,多个算法赛 Top 「日更万日,让更多人享受智能乐趣」 聚类分析概述 聚类分析的定义与意义 聚类分析(Clustering Analysis)是一种将数据对象分成多个簇(…

AI 音乐生成器 MusicGPT,同声传译StreamSpeech!Web短视频平台Sharine

AI 音乐生成器 MusicGPT,同声传译StreamSpeech!Web短视频平台Sharine。 项目简介 MusicGPT 是一款应用程序,允许在任何平台上以高性能方式本地运行最新的音乐生成 AI 模型,而无需安装 Python 或机器学习框架等严重依赖项。 目前它仅支持 Me…

【python】OpenCV—Aruco

文章目录 Detect ArucoGuess Aruco Type Detect Aruco 学习参考来自:OpenCV基础(19)使用 OpenCV 和 Python 检测 ArUco 标记 更多使用细节可以参考:【python】OpenCV—Color Correction 源码: 链接:http…

二进制方式部署consul单机版

1.consul的下载 mkdir -p /root/consul/data && cd /root/consul wget https://releases.hashicorp.com/consul/1.18.0/consul_1.18.0_linux_amd64.zip unzip consul_1.18.0_linux_amd64.zip mv consul /usr/local/bin/ 2.配置文件 // 配置文件路径: /roo…

Vue 项目运行时,报错Error: Cannot find module ‘node:path‘

Vue 项目运行时,报错Error: Cannot find module ‘node:path’ internal/modules/cjs/loader.js:883throw err;^Error: Cannot find module node:path Require stack: - D:\nodejs\node_modules\npm\node_modules\node_modules\npm\lib\cli.js - D:\nodejs\node_mo…

240628_昇思学习打卡-Day10-SSD目标检测

240628_昇思学习打卡-Day10-SSD目标检测 今天我们来看SSD(Single Shot MultiBox Detector)算法,SSD是发布于2016年的一种目标检测算法,使用的是one-stage目标检测网络,意思就是说它只需要一步,就能把目标检…

国内多个库被 rsc 钉上 Go 耻辱柱。。。

大家好,我是煎鱼。 这还是比较突然的,下午正努力打工。国内社区群里突然就闹腾起来了。 仔细一看,原来是 Go 核心团队负责人 rsc,又冷不丁搞大招 😅。他直接把国内好几个知名库给直接钉上了 Go 源码库的耻辱柱上了。 如…

普元EOS学习笔记-EOS8.3精简版安装

前言 普元EOS是优秀的高低开结合的企业应用软件开发框架。 普元:这是普元公司的名字,普元信息,上市公司哦,这里就不过多安利了。 EOS:普元公司的企业应用开发系统,开发语言是基于Java,技术框…

JavaScript(2)——输入输出和执行顺序

目录 JS的输入输出语法 输出: 输入 JS的代码执行顺序 字面量 JS的输入输出语法 输出: document.write(内容)alert(内容) 页面弹出警告框console.log(内容) 控制台输出语法,程序员调试使用 作用:向body输出内容 注意&…

zabbix-server的搭建

zabbix-server的搭建 部署 zabbix 服务端(192.168.99.180) rpm -ivh https://mirrors.aliyun.com/zabbix/zabbix/5.0/rhel/7/x86_64/zabbix-release-5.0-1.el7.noarch.rpm cd /etc/yum.repos.d sed -i s#http://repo.zabbix.com#https://mirrors.aliyun.com/zabbix# zabbix.r…

音频分离人声和伴奏可以实现吗?手机人声分离工具10款无偿分享!

随着科技的飞速发展,音频处理技术已经取得了显著的进步,其中音频分离人声和伴奏已成为现实。这一技术不仅为音乐制作人和音频工程师提供了便利,更为广大音乐爱好者提供了无限的创作可能性。本文将为大家分享10款手机人声分离工具,…

前端面试题(基础篇十二)

一、link标签定义、与import的区别 link 标签定义文档与外部资源的关系。 link 元素是空元素,它仅包含属性。 此元素只能存在于 head 部分,不过它可出现任意数。 link 标签中的 rel 属性定义了当前文档与被链接文档之间的关系。常见的 stylesheet 指的是…

C# 警告 warning MSB3884: 无法找到规则集文件“MinimumRecommendedRules.ruleset”

警告 warning MSB3884: 无法找到规则集文件“MinimumRecommendedRules.ruleset” C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\amd64\Microsoft.CSharp.CurrentVersion.targets(129,9): warning MSB3884: 无法找到规则集文件“MinimumRe…

好书速览-深入理解JVM-Part1

文章目录 第二章、Java内存区域与内存溢出异常2.1 概述2.2 运行时数据区域程序计数器(program Counter Register)Java虚拟机栈本地方法栈堆方法区运行时常量池直接内存 2.3 对象生命周期2.3.1 对象的创建2.3.2 对象的内存布局对象头类型指针对齐填充 2.3.3 对象的访…

WordPress免费模板:惊艳动态效果,打造视觉盛宴

WordPress免费模板:惊艳动态效果,打造视觉盛宴 我们为您带来了一款独具特色的WordPress免费模板,这款模板以其独特的动态效果设计,特别是引人注目的动态banner图片效果,为您的网站注入活力,打造一场视觉盛…

某配送平台未授权访问和弱口令(附赠nuclei默认密码验证脚本)

找到一个某src的子站,通过信息收集插件,发现ZABBIX-监控系统,可以日一下 使用谷歌搜索历史漏洞:zabbix漏洞 通过目录扫描扫描到后台,谷歌搜索一下有没有默认弱口令 成功进去了,挖洞就是这么简单 搜索文章还…

定时触发-uniapp + uniCloud 订阅消息实战教程(三)

上一节已经对云函数有了一定的了解,但是,为了发送订阅消息,只会云函数还是差了那么一点意思,所以接下来的这一节,将带领大家熟悉一下定时触发。 熟悉定时触发 如果云函数需要定时/定期执行,即定时触发,您可以使用云函数定时触发器。已配置定时触发器的云函数,会在相应…

SwiftUI八与UIKIT交互

代码下载 SwiftUI可以在苹果全平台上无缝兼容现有的UI框架。例如,可以在SwiftUI视图中嵌入UIKit视图或UIKit视图控制器,反过来在UIKit视图或UIKit视图控制器中也可以嵌入SwiftUI视图。 本文展示如何把landmark应用的主页混合使用UIPageViewController和…

CXL:拯救NVMe SSD缓存不足设计难题-2

LMB提出了基于CXL协议的内存扩展框架和内核模块。该方案利用CXL内存扩展器作为物理DRAM源,旨在提供一个统一的内存分配接口,使PCIe和CXL设备都能方便地访问扩展的内存资源。通过这个接口,NVMe驱动和CUDA的统一内存内核驱动可以直接高效地访问…

telegram mini APP或游戏开发之bot设置

无意中发现telegram上居然也能发布小程序和游戏了,感觉发现了新大陆一样,自己好玩试了下。 参考教程 更详细的免费文章,请查看博客:https://lengmo714.top/6e79860b.html 要在telegram上开发一个mini App或者game,创建一个专属于你的机器人就必不可少了。 创建bot机器人 …