当前位置: 首页 > 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`
http://www.lryc.cn/news/520949.html

相关文章:

  • 贪心算法汇总
  • H266/VVC 帧内预测中 ISP 技术
  • PyTorch 中的 Dropout 解析
  • 集中式架构vs分布式架构
  • 微服务主流框架和基础设施介绍
  • 4.5.1 顺序查找、折半查找(二分查找)
  • DDD - 微服务设计与领域驱动设计实战(上)_统一建模语言及事件风暴会议
  • 基于Piquasso的光量子计算机的模拟与编程
  • 44_Lua迭代器
  • 相机SD卡照片数据不小心全部删除了怎么办?有什么方法恢复吗?
  • RAG 测评基线
  • 麒麟系统设置tomcat开机自启动
  • java 学习笔记 第二阶段:Java进阶
  • 机组存储系统
  • 【基础工程搭建】内存访问异常问题分析
  • Mysql 和 navicat 的使用
  • 计算机网络(五)运输层
  • 托宾效应和托宾q理论。简单解释
  • 大数据原生集群 (Hadoop3.X为核心) 本地测试环境搭建二
  • ClickHouse vs StarRocks 选型对比
  • 04.计算机体系三层结构与优化(操作系统、计算机网络、)
  • UML系列之Rational Rose笔记八:类图
  • Pycharm 使用教程
  • pycharm+pyside6+desinger实现查询汉字笔顺GIF动图
  • vue3学习-day5
  • SpringData-Redis缓存之RedisTemplate
  • 第423场周赛:检测相邻递增子数组 Ⅰ、检测相邻递增子数组 Ⅱ、好子序列的元素之和、统计小于 N 的 K 可约简整数
  • hive知识体系
  • 第三篇 Avaya IP Office的架构及其服务组成
  • AUTOSAR EcuM(ECU状态管理器)