Rust 测试的组织结构

测试的组织结构

本章一开始就提到,测试是一个复杂的概念,而且不同的开发者也采用不同的技术和组织。Rust 社区倾向于根据测试的两个主要分类来考虑问题:单元测试unit tests)与 集成测试integration tests)。单元测试倾向于更小而更集中,在隔离的环境中一次测试一个模块,或者是测试私有接口。而集成测试对于你的库来说则完全是外部的。它们与其他外部代码一样,通过相同的方式使用你的代码,只测试公有接口而且每个测试都有可能会测试多个模块。

为了保证你的库能够按照你的预期运行,从独立和整体的角度编写这两类测试都是非常重要的。

单元测试

单元测试的目的是在与其他部分隔离的环境中测试每一个单元的代码,以便于快速而准确的某个单元的代码功能是否符合预期。单元测试与他们要测试的代码共同存放在位于 src 目录下相同的文件中。规范是在每个文件中创建包含测试函数的 tests 模块,并使用 cfg(test) 标注模块。

测试模块和 #[cfg(test)]

测试模块的 #[cfg(test)] 标注告诉 Rust 只在执行 cargo test 时才编译和运行测试代码,而在运行 cargo build 时不这么做。这在只希望构建库的时候可以节省编译时间,并且因为它们并没有包含测试,所以能减少编译产生的文件的大小。与之对应的集成测试因为位于另一个文件夹,所以它们并不需要 #[cfg(test)] 标注。然而单元测试位于与源码相同的文件中,所以你需要使用 #[cfg(test)] 来指定他们不应该被包含进编译结果中。

回忆本章第一部分新建的 adder 项目,Cargo 为我们生成了如下代码:

文件名: src/lib.rs

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

上述代码就是自动生成的测试模块。cfg 属性代表 configuration ,它告诉 Rust 其之后的项只应该被包含进特定配置选项中。在这个例子中,配置选项是 test,即 Rust 所提供的用于编译和运行测试的配置选项。通过使用 cfg 属性,Cargo 只会在我们主动使用 cargo test 运行测试时才编译测试代码。这包括测试模块中可能存在的帮助函数, 以及标注为 #[test] 的函数。

测试私有函数

测试社区中一直存在关于是否应该对私有函数直接进行测试的论战,而在其他语言中想要测试私有函数是一件困难的,甚至是不可能的事。不过无论你坚持哪种测试意识形态,Rust 的私有性规则确实允许你测试私有函数。考虑示例 11-12 中带有私有函数 internal_adder 的代码:

文件名: src/lib.rs

# fn main() {}

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

示例 11-12:测试私有函数

注意 internal_adder 函数并没有标记为 pub,不过因为测试也不过是 Rust 代码同时 tests 也仅仅是另一个模块,我们完全可以在测试中导入和调用 internal_adder。如果你并不认为应该测试私有函数,Rust 也不会强迫你这么做。

集成测试

在 Rust 中,集成测试对于你需要测试的库来说完全是外部的。同其他使用库的代码一样使用库文件,也就是说它们只能调用一部分库中的公有 API 。集成测试的目的是测试库的多个部分能否一起正常工作。一些单独能正确运行的代码单元集成在一起也可能会出现问题,所以集成测试的覆盖率也是很重要的。为了创建集成测试,你需要先创建一个 tests 目录。

tests 目录

为了编写集成测试,需要在项目根目录创建一个 tests 目录,与 src 同级。Cargo 知道如何去寻找这个目录中的集成测试文件。接着可以随意在这个目录中创建任意多的测试文件,Cargo 会将每一个文件当作单独的 crate 来编译。

让我们来创建一个集成测试。保留示例 11-12 中 src/lib.rs 的代码。创建一个 tests 目录,新建一个文件 tests/integration_test.rs,并输入示例 11-13 中的代码。

文件名: tests/integration_test.rs

use adder;

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

示例 11-13:一个 adder crate 中函数的集成测试

与单元测试不同,我们需要在文件顶部添加 use adder。这是因为每一个 tests 目录中的测试文件都是完全独立的 crate,所以需要在每一个文件中导入库。

并不需要将 tests/integration_test.rs 中的任何代码标注为 #[cfg(test)]tests 文件夹在 Cargo 中是一个特殊的文件夹, Cargo 只会在运行 cargo test 时编译这个目录中的文件。现在就运行 cargo test 试试:

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31 secs
     Running target/debug/deps/adder-abcabcabc

running 1 test
test tests::internal ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

     Running target/debug/deps/integration_test-ce99bcc2479f4607

running 1 test
test it_adds_two ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

现在有了三个部分的输出:单元测试、集成测试和文档测试。第一部分单元测试与我们之前见过的一样:每个单元测试一行(示例 11-12 中有一个叫做 internal 的测试),接着是一个单元测试的摘要行。

集成测试部分以行 Running target/debug/deps/integration-test-ce99bcc2479f4607(在输出最后的哈希值可能不同)开头。接下来每一行是一个集成测试中的测试函数,以及一个位于 Doc-tests adder 部分之前的集成测试的摘要行。

我们已经知道,单元测试函数越多,单元测试部分的结果行就会越多。同样的,在集成文件中增加的测试函数越多,也会在对应的测试结果部分增加越多的结果行。每一个集成测试文件有对应的测试结果部分,所以如果在 tests 目录中增加更多文件,测试结果中就会有更多集成测试结果部分。

我们仍然可以通过指定测试函数的名称作为 cargo test 的参数来运行特定集成测试。也可以使用 cargo test--test 后跟文件的名称来运行某个特定集成测试文件中的所有测试:

$ cargo test --test integration_test
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running target/debug/integration_test-952a27e0126bb565

running 1 test
test it_adds_two ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

这个命令只运行了 tests 目录中我们指定的文件 integration_test.rs 中的测试。

集成测试中的子模块

随着集成测试的增加,你可能希望在 tests 目录增加更多文件以便更好的组织他们,例如根据测试的功能来将测试分组。正如我们之前提到的,每一个 tests 目录中的文件都被编译为单独的 crate。

将每个集成测试文件当作其自己的 crate 来对待,这更有助于创建单独的作用域,这种单独的作用域能提供更类似与最终使用者使用 crate 的环境。然而,正如你在第 7 章中学习的如何将代码分为模块和文件的知识,tests 目录中的文件不能像 src 中的文件那样共享相同的行为。

当你有一些在多个集成测试文件都会用到的帮助函数,而你尝试按照第 7 章 “将模块移动到其他文件” 部分的步骤将他们提取到一个通用的模块中时, tests 目录中不同文件的行为就会显得很明显。例如,如果我们可以创建 一个tests/common.rs 文件并创建一个名叫 setup 的函数,我们希望这个函数能被多个测试文件的测试函数调用:

文件名: tests/common.rs

pub fn setup() {
    // 编写特定库测试所需的代码
}

如果再次运行测试,将会在测试结果中看到一个新的对应 common.rs 文件的测试结果部分,即便这个文件并没有包含任何测试函数,也没有任何地方调用了 setup 函数:

running 1 test
test tests::internal ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

     Running target/debug/deps/common-b8b07b6f1be2db70

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

     Running target/debug/deps/integration_test-d993c68b431d39df

running 1 test
test it_adds_two ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

我们并不想要common 出现在测试结果中显示 running 0 tests 。我们只是希望其能被其他多个集成测试文件中调用罢了。

为了不让 common 出现在测试输出中,我们将创建 tests/common/mod.rs ,而不是创建 tests/common.rs 。这是一种 Rust 的命名规范,这样命名告诉 Rust 不要将 common 看作一个集成测试文件。将 setup 函数代码移动到 tests/common/mod.rs 并删除 tests/common.rs 文件之后,测试输出中将不会出现这一部分。tests 目录中的子目录不会被作为单独的 crate 编译或作为一个测试结果部分出现在测试输出中。

一旦拥有了 tests/common/mod.rs,就可以将其作为模块以便在任何集成测试文件中使用。这里是一个 tests/integration_test.rs 中调用 setup 函数的 it_adds_two 测试的例子:

文件名: tests/integration_test.rs

use adder;

mod common;

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

注意 mod common; 声明与示例 7-25 中展示的模块声明相同。接着在测试函数中就可以调用 common::setup() 了。

二进制 crate 的集成测试

如果项目是二进制 crate 并且只包含 src/main.rs 而没有 src/lib.rs,这样就不可能在 tests 目录创建集成测试并使用 use 语句导入 src/main.rs 中定义的函数。只有库 crate 才会向其他 crate 暴露了可供调用和使用的函数;二进制 crate 只意在单独运行。

这就是 Rust 二进制项目明确采用 src/main.rs 调用 src/lib.rs 中的逻辑的原因之一。通过这种结构,集成测试 就可以 通过 use 测试库 crate 中的主要功能了,而如果这些重要的功能没有问题的话,src/main.rs 中的少量代码也就会正常工作且不需要测试。

总结

Rust 的测试功能提供了一个确保即使你改变了函数的实现方式,也能继续以期望的方式运行的途径。单元测试独立地验证库的不同部分,也能够测试私有函数实现细节。集成测试则检查多个部分是否能结合起来正确地工作,并像其他外部代码那样测试库的公有 API。即使 Rust 的类型系统和所有权规则可以帮助避免一些 bug,不过测试对于减少代码中不符合期望行为的逻辑 bug 仍然是很重要的。

让我们将本章和其他之前章节所学的知识组合起来,在下一章一起编写一个项目!

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

论文阅读 - Intriguing properties of neural networks

Intriguing properties of neural networks 经典论文、对抗样本领域的开山之作 发布时间:2014 论文链接: https://arxiv.org/pdf/1312.6199.pdf 作者:Christian Szegedy, Wojciech Zaremba, Ilya Sutskever, Joan Bruna, Dumitru Erhan, Ian Goodfellow,…

MongoDB教程(四):mongoDB索引

💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝你生活愉快! 文章目录 引言一、MongoD…

【RHCE】综合实验0710综合实验

题目: 主服务器192.168.244.130 防火墙允许服务的放行: selinux放行 [rootlocalhost ~]# ll -Z /nfs/rhce 总用量 4 -rw-r--r--. 1 root root unconfined_u:object_r:default_t:s0 8 7月 10 16:52 index.html -rw-r--r--. 1 nobody nobody system_…

【深度学习 pytorch】迁移学习 (迁移ResNet18)

李宏毅深度学习笔记 《深度学习原理Pytorch实战》 https://blog.csdn.net/peter6768/article/details/135712687 迁移学习 实际应用中很多任务的数据的标注成本很高,无法获得充足的训练数据,这种情况可以使用迁移学习(transfer learning)。假设A、B是两…

Dify中高质量索引模式时,通过线程池处理chunk过程

本文主要介绍了Dify中高质量索引模式时,如何通过线程池执行器来处理chunk的过程。源码位置:dify\api\core\indexing_runner.py\IndexingRunner._load。核心思想:假设一个数据集中有一个文档,该文档可以拆分为12个段(segment)。如果chunk_size=10,那么分为2批提交给线程池…

C语言——流程控制:if...else、switch...case

控制类语句: 逻辑运算符: 选择语句: if...else: if()括号内的内容终究会被转换成0,1,满足的话即为1,不满足的话为0。因此要注意,()括号内因为条件…

智慧城市3d数据可视化系统提升信息汇报的时效和精准度

在信息大爆炸的时代,数据的力量无可估量。而如何将这些数据以直观、高效的方式呈现出来,成为了一个亟待解决的问题。为此,我们推出了全新的3D可视化数据大屏系统,让数据“跃然屏上”,助力您洞察先机,决胜千…

【图解大数据技术】流式计算:Spark Streaming、Flink

【图解大数据技术】流式计算:Spark Streaming、Flink 批处理 VS 流式计算Spark StreamingFlinkFlink简介Flink入门案例Streaming Dataflow Flink架构Flink任务调度与执行task slot 和 task EventTime、Windows、WatermarksEventTimeWindowsWatermarks 批处理 VS 流式…

【我的机器学习之旅】打造个性化手写汉字识别系统

在当今这个数字化飞速发展的时代,人工智能(AI)已经渗透到我们生活的每一个角落,从智能家居到自动驾驶,无一不彰显着其强大的潜力和无限可能。作为一名计算机科学与技术专业的学生,我的毕业设计选择了一个既…

【java】力扣 合并k个升序链表

文章目录 题目链接题目描述思路代码 题目链接 23.合并k个升序链表 题目描述 给你一个链表数组,每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中,返回合并后的链表 思路 我在这个题里面用到了PriorityQueue(优先队列) 的知识 Prio…

网络安全----防御----防火墙nat以及智能选路

前面要求在前一篇博客 网络安全----防御----防火墙安全策略组网-CSDN博客 7,办公区设备可以通过电信链路和移动链路上网(多对多的NAT,并且需要保留一个公网IP不能用来转换) 8,分公司设备可以通过总公司的移动链路和电信链路访问到Dmz区的ht…

本人学习保存-macOS打开Navicat提示「“Navicat Premium”已损坏,无法打开。 你应该将它移到废纸篓。」的解决方法

新安装了macOS Ventura,打开Navicat Premium,发现会提示: “Navicat Premium”已损坏,无法打开。 你应该将它移到废纸篓。 遇到这种情况,千万别直接移到废纸篓,是有办法解决的。在这里记录一下解决方案。 …

据传 OpenAI秘密研发“Strawberry”项目

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…

2、ASPX、.NAT(环境/框架)安全

ASPX、.NAT&#xff08;环境/框架&#xff09;安全 源自小迪安全b站公开课 1、搭建组合&#xff1a; WindowsIISaspxsqlserver .NAT基于windows C开发的框架/环境 对抗Java xx.dll <> xx.jar 关键源码封装在dll文件内。 2、.NAT配置调试-信息泄露 功能点&#xf…

阿里ChatSDK使用,开箱即用聊天框

介绍&#xff1a; 效果&#xff1a;智能助理 ChatSDK&#xff0c;是在ChatUI的基础上&#xff0c;结合阿里云智能客服的最佳实践&#xff0c;沉淀和总结出来的一个开箱即用的&#xff0c;可快速搭建智能对话机器人的框架。它简单易上手&#xff0c;通过简单的配置就能搭建出对…

C++ 入门基础:开启编程之旅

引言 C 是一种高效、灵活且功能强大的编程语言&#xff0c;广泛应用于系统软件、游戏开发、嵌入式系统、科学计算等多个领域。作为 C 语言的扩展&#xff0c;C 不仅继承了 C 语言的过程化编程特性&#xff0c;还增加了面向对象编程&#xff08;OOP&#xff09;的支持&#xff…

frp内网穿透ssh,tcp经过服务器慢速和p2p模式实现高速吃满上传带宽

ssh_server aliyun_server ssh_client 办公室 云服务器 家 在家里经过云服务器中转&#xff0c;很慢&#xff0c;但是很稳定 使用p2p穿透&#xff0c;速度可以直接拉满 ssh_server cc.ini # 连接服务器配置 [common] server_addr 1…

实用机器学习(快速入门)

前言 因为需要机器学习的助力&#xff0c;所以&#xff08;浅浅&#xff09;进修了一下。现在什么东西和AI结合一下感觉就好发文章了&#xff1b;我看了好多学习视频&#xff0c;发现机器学习实际上是数学&#xff0c;并不是常规的去学习代码什么的&#xff08;虽然代码也很简…

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(五)-同时支持无人机和eMBB用户数据传输的用例

引言 本文是3GPP TR 22.829 V17.1.0技术报告&#xff0c;专注于无人机&#xff08;UAV&#xff09;在3GPP系统中的增强支持。文章提出了多个无人机应用场景&#xff0c;分析了相应的能力要求&#xff0c;并建议了新的服务级别要求和关键性能指标&#xff08;KPIs&#xff09;。…

昇思25天学习打卡营第13天 | ResNet50迁移学习

昇思25天学习打卡营第13天 | ResNet50迁移学习 文章目录 昇思25天学习打卡营第13天 | ResNet50迁移学习数据集加载数据集数据集可视化 模型训练固定特征 总结打卡 在实际应用场景中&#xff0c;由于训练数据集不足&#xff0c;很少从头开始训练整个网络。普遍做法是在一个非常大…