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://xiahunao.cn/news/2805292.html

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

相关文章

STM32F103x 的时钟源

AHB (Advanced High-performance Bus) 高速总线&#xff0c;用来接高速外设的。 APB (Advanced Peripheral Bus) 低速总线&#xff0c;用来接低速外设的&#xff0c;包含APB1 和 APB2。 APB1&#xff1a;上面连接的是低速外设&#xff0c;包括电源接口、备份接口、 CAN 、 US…

k8s的svc流量通过iptables和ipvs转发到pod的流程解析

文章目录 1. k8s的svc流量转发1.1 service 说明1.2 endpoints说明1.3 pod 说明1.4 svc流量转发的主要工作 2. iptables规则解析2.1 svc涉及的iptables链流程说明2.2 svc涉及的iptables规则实例2.2.1 KUBE-SERVICES规则链2.2.2 KUBE-SVC-EFPSQH5654KMWHJ5规则链2.2.3 KUBE-SEP-L…

HTTP概要

文章目录 什么是HTTP?URL的结构请求报文结构请求方法GETHEADPOSTPUTDELETETRACEOPTIONSCONNECTPATCH解释 请求头字段 响应报文结构响应状态响应头字段 HTTP会话3次握手无状态协议 什么是HTTP? HTTP&#xff0c;即Hypertext Transfer Protocol(超文本传输协议) 它是一个”请…

VUE基础知识九 ElemrntUI项目

ElementUI官网 一 项目 最终完成的效果&#xff1a; 切换上边的不同按钮&#xff0c;下方显示不同的表格数据 在src/components下新建不同业务组件的文件夹 1.1 搭建项目 使用脚手架搭建项目后&#xff0c;引入ElementUI&#xff08;搭建、引入ElementUI步骤在第七节里已…

2024能源动力、机械自动化与航天航空技术国际学术会议(ICEPMAT2024)

2024能源动力、机械自动化与航天航空技术国际学术会议(ICEPMAT2024) 会议简介 能源动力、机械自动化和航空航天技术国际学术会议&#xff08;ICEPMAT2024&#xff09;将于2024年在北京举行。会议将探讨能源动力、机械自动化、航空航天技术领域的新研究热点、核心技术和发展趋…

vue手写卡片切换,并且点击获取到卡片信息

需求&#xff1a;做一个卡片样式的列表&#xff0c;之后有一些基本信息&#xff0c;之后卡片选中后样式不一样&#xff0c;默认选中第一个卡片&#xff0c;点击卡片后可以获取到卡片的信息 一、效果 二、关键代码 index默认重0开始,activeTable默认为0,0-0等于0&#xff0c;但…

【Django开发】0到1开发美多shop项目:Celery短信和用户注册。全md文档笔记(附代码,已分享)

本系列文章md笔记&#xff08;已分享&#xff09;主要讨论django商城项目开发相关知识。本项目利用Django框架开发一套前后端不分离的商城项目&#xff08;4.0版本&#xff09;含代码和文档。功能包括前后端不分离&#xff0c;方便SEO。采用Django Jinja2模板引擎 Vue.js实现…

消息队列MQ详解(Kafka、RabbitMQ、RocketMQ、ActiveMQ等)

文章目录 概述消息中间件的优势&#xff08;异步削峰解耦&#xff09;消息队列的缺点消息中间件模式分类消息队列使用场景和应用场景消息中间件常用协议消息中间件的组成如何实现高吞吐量MQ 如何避免消息堆积消息堆积如何处理如何解决消息队列的延时以及过期失效问题&#xff1…

电子元件分销商

Top 10 Active Electronic Parts Distributors List – 2022 / 2023 一家从众多制造商那里收购所有电子元件并销售给客户的公司被称为电子元件分销商。 A company that acquires all electronic components from numerous manufacturers and sells them to customers from a si…

【挖坑前后指针版】快速排序(3)

目录 挖坑版 整体思路 图解分析 代码实现 前后指针版 整体思路 图解分析 代码实现 在前面我们基于hoare的思想实现了hoare版本的快速排序&#xff0c;但是我们发现hoare版本的快排&#xff0c;易错点太多也不是那么容易理解&#xff0c;所以基于hoare的思想有创新了挖…

欢迎 Gemma: Google 最新推出开源大语言模型

今天&#xff0c;Google 发布了一系列最新的开放式大型语言模型 —— Gemma&#xff01;Google 正在加强其对开源人工智能的支持&#xff0c;我们也非常有幸能够帮助全力支持这次发布&#xff0c;并与 Hugging Face 生态完美集成。 Gemma 提供两种规模的模型&#xff1a;7B 参数…

Redis能保证数据不丢失吗?

引言 大家即使没用过Redis&#xff0c;也应该都听说过Redis的威名。 Redis是一种Nosql类型的数据存储&#xff0c;全称Remote Dictionary Server&#xff0c;也就是远程字典服务器&#xff0c;用过Dictionary的应该都知道它是一种键值对&#xff08;Key-Value&#xff09;的数…

网关服务gateway注册Consul时报错Consul service ids must not be empty

网关服务gateway启动时&#xff0c;初始化Consul相关配置时报错。 Consul service ids must not be empty, must start with a letter, end with a letter or digit, and have as interior characters only letters, digits, and hyphen: cbda-server-gateway:10.111.236.142:…

Unity 2021.3发布WebGL设置以及nginx的配置

使用unity2021.3发布webgl 使用Unity制作好项目之后建议进行代码清理&#xff0c;这样会即将不用的命名空间去除&#xff0c;不然一会在发布的时候有些命名空间webgl会报错。 平台转换 将平台设置为webgl 设置色彩空间压缩方式 Compression Format 设置为DisabledDecompre…

C语言特殊函数

静态函数 背景知识&#xff1a;普通函数都是跨文件可见的&#xff0c;即在文件 a.c 中定义的函数可以在 b.c 中使用。 静态函数&#xff1a;只能在定义的文件内可见的函数&#xff0c;称为静态函数。 语法 staitc void f(void) // 在函数头前面增加关键字 static &#xff…

Runaway Queries 管理:提升 TiDB 稳定性的智能引擎

在数字化系统扮演重要角色的今天&#xff0c;数据库稳定性成为企业关注的核心问题。对于重要计算机系统而言&#xff0c;突发的性能下降可能对业务造成不可估量的损失。为了稳定数据库性能&#xff0c;用户可以从管理流程入手规范变更的测试&#xff0c;或者利用产品手段减少预…

基于JAVA的房屋出售出租系统 开源项目

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 房屋销售模块2.2 房屋出租模块2.3 预定意向模块2.4 交易订单模块 三、系统展示四、核心代码4.1 查询房屋求租单4.2 查询卖家的房屋求购单4.3 出租意向预定4.4 出租单支付4.5 查询买家房屋销售交易单 五、免责说明 一、摘…

SQL 中如何实现多表关联查询?

阅读本文之前请参阅----MySQL 数据库安装教程详解&#xff08;linux系统和windows系统&#xff09; 在SQL中&#xff0c;多表关联查询是通过使用JOIN操作来实现的&#xff0c;它允许你从两个或多个表中根据相关列的值来检索数据。以下是几种常见的JOIN类型&#xff1a; …

docker镜像和容器的关系

背景 镜像和容器都是docker中非常重要的概念&#xff0c;镜像是静态的&#xff0c;而容器是动态的&#xff0c;两者的关系就类似类和实例的关系&#xff0c;本文就来分析下两者的关联 镜像和容器 我们知道镜像是存放在仓库中的静态的文件&#xff0c;而容器是运行中的进程&a…

【Python_Zebra斑马打印机编程学习笔记(二)】基于BarTender将btw文件转换为zpl文件

基于BarTender将btw文件转换为zpl文件 基于BarTender将btw文件转换为zpl文件前言一、BarTender1、BarTender 介绍2、BarTender 安装 二、导出 ZPL 文件1、导出 ZPL 文件步骤2、Zebra 打印机驱动安装 基于BarTender将btw文件转换为zpl文件 前言 本文介绍如何基于 BarTender 软…