【Rust自学】12.6. 使用TDD(测试驱动开发)开发库功能
12.6.0. 写在正文之前
第12章要做一个实例的项目——一个命令行程序。这个程序是一个grep(Global Regular Expression Print),是一个全局正则搜索和输出的工具。它的功能是在指定的文件中搜索出指定的文字。
这个项目分为这么几步:
- 接收命令行参数
- 读取文件
- 重构:改进模块和错误处理
- 使用TDD(测试驱动开发)开发库功能(本文)
- 使用环境变量
- 将错误信息写入标准错误而不是标准输出
喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)

12.6.1. 回顾
以下是截止到上一篇文章为止所写出的全部代码。
lib.rs:
use std::error::Error;
use std::fs; pub struct Config { pub query: String, pub filename: String,
} impl Config { pub fn new(args: &[String]) -> Result<Config, &'static str> { if args.len() < 3 { return Err("Not enough arguments"); } let query = args[1].clone(); let filename = args[2].clone(); Ok(Config { query, filename}) }
} pub fn run(config: Config) -> Result<(), Box<dyn Error>> { let contents = fs::read_to_string(config.filename)?; println!("With text:\n{}", contents); Ok(())
}
main.rs:
use std::env;
use std::process;
use minigrep::Config; fn main() { let args:Vec<String> = env::args().collect(); let config = Config::new(&args).unwrap_or_else(|err| { println!("Problem parsing arguments: {}", err); process::exit(1); }); if let Err(e) = minigrep::run(config) { println!("Application error: {}", e); process::exit(1); }
}
在前几节中我们完成了对业务逻辑的迁移,把它分离到lib.rs里。这样对编写测试帮助很大,因为lib.rs中的逻辑不需要在命令行下运行就可以直接使用不同的参数调用业务功能函数,并校验其返回值,也就是针对业务逻辑进行测试。
12.6.2. 什么是测试驱动开发TDD
TDD是Test-Driven Development的缩写,中文名为测试驱动开发,一般遵循以下步骤:
- 编写一个会失败的测试,运行该测试,并确保它是按照预期的原因失败
- 编写或修改刚好足够的代码,让新测试通过
- 重构刚刚添加或修改的代码,确保测试会通过
- 返回步骤1,继续
TDD只是众多软件开发方法中的一种,但是它能对代码的设计工作起到指导和帮助的作用。先编写测试,然后再编写能够通过测试的代码也有助于开发过程中保持较高的测试覆盖率。
本篇文章会通过测试驱动开发的步骤完成程序的搜索逻辑——在文件内容中搜索指定的字符串,将符合的内容的行数放在一个列表中。这个函数会被命名为search。
12.6.3. 修改代码
按照TDD的步骤来写代码:
1. 编写会失败的测试
首先到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)); }
}
也就是说,因为query存储的"duct"在"safe, fast, productive.“这一行,所以返回值会是元素为String的Vector,并且只有一个元素,内容会是"safe, fast, productive.”
返回值是Vector是因为search函数预期能处理多个符合的结果,当然这个测试函数只可能有一个结果,这个测试函数取名叫one_result也是因为如此。
写好了测试模块,接下来写search函数:
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { vec![]
}
- 为了让这个函数能被外部函数调用,得使用
pub来声明为公共的 - 这个函数得加生命周期标志,因为有多个非
self的参数,Rust无法判断哪个参数的生命周期跟返回值的生命周期相同。 - 返回值
Vector内的元素是字符串切片,是从contents截取的,所以返回值应和contents的生命周期相同,所以给它们两个标注了一样的生命周期'a,而query则不需要生命周期标注。 - 函数内容只需要确保能通过编译即可,因为TDD的第一步是编写一个会出错的测试,所以出错才是想要的结果。
测试结果:
$ cargo testCompiling minigrep v0.1.0 (file:///projects/minigrep)Finished `test` profile [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 src/lib.rs:44:9:
assertion `left == right` failedleft: ["safe, fast, productive."]right: []
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`
这个测试失败了,但没问题,这就是TDD第一步想要的结果。
2. 编写或修改刚好足够的代码,让新测试通过
第一步完成,接下来执行TDD的第二步:编写或修改刚好足够的代码,让新测试通过。
思考search的思路,应该是遍历contents的每一行,在遍历的时候查找是否有符合query的字符串,有就把这一行放到返回值的列表中;如果没有,什么都不做,遍历下一行。最后把所有结果放到Vector里返回即可。
-
对于遍历每一行,可以使用
lines方法,它会返回一个迭代器(13章会细讲),会把字符串的内容一行一行地返回。 -
对于查找是否有符合
query的字符串,可以使用contains方法,它返回的是一个布尔类型,有符合的就返回true,反之则为false。 -
最后别忘了,要把符合的行放到
Vector里。
根据以上这些知识,就可以写出代码了:
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
}
注意:这里results不用显示声明元素类型是因为下文中往这个Vector里添加了line这个&str类型,Rust推断出results里的元素类型是&str。
现在运行一下测试:
$ cargo testCompiling minigrep v0.1.0 (file:///projects/minigrep)Finished `test` profile [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
测试通过,没有问题。
3. 在run函数中使用search函数
search函数目前写好了,那就可以在run函数中调用了:
pub fn run(config: Config) -> Result<(), Box<dyn Error>> { let contents = fs::read_to_string(config.filename)?; for line in search(&config.query, &contents) { println!("{}", line); } Ok(())
}
通过循环的方式找到符合的一行就立马打印出来。
试运行一下:
$ cargo run -- frog poem.txtCompiling minigrep v0.1.0 (file:///projects/minigrep)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.38sRunning `target/debug/minigrep frog poem.txt`
How public, like a frog
这个例子只有单行,试试有多行的字符:
$ cargo run -- body poem.txtCompiling minigrep v0.1.0 (file:///projects/minigrep)Finished `dev` profile [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` profile [unoptimized + debuginfo] target(s) in 0.0sRunning `target/debug/minigrep monomorphization poem.txt`
相关文章:
【Rust自学】12.6. 使用TDD(测试驱动开发)开发库功能
12.6.0. 写在正文之前 第12章要做一个实例的项目——一个命令行程序。这个程序是一个grep(Global Regular Expression Print),是一个全局正则搜索和输出的工具。它的功能是在指定的文件中搜索出指定的文字。 这个项目分为这么几步: 接收命令行参数读取…...
贪心算法汇总
1.贪心算法 贪心的本质是选择每一阶段的局部最优,从而达到全局最优。 如何能看出局部最优是否能推出整体最优 靠自己手动模拟,如果模拟可行,就可以试一试贪心策略,如果不可行,可能需要动态规划。 如何验证可不可以…...
H266/VVC 帧内预测中 ISP 技术
帧内子划分 ISP ISP 技术是在 JVET-2002-v3 提案中详细介绍其原理,在 VTM8 中完整展示算法。ISP是线基内预测(LIP)模式的更新版本,它改善了原始方法在编码增益和复杂度之间的权衡,ISP 算法的核心原理就是利用较近的像…...
PyTorch 中的 Dropout 解析
文章目录 一、Dropout 的核心作用数值示例:置零与缩放**训练阶段****推理阶段** 二、Dropout 的最佳使用位置与具体实例解析1. 放在全连接层后2. 卷积层后的使用考量3. BatchNorm 层与 Dropout 的关系4. Transformer 中的 Dropout 应用 三、如何确定 Dropout 的位置…...
集中式架构vs分布式架构
一、集中式架构 如何准确理解集中式架构 1. 集中式架构的定义 集中式架构是一种将系统的所有计算、存储、数据处理和控制逻辑集中在一个或少数几个节点上运行的架构模式。这些中央节点(服务器或主机)作为系统的核心,负责处理所有用户请求和…...
微服务主流框架和基础设施介绍
概述 微服务架构的落地需要解决服务治理问题,而服务治理依赖良好的底层方案。当前,微服务的底层方案总的来说可以分为两 种:微服务SDK (微服务框架)和服务网格。 微服务框架运行原理: 应用程序通过接入 SD…...
4.5.1 顺序查找、折半查找(二分查找)
文章目录 基本概念顺序查找折半查找(二分查找)索引顺序查找 基本概念 查找表:由同类元素构成的集合。 查找表按照是否可以修改数据表,可分为静态查找表、动态查找表。 静态查找表:不能修改数据表,可进行查询…...
DDD - 微服务设计与领域驱动设计实战(上)_统一建模语言及事件风暴会议
文章目录 Pre概述业务流程需求分析的困境统一语言建模事件风暴会议什么是事件风暴(Event Storming)事件风暴会议 总结 Pre DDD - 软件退化原因及案例分析 DDD - 如何运用 DDD 进行软件设计 DDD - 如何运用 DDD 进行数据库设计 DDD - 服务、实体与值对…...
基于Piquasso的光量子计算机的模拟与编程
一、引言 在科技飞速发展的当下,量子计算作为前沿领域,正以前所未有的态势蓬勃崛起。它凭借独特的量子力学原理,为解决诸多经典计算难以攻克的复杂问题提供了全新路径。从优化物流配送网络,以实现资源高效调配,到药物分子结构的精准模拟,加速新药研发进程;从金融风险的…...
44_Lua迭代器
在Lua中,迭代器是一种用于遍历集合元素的重要工具。掌握迭代器的使用方法,对于提高Lua编程的效率和代码的可读性具有重要意义。 1.迭代器概述 1.1 迭代器介绍 迭代器是一种设计模式,它提供了一种访问集合元素的方法,而不需要暴露其底层结构。在Lua中,迭代器通常以一个函…...
相机SD卡照片数据不小心全部删除了怎么办?有什么方法恢复吗?
前几天,小编在后台友收到网友反馈说他在整理相机里的SD卡,原本是想把那些记录着美好瞬间的照片导出来慢慢欣赏。结果手一抖,不小心点了“删除所有照片”,等他反应过来,屏幕上已经显示“删除成功”。那一刻,…...
RAG 测评基线
RAG (Retrieval-Augmented Generation) 概述 RAG 是一种大模型的技术,旨在通过将信息检索与生成模型(如 GPT)结合,增强模型的生成能力。传统的生成模型通常依赖于内部的训练数据来生成答案,但这种方式往往存在回答准确…...
麒麟系统设置tomcat开机自启动
本文针对的麒麟操作系统使用的是SystemD,那么配置Tomcat开机自启动的最佳方式是创建一个SystemD服务单元文件。以下是具体步骤: 确保Tomcat已正确安装: 确认Tomcat已经正确安装,并且可以手动启动和停止。 创建SystemD服务文件&am…...
java 学习笔记 第二阶段:Java进阶
目录 多线程编程 线程的概念与生命周期 创建线程的两种方式(继承Thread类、实现Runnable接口) 线程同步与锁机制(synchronized、Lock) 线程池(ExecutorService) 线程间通信(wait、notify、notifyAll) 实践建议:编写多线程程序,模拟生产者-消费者问题。 反射机…...
机组存储系统
局部性 理论 程序执行,会不均匀访问主存,有些被频繁访问,有些很少被访问 时间局部性 被用到指令,不久可能又被用到 产生原因是大量循环操作 空间局部性 某个数据和指令被使用,附近数据也可能使用 主要原因是顺序存…...
【基础工程搭建】内存访问异常问题分析
前言 汽车电子嵌入式开始更新全新的AUTOSAR项目实战专栏内容,从0到1搭建一个AUTOSAR工程,内容会覆盖AUTOSAR通信协议栈、存储协议栈、诊断协议栈、MCAL、系统服务、标定、Bootloader、复杂驱动、功能安全等所有常见功能和模块,全网同步更新开发设计文档(后期也会更新视频内…...
Mysql 和 navicat 的使用
初识navicat 点开navicat,然后点击连接选择mysql连接,输入密码(一般都是123456)即可进行连接mysql 可以看见mysql中有如下已经建立好的数据库,是我之前已经建立过的数据库,其中test就是我之前建立的数据库…...
计算机网络(五)运输层
5.1、运输层概述 概念 进程之间的通信 从通信和信息处理的角度看,运输层向它上面的应用层提供通信服务,它属于面向通信部分的最高层,同时也是用户功能中的最低层。 当网络的边缘部分中的两个主机使用网络的核心部分的功能进行端到端的通信时…...
托宾效应和托宾q理论。简单解释
托宾效应和托宾q理论 托宾效应(Tobin Effect)和托宾q理论(Tobins q Theory)都是由美国经济学家詹姆斯托宾(James Tobin)提出的,它们在宏观经济学和金融经济学中占有重要地位。 托宾效应 托宾…...
大数据原生集群 (Hadoop3.X为核心) 本地测试环境搭建二
本篇安装软件版本 mysql5.6 spark3.2.1-hadoop3.2 presto0.272 zeppelin0.11.2 kafka_2.13_3.7.2 mysql 安装步骤见-》 https://blog.csdn.net/dudadudadd/article/details/110874570 spark 安装步骤见-》https://blog.csdn.net/dudadudadd/article/details/109719624 安装…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...
