Rust之构建命令行程序(四):用TDD(测试-驱动-开发)模式来开发库的功能

开发环境

  • Windows 11
  • Rust 1.75.0 
  • VS Code 1.86.2

项目工程

这次创建了新的工程minigrep.

用测试-驱动模式来开发库的功能

 既然我们已经将逻辑提取到src/lib.rs中,并将参数收集和错误处理留在src/main.rs中,那么为代码的核心功能编写测试就容易多了。我们可以用各种参数直接调用函数并检查返回值,而不必从命令行调用我们的二进制文件。

在这一节中,我们将使用测试驱动开发(TDD)过程通过以下步骤向minigrep程序添加搜索逻辑:

  1. 编写一个失败的测试并运行它,以确保它因您预期的原因而失败。
  2. 编写或修改足够的代码以使新测试通过。
  3. 重构您刚刚添加或更改的代码,并确保测试继续通过。
  4. 从第1步开始重复!

尽管这只是编写软件的众多方法之一,但TDD可以帮助推动代码设计。在编写通过测试的代码之前编写测试有助于在整个过程中保持高测试覆盖率。

我们将测试该功能的实现,该功能将在文件内容中实际搜索查询字符串,并生成匹配查询的行列表。我们将在一个名为search的函数中添加此功能。

 编写失败的测试

 因为我们不再需要它们了,让我们把println!拿走吧!我们用来检查程序行为的来自src/lib.rs和src/main.rs的语句。然后,在src/lib.rs中,添加一个带有tests函数的测试模块,就像我们在之前的章节中所做的那样。测试函数指定了我们希望search函数具有的行为:它将获取一个查询和要搜索的文本,并且它将只返回包含该查询的文本行。示例12-15显示了这个测试,它还不能编译。

文件名:src/lib.rs

#[cfg(test)]
mod tests {use super::*;#[test]fn one_result() {let query = "duct";let contents = "\
Rust:
safe, fast, productive.
Pick three.";assert_eq!(vec!["safe, fast, productive."], search(query, contents));}
}

示例12-15:为我们希望拥有的search功能创建失败测试

该测试搜索字符串“duct”。我们正在搜索的文本有三行,其中只有一行包含“duct”(注意,左双引号后面的反斜杠告诉Rust不要在该字符串文字内容的开头放置换行符)。我们断言从search函数返回的值只包含我们期望的行。 

我们还不能运行这个测试并看着它失败,因为测试甚至没有编译:search功能还不存在!根据TDD原则,我们将通过添加一个总是返回一个空向量的search函数的定义来添加足够的代码来编译和运行测试,如示例12-16所示。那么测试应该会编译并失败,因为空向量与包含行“safe, fast, productive.”的向量不匹配。

文件名:src/lib.rs

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {vec![]
}

示例12-16:定义足够的search函数以便我们的测试可以编译

 请注意,我们需要在search的签名中定义一个显式的生命周期‘a',并将该生命周期与contents参数和返回值一起使用。回想一下之前章节,生存期参数指定哪个参数的生存期与返回值的生存期相关联。在这种情况下,我们指出返回的向量应该包含引用参数contents片段的字符串片段(而不是参数query)。

换句话说,我们告诉Rust,search函数返回的数据将与contents参数中传递给search函数的数据一样长。这很重要!切片引用的数据必须有效,引用才能有效;如果编译器认为我们正在生成query的字符串片段而不是contents片段,它将错误地进行安全检查。

如果我们忘记了生存期注释并试图编译该函数,我们将得到以下错误:

$ cargo buildCompiling minigrep v0.1.0 (file:///projects/minigrep)
error[E0106]: missing lifetime specifier--> src/lib.rs:28:51|
28 | pub fn search(query: &str, contents: &str) -> Vec<&str> {|                      ----            ----         ^ expected named lifetime parameter|= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `query` or `contents`
help: consider introducing a named lifetime parameter|
28 | pub fn search<'a>(query: &'a str, contents: &'a str) -> Vec<&'a str> {|              ++++         ++                 ++              ++For more information about this error, try `rustc --explain E0106`.
error: could not compile `minigrep` due to previous error

 Rust不可能知道我们需要两个参数中的哪一个,所以我们需要明确地告诉它。因为contents是包含所有文本的参数,我们希望返回文本中匹配的部分,所以我们知道contents是应该使用生存期语法连接到返回值的参数。

其他编程语言不要求您将参数连接到签名中的返回值,但随着时间的推移,这种做法会变得越来越容易。

现在让我们运行测试:

$ cargo testCompiling minigrep v0.1.0 (file:///projects/minigrep)Finished test [unoptimized + debuginfo] target(s) in 0.97sRunning unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)running 1 test
test tests::one_result ... FAILEDfailures:---- tests::one_result stdout ----
thread 'tests::one_result' panicked at 'assertion failed: `(left == right)`left: `["safe, fast, productive."]`,right: `[]`', src/lib.rs:44:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtracefailures:tests::one_resulttest result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00serror: test failed, to rerun pass `--lib`

太好了,测试失败了,正如我们所料。让我们通过测试吧!

编写代码以通过测试

目前,我们的测试失败了,因为我们总是返回一个空向量。为了解决这个问题并实现search,我们的程序需要遵循以下步骤: 

  • 遍历每一行内容。
  • 检查该行是否包含我们的查询字符串。
  • 如果是的话,把它添加到我们返回的值列表中。
  • 如果没有,什么都不要做。
  • 返回匹配的结果列表。

让我们完成每一步,从遍历行开始。

使用lines方法遍历行

Rust有一个有用的方法来处理字符串的逐行迭代,方便地命名为lines,如示例12-17所示。注意这还不能编译。 

文件名:src/lib.rs

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {for line in contents.lines() {// do something with line}
}

示例12-17:遍历contents中的每一行

lines方法返回迭代器。我们将在后续章节深入讨论迭代器,但是回想一下你在示例3-5中看到了使用迭代器的这种方式,在那里我们使用了一个带有迭代器的for循环来对集合中的每一项运行一些代码。 

搜索查询的每一行

接下来,我们将检查当前行是否包含我们的查询字符串。幸运的是,字符串有一个名为contains的有用方法可以帮我们做到这一点!在search函数中添加对contains方法的调用,如示例12-18所示。请注意,这仍然不会编译。 

文件名:src/lib.rs

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {for line in contents.lines() {if line.contains(query) {// do something with line}}
}

示例12-18:添加查看行是否包含query中的字符串的功能

目前,我们正在构建功能。为了让它编译,我们需要从主体返回一个值,就像我们在函数签名中指出的那样。 

存储匹配行

 为了完成这个函数,我们需要一种方法来存储我们想要返回的匹配行。为此,我for循环之前创建一个可变向量,并调用push方法在向量中存储一行。在for循环之后,我们返回向量,如示例12-19所示。

文件名:src/lib.rs

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {let mut results = Vec::new();for line in contents.lines() {if line.contains(query) {results.push(line);}}results
}

清单12-19:存储匹配的行以便我们可以返回它们

现在search函数应该只返回包含查询的行,我们的测试应该通过了。让我们进行测试:

$ cargo testCompiling minigrep v0.1.0 (file:///projects/minigrep)Finished test [unoptimized + debuginfo] target(s) in 1.22sRunning unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)running 1 test
test tests::one_result ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sRunning unittests src/main.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)running 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sDoc-tests minigreprunning 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

 我们的测试通过了,所以我们知道它有效!

此时,我们可以考虑重构搜索功能实现的机会,同时保持测试通过以保持相同的功能。搜索函数中的代码不算太差,但它没有利用迭代器的一些有用特性。我们将在后续章节回到这个例子,在那里我们将详细探讨迭代器,并看看如何改进它。 

使用运行功能中的搜索功能

 既然search函数已经运行并经过测试,我们需要从run函数中调用search。我们需要将config.query值和run从文件中读取的contents传递给search函数。然后run将打印search返回的每一行:

文件名:src/lib.rs

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {let contents = fs::read_to_string(config.file_path)?;for line in search(&config.query, &contents) {println!("{line}");}Ok(())
}// 全部代码
use std::error::Error;
use std::fs;pub struct Config {query: String,file_path: String,
}pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {let mut results = Vec::new();for line in contents.lines() {if line.contains(query) {results.push(line);}}results
}pub fn run(config: Config) -> Result<(), Box<dyn Error>> {let contents = fs::read_to_string(config.file_path)?;for line in search(&config.query, &contents) {println!("{line}");}Ok(())
}

我们仍然使用for循环从search中返回每一行并打印出来。

现在整个程序应该工作了!让我们试一试,首先用一个词来回答艾米莉·狄金森诗歌“青蛙”中的一行:

$ cargo run -- frog poem.txtCompiling minigrep v0.1.0 (file:///projects/minigrep)Finished dev [unoptimized + debuginfo] target(s) in 0.38sRunning `target/debug/minigrep frog poem.txt`
How public, like a frog

 酷!现在让我们尝试一个可以匹配多行的单词,例如“body”:

$ cargo run -- body poem.txtCompiling minigrep v0.1.0 (file:///projects/minigrep)Finished dev [unoptimized + debuginfo] target(s) in 0.0sRunning `target/debug/minigrep body poem.txt`
I'm nobody! Who are you?
Are you nobody, too?
How dreary to be somebody!

 最后,让我们确保在搜索一个不在诗中的单词时不会出现任何行,例如“单形化”:

$ cargo run -- monomorphization poem.txtCompiling minigrep v0.1.0 (file:///projects/minigrep)Finished dev [unoptimized + debuginfo] target(s) in 0.0sRunning `target/debug/minigrep monomorphization poem.txt`

太棒了。我们已经构建了我们自己的经典工具的迷你版本,并学习了很多关于如何构建应用程序的知识。我们还学习了一些关于文件输入和输出、生存期、测试和命令行解析的知识。

为了完成这个项目,我们将简要演示如何使用环境变量以及如何打印到标准错误,这两者在您编写命令行程序时都很有用。

本章重点

  • 了解TDD概念
  • 如何使用TDD
  • 如何编写TDD案例和注意细节

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

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

相关文章

【MATLAB源码-第146期】基于matlab的信源编码仿真GUI,对比霍夫曼编码,算术编码和LZ编码。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 霍夫曼编码、算术编码和LZ编码是三种广泛应用于数据压缩领域的编码技术。它们各自拥有独特的设计哲学、实现方式和适用场景&#xff0c;因此在压缩效率、编解码速度和内存使用等方面表现出不同的特点。接下来详细描述这三种编…

【大厂AI课学习笔记】【2.2机器学习开发任务实例】(7)特征构造

特征分析之后&#xff0c;就是特征构造。 特征构造第一步 特征构造往往要进行数据的归一化。 在本案例中&#xff0c;我们将所有的数据&#xff0c;将所有特征区间调整为0~1之间。 如上图。 那么&#xff0c;为什么要进行归一化&#xff0c;又如何将数据&#xff0c;调整为…

QtCreator“设计”按钮灰色无法点击,如何解决

Mac中安装QML Designer插件&#xff1a; 首选项-> 关于插件 -> 勾选QT Quick下的QML Designer 点击确定安装插件&#xff0c;重启Qt Creator后生效

【Langchain多Agent实践】一个有推销功能的旅游聊天机器人

【LangchainStreamlit】旅游聊天机器人_langchain streamlit-CSDN博客 视频讲解地址&#xff1a;【Langchain Agent】带推销功能的旅游聊天机器人_哔哩哔哩_bilibili 体验地址&#xff1a; http://101.33.225.241:8503/ github地址&#xff1a;GitHub - jerry1900/langcha…

【达梦数据库】数据库的方言问题导致的启动失败

问题场景 在项目中采用了hibernate &#xff0c;连接数据库原本为ORACLE&#xff0c;后续打算改造为国产数据库 达梦 链接配置&#xff1a; # 达梦写法&#xff0c; index:driver-class-name: dm.jdbc.driver.DmDriverjdbc-url: jdbc:dm://192.168.220.225:5236/IDX4username:…

【QT 5 +Linux下软件生成+qt软件生成使用工具+学习他人文章+第一篇:使用linuxdeployqt软件生成】

【QT 5 Linux下软件生成qt软件生成使用工具学习他人文章第一篇&#xff1a;使用linuxdeployqt软件生成】 1、前言2、实验环境3、自我学习总结-本篇总结1、新手的疑问&#xff0c;做这件事的目的2、了解工具&#xff1a;linuxdeployqt工具3、解决相关使用过程中问题 4、参照文章…

新手想要做好抖音小店,在开店前你需要知道这五点注意事项!

大家好&#xff0c;我是电商小布。 开抖店你说难吗&#xff0c;其实也不难&#xff0c;把需要的材料准备好就可以着手开店。 难的呢&#xff0c;是在小店的运营上边。 所以新手开店想要少出错&#xff0c;少踩坑&#xff0c;一定要提前把店铺的相关注意事项搞清楚。 今天&a…

将yolov8权重文件转为onnx格式并在c#中使用

yolo模型转ONNX 在yolov8中&#xff0c;我们将训练结果的.pt权重文件转换为onnx格式只需要使用ultralytics库中的YOLO类&#xff0c;使用pip安装ultralytics库&#xff0c;然后执行下面python代码 from ultralytics import YOLO# 加载YOLOv8模型 model YOLO("best.pt&q…

Selenium浏览器自动化测试框架详解

selenium简介 介绍 Selenium [1] 是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。支持的浏览器包括IE&#xff08;7, 8, 9, 10, 11&#xff09;&#xff0c;Mozilla Firefox&#xff0c;Safari&#xff0c;Google C…

深度学习中数据的转换

原始&#xff08;文本、音频、图像、视频、传感器等&#xff09;数据被转化成结构化且适合机器学习算法或深度学习模型使用的格式。 原始数据转化为结构化且适合机器学习和深度学习模型使用的格式&#xff0c;通常需要经历以下类型的预处理和转换&#xff1a; 文本数据&#xf…

台式电脑电源功率越大越费电吗?装机选购多少W电源

要组装一台电脑&#xff0c;我们首先需要选择硬件。 硬件搭配最关键的一点就是CPU和主板的兼容性。 硬件、电源等之间的平衡都需要仔细考虑。 那么台式电脑电源多大功率合适呢&#xff1f; 下面分享组装电脑电源瓦数选购指南&#xff0c;教您正确选择合适的电源瓦数。 让我们来…

消息中间件篇之RabbitMQ-消息不丢失

一、生产者确认机制 RabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程中丢失。消息发送到MQ以后&#xff0c;会返回一个结果给发送者&#xff0c;表示消息是否处理成功。 当消息没有到交换机就失败了&#xff0c;就会返回publish-confirm。当消息没有到达MQ时&…

防御保护--VPN

目录 VPN的概述 VPN的分类 VPN的核心技术 --- 隧道技术 VPN其他常用技术 VPN的概述 VPN --- 虚拟专用网 --- 一般指依靠ISP或者其他NSP&#xff0c;也可以是企业自身&#xff0c;提供的一条虚拟网 络专线。这个虚拟的专线是逻辑上的&#xff0c;而不是物理上的&#xff0c;所…

LeetCode 2583. 二叉树中的第 K 大层和

题目链接https://leetcode.cn/problems/kth-largest-sum-in-a-binary-tree/?envTypedaily-question&envId2024-02-23 和我上一篇发的博客类似&#xff0c;通过广度优先遍历二叉树&#xff0c;使用队列存每层的节点值&#xff0c; 最后再做处理&#xff0c;输出第K大层和 …

书生·浦语大模型实战营第二节课作业

使用 InternLM-Chat-7B 模型生成 300 字的小故事&#xff08;基础作业1&#xff09;。 熟悉 hugging face 下载功能&#xff0c;使用 huggingface_hub python 包&#xff0c;下载 InternLM-20B 的 config.json 文件到本地&#xff08;基础作业2&#xff09;。 下载过程 进阶…

Nest.js权限管理系统开发(三)环境变量与配置文件

一般来说数据库的配置包含了一些敏感信息&#xff0c;不宜写在代码中提交到远程仓库&#xff0c;所以我们可以将配置写在配置文件中,然后提交 git 时候将生产环境的配置文件其忽略。我们可以新建.env和.env.prod两个文件分别存放开发与生产环境配置&#xff0c;也可以使用YAML等…

2024-2-22 作业

作业要求&#xff1a; 复习前面知识点(指针、结构体、函数)整理思维导图顺序表(按位置插入、按位置删除和去重、重新写)理解链表的代码&#xff0c;尝试写一下链表的尾插和输出 1.复习前面知识点(指针、结构体、函数) 2.整理思维导图 3.顺序表(按位置插入、按位置删除和去重、…

J7 - 对于ResNeXt-50算法的思考

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 J6周有一段代码如下 思考过程 首先看到这个问题的描述&#xff0c;想到的是可能使用了向量操作的广播机制然后就想想办法验证一下&…

Vue 图片轮播第三方库 介绍

Vue图片轮播是一种在网页上以自动或手动方式展示图片的组件&#xff0c;常用于产品展示、网站banner等场景。有许多第三方库可以帮助Vue开发者轻松实现图片轮播功能。以下是一些流行的Vue图片轮播第三方库的介绍&#xff1a; 1. Vue-awesome-swiper - **简介**&#xff1a;V…

数字化转型导师坚鹏:县域数字化转型案例研究

县域数字化转型案例研究 课程背景&#xff1a; 很多县级政府存在以下问题&#xff1a; 不清楚县域数字化转型的发展模式 不清楚县域数字化转型的成功案例 课程特色&#xff1a; 针对性强 实用性强 创新性强 学员收获: 学习县域数字化转型的发展模式。 学习县…