【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 test
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.97s
     Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)

running 1 test
test tests::one_result ... FAILED

failures:

---- tests::one_result stdout ----
thread 'tests::one_result' panicked at src/lib.rs:44:9:
assertion `left == right` failed
  left: ["safe, fast, productive."]
 right: []
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::one_result

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: 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 test
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 1.22s
     Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)

running 1 test
test tests::one_result ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests minigrep

running 0 tests

test 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.txt
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.38s
     Running `target/debug/minigrep frog poem.txt`
How public, like a frog

这个例子只有单行,试试有多行的字符:

$ cargo run -- body poem.txt
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
     Running `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.txt
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
     Running `target/debug/minigrep monomorphization poem.txt`

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/953793.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于springboot+vue的洪涝灾害应急信息管理系统设计与实现

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

SpringBoot + 事务钩子函数

一、案例背景 拿支付系统相关的业务来举例。在支付系统中&#xff0c;我们需要记录每个账户的资金流水&#xff08;记录用户A因为哪个操作扣了钱&#xff0c;因为哪个操作加了钱&#xff09;&#xff0c;这样我们才能对每个账户的账做到心中有数&#xff0c;对于支付系统而言&…

基于微信小程序的智能停车场管理系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

接上一主题,实现QtByteArray任意进制字符串转为十进制数

函数&#xff1a; /// <summary>/// n进制字符串转为十进制数&#xff0c;snDefine的长度最小为二进制数。/// 例子&#xff1a;/// _pn(_Math::strNToInt(_t("1010"), _t("01")));/// _pn(_Math::strNToInt(_t("-1010"), _t("0123…

小游戏前端地区获取

目前前端获取除了太平洋&#xff0c;没有其它的了。 //在JS中都是使用的UTF-8&#xff0c;然而requst请求后显示GBK却是乱码&#xff0c;对传入的GBK字符串&#xff0c;要用数据流接收&#xff0c;responseType: "arraybuffer" tt.request({url: "https://whoi…

sosadmin相关命令

sosadmin命令 以下是本人翻译的官方文档&#xff0c;如有不对&#xff0c;还请指出&#xff0c;引用请标明出处。 原本有个对应表可以跳转的&#xff0c;但是CSDN的这个[](#)跳转好像不太一样&#xff0c;必须得用html标签&#xff0c;就懒得改了。 sosadmin help 用法 sosadm…

人工智能提高安全性的8种方法

人工智能提高安全性的8种方法 人工智能&#xff08;AI&#xff09;通过增强威胁检测、简化响应和加强各个领域的防御&#xff0c;正在彻底改变网络安全。根据HPE的见解&#xff0c;以下是基于AI改善安全性的八种关键方式。 ​ ​ 高级威胁检测和实时监控&#xff1a; 人工智能…

Android SDK下载安装(图文详解)

安装完sdk&#xff0c;就可以直接使用adb命令了&#xff0c;我们做app自动化测试&#xff0c;也需要sdk环境的依赖。 1. 下载Android SDK 网盘下载地址&#xff1a;https://pan.quark.cn/s/8398e52cefc9 官网下载地址&#xff1a;https://www.androiddevtools.cn/ &#xff08;…

25/1/13 嵌入式笔记 继续学习Esp32

PWM&#xff08;Pulse Width Modulation&#xff0c;脉宽调制&#xff09; 是一种通过快速切换高低电平来模拟中间电压值的技术。它广泛应用于控制 LED 亮度、电机速度、音频生成等场景。 analogWrite函数:用于在微控制器&#xff08;如 Arduino&#xff09;上生成模拟信号。 …

uniapp区域滚动——上划进行分页加载数据(详细教程)

##标题 用来总结和学习&#xff0c;便于自己查找 文章目录 一、为什么scroll-view?          1.1 区域滚动页面滚动&#xff1f;          1.2 代码&#xff1f; 二、分页功能&#xff1f;          2.1 如何实现&#xff…

【Git版本控制器--1】Git的基本操作--本地仓库

目录 初识git 本地仓库 认识工作区、暂存区、版本库 add操作与commit操作 master文件与commit id 修改文件 版本回退 撤销修改 删除文件 初识git Git 是一个分布式版本控制系统&#xff0c;主要用于跟踪文件的更改&#xff0c;特别是在软件开发中。 为什么要版本…

【STM32-学习笔记-7-】USART串口通信

文章目录 USART串口通信Ⅰ、硬件电路Ⅱ、常见的电平标准Ⅲ、串口参数及时序Ⅳ、STM32的USART简介数据帧起始位侦测数据采样波特率发生器 Ⅴ、USART函数介绍Ⅵ、USART_InitTypeDef结构体参数1、USART_BaudRate2、USART_WordLength3、USART_StopBits4、USART_Parity5、USART_Mode…

Spring MVC简单数据绑定

【图书介绍】《SpringSpring MVCMyBatis从零开始学&#xff08;视频教学版&#xff09;&#xff08;第3版&#xff09;》_springspringmvcmybatis从零开始 代码、课件、教学视频与相关软件包下载-CSDN博客 《SpringSpring MVCMyBatis从零开始学(视频教学版)&#xff08;第3版&…

初识JVM HotSopt 的发展历程

目录 导学 目前企业对程序员的基本要求 面向的对象 实战 学习目标 JVM 是什么 JVM 的三大核心功能 各大 JVM look 看一下虚拟机 HotSopt 的发展历程 总结 导学 目前企业对程序员的基本要求 面向的对象 实战 学习目标 JVM 是什么 JVM 的三大核心功能 即时编译 主要是…

【pytorch】注意力机制-1

1 注意力提示 1.1 自主性的与非自主性的注意力提示 非自主性提示&#xff1a; 可以简单地使用参数化的全连接层&#xff0c;甚至是非参数化的最大汇聚层或平均汇聚层。 自主性提示 注意力机制与全连接层或汇聚层区别开来。在注意力机制的背景下&#xff0c;自主性提示被称为查…

大数据技术Kafka详解 ⑤ | Kafka中的CAP机制

目录 1、分布式系统当中的CAP理论 1.1、CAP理论 1.2、Partitiontolerance 1.3、Consistency 1.4、Availability 2、Kafka中的CAP机制 C软件异常排查从入门到精通系列教程&#xff08;核心精品专栏&#xff0c;订阅量已达600多个&#xff0c;欢迎订阅&#xff0c;持续更新…

ESP-IDF学习记录(5) 画一块esp32-c3 PCB板

最近看了半个多月&#xff0c;趁着嘉立创官方活动&#xff0c;研究esp32-c3规格书&#xff0c;白嫖PCB 和元器件。原本计划按照官方推荐的搞个四层板&#xff0c;结果打样太贵&#xff0c;火速改成双层板&#xff0c;用了官方的券。小于10*10,也可以使用嘉立创的免费打样。 下面…

nginx 实现 正向代理、反向代理 、SSL(证书配置)、负载均衡 、虚拟域名 ,使用其他中间件监控

我们可以详细地配置 Nginx 来实现正向代理、反向代理、SSL、负载均衡和虚拟域名。同时&#xff0c;我会介绍如何使用一些中间件来监控 Nginx 的状态和性能。 1. 安装 Nginx 如果你还没有安装 Nginx&#xff0c;可以通过以下命令进行安装&#xff08;以 Ubuntu 为例&#xff0…

Netty 入门学习

前言 学习Spark源码绕不开通信&#xff0c;Spark通信是基于Netty实现的&#xff0c;所以先简单学习总结一下Netty。 Spark 通信历史 最开始: Akka Spark 1.3&#xff1a; 开始引入Netty&#xff0c;为了解决大块数据&#xff08;如Shuffle&#xff09;的传输问题 Spark 1.6&…

鸿蒙报错Init keystore failed: keystore password was incorrect

报错如下&#xff1a; > hvigor ERROR: Failed :entry:defaultSignHap... > hvigor ERROR: Tools execution failed. 01-13 16:35:55 ERROR - hap-sign-tool: error: Init keystore failed: keystore password was incorrect * Try the following: > The key stor…