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.rhkb.cn/news/264861.html

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

相关文章

抽象的后端

Connection refused: no further information 出现这条代码的核心是你使用redis&#xff0c;但是本地没有开启redis服务 如何启动redis服务 第一步&#xff1a;确定你安装了对应的框架 以spring为例 <dependency><groupId>org.springframework.boot</group…

初识Lombok

前言 最近读一些公司的业务代码&#xff0c;发现近几年的java项目工程中都使用了lombok&#xff0c;lombok是一个可以自动生成get,set、toString等模板类方法的工具框架&#xff0c;程序再引入lombok后&#xff0c;添加一个注解便可以不写get\set\toString等方法。 Lombok示例…

【DL】深度学习之语音识别

目录 1 核心概念 2 安装依赖库 3 实践 语音信号处理&#xff08;Speech Signal Processing&#xff09;简称语音处理。 语音识别&#xff08;ASR&#xff09;和自然语言处理&#xff08;NLP&#xff09;&#xff1a;语音识别就是将语音信号转化成文字文本&#xff0c;简单实…

UE5 C++ Widget练习 Button 和 ProgressBar创建血条

一. 1.C创建一个继承Widget类的子类&#xff0c; 命名为MyUserWidget 2.加上Button 和 UserWidget的头文件 #include "CoreMinimal.h" #include "Components/Button.h" #include "Blueprint/UserWidget.h" #include "MyUserWidget.genera…

微信群发一次能发1000个好友了!

微信为我们日常交流不可或缺的沟通工具 群发功能在日常生活中使用得也比较多 但有时候 比如在节假日&#xff0c;需要向所有客户发送祝福时 在搞活动时&#xff0c;想向所有客户发起邀约时 如果一个一个点击来发送信息 会非常麻烦 但是&#xff01;&#xff01; 我今天…

提升Vue3应用效率的秘诀:深入比较ref与reactive!

ref 和 reactive 是 Vue3 中实现响应式数据的核心 API。ref 用于包装基本数据类型&#xff0c;而 reactive 用于处理对象和数组。尽管 reactive 似乎更适合处理对象&#xff0c;但 Vue3 官方文档更推荐使用 ref。 我的想法&#xff0c;ref就是比reactive好用&#xff0c;官方也…

安防视频监控平台EasyNVR级联视频上云管理平台EasyNVS,出现报错“i/o deadline reached”该如何解决?

上云网关管理平台EasyNVS视频综合管理系统具备汇聚与管理EasyGBS、EasyNVR等平台的能力&#xff0c;系统可以将接入的视频资源实现视频能力统一输出&#xff0c;并能进行远程可视化运维等管理功能&#xff0c;还能解决设备现场没有固定公网IP却需要在公网直播的需求。 有用户反…

下载 axios.js 文件到本地【linux】

方式一 npm install axios在$NODE_PATH/node_modules/axios/dist路径下即可找到axios.js。 方式二 1、百度搜索 GitHub 官网&#xff1a;https://github.com/ 2、搜索 axios 3、点击 axios/axios 4、下载到本地 5、解压&#xff0c;进入到 dist 文件夹** 参考&#x…

2步破解官方sublime4

sublime简要破解流程 1.下载sublime官方最新版2. 破解流程 1.下载sublime官方最新版 打开 官方网站下载 portable version 版&#xff0c;省的安装。。解压到任意位置&#xff0c;备份 sublime_text.exe 文件 2. 破解流程 打开网址把文件 sublime_text.exe 拖入网页搜索替换…

如何使用Lychee+cpolar搭建本地私人图床并实现远程访问存储图片

文章目录 1.前言2. Lychee网站搭建2.1. Lychee下载和安装2.2 Lychee网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 图床作为图片集中存放的服务网站&#xff0c;可以看做是云存储的一部分&#xff0c;既可…

React PureComponent 和 React.memo()区别

1 注意 ● PureComponent和memo仅作为性能优化的方式存在 ● 不要依赖它来阻止渲染&#xff0c;会产生BUG ● PureComponnet 和memo 都是通过对 props 值的浅比较来决定该组件是否需要更新的。 2 PureComponent 和React.memo() 区别 PureComponent 和React.memo()都是React优化…

如何使用 OpenAI Sora?

Sora - 探索AI视频模型的无限可能 OpenAI 的最新项目名为 Sora&#xff0c;这是一个强大的文本到视频模型&#xff0c;可以根据简单的文本提示生成令人兴奋的视频。这个尖端的人工智能模型允许用户描述一个场景&#xff0c;例如“卡通袋鼠跳迪斯科舞”&#xff0c;Sora将生成与…

【C语言基础】:操作符详解(二)

文章目录 操作符详解一、上期扩展二、单目操作符三、逗号表达式四、下标访问[]、 函数调用()五、结构成员访问操作符六、操作符的属性&#xff1a;优先级、结合性1. 优先级2. 结合性 操作符详解 上期回顾&#xff1a;【C语言基础】&#xff1a;操作符详解(一) 一、上期扩展 …

Spring Security源码学习

Spring Security本质是一个过滤器链 过滤器链本质是责任链设计模型 1. HttpSecurity 【第五篇】深入理解HttpSecurity的设计-腾讯云开发者社区-腾讯云 在以前spring security也是采用xml配置的方式&#xff0c;在<http>标签中配置http请求相关的配置&#xff0c;如用户…

unity Android包安装运行后提示:此要用与最新版Android不兼容。

问题 unity 打包apk&#xff0c;安装运行后提示**“此要用与最新版Android不兼容。…” 解决办法 参考文献 https://blog.csdn.net/liweidecsdn/article/details/135997780

【软件测试】--功能测试2--常用设计测试用例方法

一、解决穷举场景 重点&#xff1a;使用等价类划分法 1.1 等价类划分法 重点&#xff1a;有效等价和单个无效等价各取1个即可。 步骤&#xff1a;1、明确需求2、确定有效和无效等价3、根据有效和无效造数据编写用例 1.2 案例&#xff08;qq合法验证&#xff09; 需求&#xff…

【MySQL】学习和总结联合查询

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-OPj5g6evbkm5ol0U {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

番外篇 | YOLOv5+DeepSort实现行人目标跟踪检测

前言:Hello大家好,我是小哥谈。DeepSort是一种用于目标跟踪的深度学习算法。它结合了目标检测和目标跟踪的技术,能够在视频中准确地跟踪多个目标,并为每个目标分配一个唯一的ID。DeepSort的核心思想是将目标检测和目标跟踪两个任务进行联合训练,以提高跟踪的准确性和稳定性…

ChatGPT 国内快速上手指南

ChatGPT简介 ChatGPT是由OpenAI团队研发的自然语言处理模型&#xff0c;该模型在大量的互联网文本数据上进行了预训练&#xff0c;使其具备了深刻的语言理解和生成能力。 GPT拥有上亿个参数&#xff0c;这使得ChatGPT在处理各种语言任务时表现卓越。它的训练使得模型能够理解上…

2024水科技大会暨技术装备成果展览会——高品质供水和饮用水水源安全保障论坛

供水与饮水安全直接关系到人民群众的生活与健康&#xff0c;切实做好城市供水与饮水安全保障工作&#xff0c;是把以人为本真正落到实处的一项紧迫任务。近年来&#xff0c;中央和地方加大了城乡供水与饮水安全保障工作的力度&#xff0c;对标最优质供水城市建设要求&#xff0…