当前位置: 首页 > news >正文

【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.“这一行,所以返回值会是元素为StringVector,并且只有一个元素,内容会是"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)&#xff0c;是一个全局正则搜索和输出的工具。它的功能是在指定的文件中搜索出指定的文字。 这个项目分为这么几步&#xff1a; 接收命令行参数读取…...

贪心算法汇总

1.贪心算法 贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。 如何能看出局部最优是否能推出整体最优 靠自己手动模拟&#xff0c;如果模拟可行&#xff0c;就可以试一试贪心策略&#xff0c;如果不可行&#xff0c;可能需要动态规划。 如何验证可不可以…...

H266/VVC 帧内预测中 ISP 技术

帧内子划分 ISP ISP 技术是在 JVET-2002-v3 提案中详细介绍其原理&#xff0c;在 VTM8 中完整展示算法。ISP是线基内预测&#xff08;LIP&#xff09;模式的更新版本&#xff0c;它改善了原始方法在编码增益和复杂度之间的权衡&#xff0c;ISP 算法的核心原理就是利用较近的像…...

PyTorch 中的 Dropout 解析

文章目录 一、Dropout 的核心作用数值示例&#xff1a;置零与缩放**训练阶段****推理阶段** 二、Dropout 的最佳使用位置与具体实例解析1. 放在全连接层后2. 卷积层后的使用考量3. BatchNorm 层与 Dropout 的关系4. Transformer 中的 Dropout 应用 三、如何确定 Dropout 的位置…...

集中式架构vs分布式架构

一、集中式架构 如何准确理解集中式架构 1. 集中式架构的定义 集中式架构是一种将系统的所有计算、存储、数据处理和控制逻辑集中在一个或少数几个节点上运行的架构模式。这些中央节点&#xff08;服务器或主机&#xff09;作为系统的核心&#xff0c;负责处理所有用户请求和…...

微服务主流框架和基础设施介绍

概述 微服务架构的落地需要解决服务治理问题&#xff0c;而服务治理依赖良好的底层方案。当前&#xff0c;微服务的底层方案总的来说可以分为两 种&#xff1a;微服务SDK &#xff08;微服务框架&#xff09;和服务网格。 微服务框架运行原理&#xff1a; 应用程序通过接入 SD…...

4.5.1 顺序查找、折半查找(二分查找)

文章目录 基本概念顺序查找折半查找&#xff08;二分查找&#xff09;索引顺序查找 基本概念 查找表&#xff1a;由同类元素构成的集合。 查找表按照是否可以修改数据表&#xff0c;可分为静态查找表、动态查找表。 静态查找表&#xff1a;不能修改数据表&#xff0c;可进行查询…...

DDD - 微服务设计与领域驱动设计实战(上)_统一建模语言及事件风暴会议

文章目录 Pre概述业务流程需求分析的困境统一语言建模事件风暴会议什么是事件风暴&#xff08;Event Storming&#xff09;事件风暴会议 总结 Pre DDD - 软件退化原因及案例分析 DDD - 如何运用 DDD 进行软件设计 DDD - 如何运用 DDD 进行数据库设计 DDD - 服务、实体与值对…...

基于Piquasso的光量子计算机的模拟与编程

一、引言 在科技飞速发展的当下,量子计算作为前沿领域,正以前所未有的态势蓬勃崛起。它凭借独特的量子力学原理,为解决诸多经典计算难以攻克的复杂问题提供了全新路径。从优化物流配送网络,以实现资源高效调配,到药物分子结构的精准模拟,加速新药研发进程;从金融风险的…...

44_Lua迭代器

在Lua中,迭代器是一种用于遍历集合元素的重要工具。掌握迭代器的使用方法,对于提高Lua编程的效率和代码的可读性具有重要意义。 1.迭代器概述 1.1 迭代器介绍 迭代器是一种设计模式,它提供了一种访问集合元素的方法,而不需要暴露其底层结构。在Lua中,迭代器通常以一个函…...

相机SD卡照片数据不小心全部删除了怎么办?有什么方法恢复吗?

前几天&#xff0c;小编在后台友收到网友反馈说他在整理相机里的SD卡&#xff0c;原本是想把那些记录着美好瞬间的照片导出来慢慢欣赏。结果手一抖&#xff0c;不小心点了“删除所有照片”&#xff0c;等他反应过来&#xff0c;屏幕上已经显示“删除成功”。那一刻&#xff0c;…...

RAG 测评基线

RAG (Retrieval-Augmented Generation) 概述 RAG 是一种大模型的技术&#xff0c;旨在通过将信息检索与生成模型&#xff08;如 GPT&#xff09;结合&#xff0c;增强模型的生成能力。传统的生成模型通常依赖于内部的训练数据来生成答案&#xff0c;但这种方式往往存在回答准确…...

麒麟系统设置tomcat开机自启动

本文针对的麒麟操作系统使用的是SystemD&#xff0c;那么配置Tomcat开机自启动的最佳方式是创建一个SystemD服务单元文件。以下是具体步骤&#xff1a; 确保Tomcat已正确安装&#xff1a; 确认Tomcat已经正确安装&#xff0c;并且可以手动启动和停止。 创建SystemD服务文件&am…...

java 学习笔记 第二阶段:Java进阶

目录 多线程编程 线程的概念与生命周期 创建线程的两种方式(继承Thread类、实现Runnable接口) 线程同步与锁机制(synchronized、Lock) 线程池(ExecutorService) 线程间通信(wait、notify、notifyAll) 实践建议:编写多线程程序,模拟生产者-消费者问题。 反射机…...

机组存储系统

局部性 理论 程序执行&#xff0c;会不均匀访问主存&#xff0c;有些被频繁访问&#xff0c;有些很少被访问 时间局部性 被用到指令&#xff0c;不久可能又被用到 产生原因是大量循环操作 空间局部性 某个数据和指令被使用&#xff0c;附近数据也可能使用 主要原因是顺序存…...

【基础工程搭建】内存访问异常问题分析

前言 汽车电子嵌入式开始更新全新的AUTOSAR项目实战专栏内容,从0到1搭建一个AUTOSAR工程,内容会覆盖AUTOSAR通信协议栈、存储协议栈、诊断协议栈、MCAL、系统服务、标定、Bootloader、复杂驱动、功能安全等所有常见功能和模块,全网同步更新开发设计文档(后期也会更新视频内…...

Mysql 和 navicat 的使用

初识navicat 点开navicat&#xff0c;然后点击连接选择mysql连接&#xff0c;输入密码&#xff08;一般都是123456&#xff09;即可进行连接mysql 可以看见mysql中有如下已经建立好的数据库&#xff0c;是我之前已经建立过的数据库&#xff0c;其中test就是我之前建立的数据库…...

计算机网络(五)运输层

5.1、运输层概述 概念 进程之间的通信 从通信和信息处理的角度看&#xff0c;运输层向它上面的应用层提供通信服务&#xff0c;它属于面向通信部分的最高层&#xff0c;同时也是用户功能中的最低层。 当网络的边缘部分中的两个主机使用网络的核心部分的功能进行端到端的通信时…...

托宾效应和托宾q理论。简单解释

托宾效应和托宾q理论 托宾效应&#xff08;Tobin Effect&#xff09;和托宾q理论&#xff08;Tobins q Theory&#xff09;都是由美国经济学家詹姆斯托宾&#xff08;James Tobin&#xff09;提出的&#xff0c;它们在宏观经济学和金融经济学中占有重要地位。 托宾效应 托宾…...

大数据原生集群 (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 安装…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎&#xff08;Physics Engine&#xff09; 物理引擎 是一种通过计算机模拟物理规律&#xff08;如力学、碰撞、重力、流体动力学等&#xff09;的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互&#xff0c;广泛应用于 游戏开发、动画制作、虚…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

React---day11

14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store&#xff1a; 我们在使用异步的时候理应是要使用中间件的&#xff0c;但是configureStore 已经自动集成了 redux-thunk&#xff0c;注意action里面要返回函数 import { configureS…...

jmeter聚合报告中参数详解

sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample&#xff08;样本数&#xff09; 表示测试中发送的请求数量&#xff0c;即测试执行了多少次请求。 单位&#xff0c;以个或者次数表示。 示例&#xff1a;…...

PostgreSQL——环境搭建

一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在&#xff0…...

为什么要创建 Vue 实例

核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...