Rust之自动化测试(三): 测试组合

开发环境

  • Windows 10
  • Rust 1.73.0

 

  • VS Code 1.83.1

项目工程

这里继续沿用上次工程rust-demo

 测试组合

正如本章开始时提到的,测试是一个复杂的学科,不同的人使用不同的术语和组织。Rust社区根据两个主要类别来考虑测试:单元测试和集成测试。单元测试很小,也更集中,一次测试一个独立的模块,并且可以测试私有接口。集成测试完全在库的外部,使用代码的方式和其他外部代码一样,只使用公共接口,每次测试可能使用多个模块。

编写这两种类型的测试对于确保你的库的各个部分按照你期望的那样,单独地或者一起地工作是很重要的。

单元测试

单元测试的目的是独立于代码的其余部分测试每个代码单元,以快速查明代码在哪里以及没有按预期工作。您将把单元测试放在每个文件的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() {let result = 2 + 2;assert_eq!(result, 4);}
}

 这段代码是自动生成的测试模块。属性cfg代表配置,它告诉Rust只有在给定某个配置选项的情况下,才应该包含以下项目。在这种情况下,配置选项是test,它由Rust提供,用于编译和运行测试。通过使用cfg属性,只有当我们主动使用cargo test运行测试时,Cargo才会编译我们的测试代码。除了用#[test]注释的函数之外,这还包括可能在这个模块中的任何帮助函数。 

测试私有函数

在测试社区中有关于私有函数是否应该被直接测试的争论,其他语言使得私有函数很难或者不可能被测试。不管你坚持哪种测试思想,Rust的隐私规则确实允许你测试私有功能。考虑示例11-12中带有私有函数internal_adder的代码。

 文件名:src/lib.rs

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模块只是另一个模块。正如我们在“模块树中引用项目的路径”一节中所讨论的,子模块中的项目可以使用它们的祖先模块中的项目。在这个测试中,我们use super::*tests模块的所有父项纳入范围,然后测试可以调用internal_adder。如果你认为私有函数不应该被测试,Rust中没有任何东西会强迫你这么做。

集成测试 

在Rust中,集成测试完全在你的库之外。他们使用你的库的方式和其他代码一样,这意味着他们只能调用属于你的库的公共API的函数。他们的目的是测试你的库的许多部分是否正确地一起工作。独立正常工作的代码单元在集成时可能会出现问题,因此集成代码的测试覆盖率也很重要。要创建集成测试,您首先需要一个测试目录。

tests目录

我们在项目目录的顶层创建一个测试目录,紧挨着src。Cargo知道在这个目录中寻找集成测试文件。然后,我们可以根据需要制作尽可能多的测试文件,Cargo会将每个文件编译成一个单独的crate箱。

让我们创建一个集成测试。示例11-12中的代码仍然在src/lib.rs文件中,创建一个tests目录,并创建一个名为tests/integration_test.rs的新文件。

adder
├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
    └── integration_test.rs

将示例11-13中的代码输入tests/integration_test.rs文件: 

文件名:tests/integration_test.rs

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

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

tests目录中的每个文件都是一个单独的crate箱,所以我们需要将我们的库放到每个测试箱子的范围内。因此,我们在代码的顶部添加了use adder,这在单元测试中是不需要的。 

我们不需要用#[cfg(test)]注释tests/integration_test.rs中的任何代码。Cargo对tests目录进行了特殊处理,仅当我们运行cargo test时才编译该目录中的文件。立即运行cargo test

$ cargo testCompiling adder v0.1.0 (file:///projects/adder)Finished test [unoptimized + debuginfo] target(s) in 1.31sRunning unittests src/lib.rs (target/debug/deps/adder-1082c4b063a8fbe6)running 1 test
test tests::internal ... 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-1082c4b063a8fbe6)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

 输出的三个部分包括单元测试、集成测试和doc测试。请注意,如果某个部分中的任何测试失败,下面的部分将不会运行。例如,如果一个单元测试失败,集成和文档测试将不会有任何输出,因为这些测试只有在所有单元测试都通过的情况下才会运行。

单元测试的第一部分和我们看到的一样:每个单元测试一行(我们在示例11-12中添加了一个名为internal的行),然后是单元测试的总结行。 

集成测试部分从Running tests/integration_test.rs行开始,接下来,集成测试中的每个测试函数都有一行,在Doc-tests adder部分开始之前,集成测试的结果有一个摘要行。 

每个集成测试文件都有自己的部分,所以如果我们在测试目录中添加更多的文件,就会有更多的集成测试部分。

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

$ cargo test --test integration_testCompiling adder v0.1.0 (file:///projects/adder)Finished test [unoptimized + debuginfo] target(s) in 0.64sRunning 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

 此命令仅运行tests/integration_test.rs文件中的测试。

集成测试中的子模块

当您添加更多的集成测试时,您可能想要在测试目录中创建更多的文件来帮助组织它们;例如,您可以根据测试功能对测试功能进行分组。如前所述,tests目录中的每个文件都被编译成它自己的单独的箱,这对于创建单独的范围来更接近地模拟最终用户使用您的箱的方式是很有用的。然而,这意味着tests目录中的文件与src中的文件不具有相同的行为,正如你在第7章中了解到的如何将代码分成模块和文件。

当您有一组在多个集成测试文件中使用的帮助函数,并且您试图按照第7章“将模块分离到不同的文件”一节中的步骤将它们提取到一个公共模块中时,测试目录文件的不同行为是最明显的。例如,如果我们创建tests/common.rs并在其中放置一个名为setup的函数,我们可以向setup添加一些代码,我们希望从多个测试文件中的多个测试函数调用这些代码:

文件名:tests/common.rs

pub fn setup() {// setup code specific to your library's tests would go here
}

 当我们再次运行测试时,我们将在common.rs文件的测试输出中看到一个新的部分,尽管这个文件不包含任何测试函数,也没有从任何地方调用setup函数:

$ cargo testCompiling adder v0.1.0 (file:///projects/adder)Finished test [unoptimized + debuginfo] target(s) in 0.89sRunning unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)running 1 test
test tests::internal ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sRunning tests/common.rs (target/debug/deps/common-92948b65e88960b4)running 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sRunning tests/integration_test.rs (target/debug/deps/integration_test-92948b65e88960b4)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

common出现在测试结果中,并显示正在running 0 tests,这不是我们想要的。我们只是想与其他集成测试文件共享一些代码。 

 为了避免common出现在测试输出中,我们不创建tests/common.rs,而是创建tests/common/mod.rs。

├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
    ├── common
    │   └── mod.rs
    └── integration_test.rs

 这是我们在之前的章节“替代文件路径”一节中提到的,Rust也理解的旧命名约定。以这种方式命名文件告诉Rust不要将common模块视为集成测试文件。当我们将setup函数代码移动到tests/common/mod.rs中并删除tests/common.rs文件时,测试输出中的部分将不再出现。测试目录子目录中的文件不会被编译成单独的箱子,也不会在测试输出中包含任何部分。

在我们创建了tests/common/mod.rs之后,我们可以从任何集成测试文件中将它作为一个模块来使用。下面是一个从tests/integration_test.rs中的it_adds_two测试调用setup函数的示例: 

文件名: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-21中演示的模块声明相同。然后在测试函数中,我们可以调用common::setup()函数。

二元crate箱的集成测试 

如果我们的项目是一个只包含src/main.rs文件而没有src/lib.rs文件的二进制文件,我们就不能在tests目录中创建集成测试,也不能用use语句将src/main.rs文件中定义的函数带入范围。只有库crate箱暴露了其他箱可以使用的功能;二进制crate箱是用来独立运行的。

这就是提供二进制文件的Rust项目有一个直接的src/main.rs文件来调用src/lib.rs文件中的逻辑的原因之一。使用这种结构,集成测试可以测试库的use情况,以使重要的功能可用。如果重要的功能起作用,src/main.rs文件中的少量代码也将起作用,并且这少量代码不需要测试。 

总结 

Rust的测试功能提供了一种方式来指定代码应该如何运行,以确保它继续按照您的预期工作,即使您进行了更改。单元测试分别测试库的不同部分,并且可以测试私有的实现细节。集成测试检查库的许多部分是否能正确地协同工作,它们使用库的公共API来测试代码,就像外部代码使用它一样。尽管Rust的类型系统和所有权规则有助于防止某些类型的错误,但测试对于减少与代码预期行为有关的逻辑错误仍然很重要。

让我们结合你在本章和前几章学到的知识来做一个项目!

本章重点

  • 测试组合的概念和分类
  • 单元测试的概念和使用
  • 集成测试的概念和使用 

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

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

相关文章

轻松搭建个人web站点:OpenWRT教程结合内网穿透技术实现公网远程访问

文章目录 前言1. 检查uhttpd安装2. 部署web站点3. 安装cpolar内网穿透4. 配置远程访问地址5. 配置固定远程地址 前言 uhttpd 是 OpenWrt/LuCI 开发者从零开始编写的 Web 服务器,目的是成为优秀稳定的、适合嵌入式设备的轻量级任务的 HTTP 服务器,并且和…

ubuntu启动模式介绍以及如何进入单用户模式和恢复模式

Ubuntu操作系统提供了多种启动模式,每种模式都有不同的用途和功能。下面将深入介绍Ubuntu的几种启动模式: 正常启动模式(Normal boot):这是默认的启动模式,也是大多数用户使用的模式。在正常启动模式下&am…

mk语法示例

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

gitlab 离线安装问题解决:NOKEY,signature check fail

文章目录 1,安装gitlab-ce-161.1,rpm安装gitlab问题: NOKEY、signature check fail1.2, docker 启动 gitlab: ThreadError: cant create Thread: Operation not permitted 2,安装gitlab-ce-142.1 修改配置,重新编译2.2 root用户启…

陪诊小程序|陪诊小程序关爱健康,无忧陪伴

随着社会发展和人们生活水平的提高,健康问题成为人们关注的焦点。然而,在就医过程中,许多患者常常感到孤独和无助,缺乏得到家人陪伴的温暖与安慰。为了解决这一问题,我们公司开发了一款创新的陪诊小程序软件&#xff0…

迁移conda环境后,非root用户执行pip命令和jupyter命令报错/bad interpreter: Permission denied

移动conda环境,在移动的环境执行pip和jupyter 报错-bash: /data/home/用户名/anaconda3/envs/llm/bin/pip: /root/anaconda3/envs/llm/bin/python: bad interpreter: Permission denied 报错信息 一、原因 原因是当前的这个data/home/用户名/anaconda3/envs/环境名…

POSIX信号量

目录 信号量的原理 信号量函数 使用信号量实现线程互斥功能 基于环形队列的生产消费模型 生产者和消费者必须遵守的两个规则 信号量的原理 通过之前的学习,我们知道有的资源可能会被多个执行流同时申请访问,我们将这种资源叫做临界资源&#xff0c…

记录一次线下渗透电气照明系统(分析与实战)

项目地址:https://github.com/MartinxMax/S-Clustr 注意 本次行动未造成任何设备损坏,并在道德允许范围内测试 >ethical hacking< 发现过程 在路途中,发现一个未锁的配电柜,身为一个电工自然免不了好奇心(非专业人士请勿模仿,操作不当的话220V人就直了) 根据照片,简…

算法__中缀表达式转后缀表达式

文章目录 概念算法中缀转后缀案例讲解 后缀算值案例讲解 概念 中缀表达式就是日常生活中遇到的运算表达式&#xff0c;例如a*(b-c)&#xff1b; 后缀表达式则是另一种运算表达式&#xff0c;其特点在于运算符在对象后&#xff0c;且表达式中没有括号&#xff0c;例如abc-* 算…

观察者模式-对象间的联动

有个商城小程序&#xff0c;用户希望当有新品上市的时候能通知他们。这样用户就可以不要时刻盯着小程序了。在这个场景中&#xff0c;用户向小程序订阅了一个服务——发送新品短信。小程序在有新品上线时负责向订阅客户发出这个消息。 这就是发布-订阅模式&#xff0c;也称观察…

Python基础教程:内置函数之字典函数的使用方法

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 len(字典名)&#xff1a; 返回键的个数&#xff0c;即字典的长度 # len(字典名)&#xff1a; # 返回键的个数&#xff0c;即字典的长度dic {a:123,b:456,c:789…

Linux——shell外壳程序

shell外壳程序 1. 什么是shell外壳程序 Linux严格意义上说的是一个操作系统&#xff0c;我们称之为“核心 “ &#xff0c;但我们一般用户&#xff0c;不能直接使用核心。 而是通过核心的“外壳”程序&#xff0c;也就是所谓的shell。 shell是所有外壳程序的统称 平时程序员…

CLIP模型原理

CLIP模型 CLIP(Contrastive Language-Image Pre-Training) 模型是 OpenAI 在 2021 年初发布的用于匹配图像和文本的预训练神经网络模型&#xff0c;是近年来在多模态研究领域的经典之作。OpenAI 收集了 4 亿对图像 - 文本对&#xff08;一张图像和它对应的文本描述&#xff09…

shell的for循环与结构化

shell笔记 列表for循环不带列表for循环for循环举例1.例1 所有文件名大写替换为小写2. 例2 读取/etc/passwd文件&#xff0c;依次输出ip段3. 例3 读取/etc/hosts内容for循环&#xff0c;执行ping4. 例4 循环ip列表&#xff0c;输出对应编号5. 例5 批量添加用户 break1. 例1 brea…

FPGA project : IIC_wr_eeprom

简介&#xff1a; 简单双向二线制&#xff0c;同步串行总线。 scl&#xff1a;串行时钟线&#xff0c;用于同步通讯数据。 sda&#xff1a;双向串行数据线。 物理层&#xff1a; 1&#xff0c;支持挂载多设备。 2&#xff0c;二线制。 3&#xff0c;每个设备有其单独的地…

安装visual studio报错“无法安装msodbcsql“

在安装visual studio2022时安装完成后提示无法安装msodbcsql, 查看日志文件详细信息提示&#xff1a;指定账户已存在。 未能安装包“msodbcsql,version17.2.30929.1,chipx64,languagezh-CN”。 搜索 URL https://aka.ms/VSSetupErrorReports?qPackageIdmsodbcsql;PackageActi…

分布式缓存Spring Cache

一、缓存里的数据如何和数据库的数据保持一致&#xff1f; 缓存数据一致性1)、双写模式2)、失效模式1、缓存数据一致性-双写模式 2、 缓存数据一致性-失效模式 我们系统的一致性解决方案: 1、缓存的所有数据都有过期时间&#xff0c;数据过期下一次查询触发主动更新 2、读写数据…

Android 10 中的隐私权变更

Android 10 中的隐私权变更 重大变更外部存储访问权限范围限定为应用文件和媒体在后台运行时访问设备位置信息需要权限以 Android 9 或更低版本为目标平台时自动授予访问权限在设备升级到 Android 10 后访问针对从后台启动 Activity 的限制标识符和数据移除了联系人亲密程度信息…

JIT耗时优化

优质博文&#xff1a;IT-BLOG-CN 一、背景 业务流量突增&#xff0c;机器直接接入大量流量QPS2000&#xff0c;JIT和GC会消耗太多CPU资源&#xff0c;导致1-2分钟时间内的请求超时导致异常&#xff0c;因此采用流量预热的方式&#xff0c;让机器逐步接入流量&#xff0c;需要预…

go语言Array 与 Slice

有的语言会把数组用作常用的基本的数据结构&#xff0c;比如 JavaScript&#xff0c;而 Golang 中的数组(Array)&#xff0c;更倾向定位于一种底层的数据结构&#xff0c;记录的是一段连续的内存空间数据。但是在 Go 语言中平时直接用数组的时候不多&#xff0c;大多数场景下我…