Rust 开发命令行工具(中)(一)

简介: Rust 开发命令行工具(中)(一)

生活在不可避免地走向庸俗。--王小波

大家好,我是柒八九


前言

在上一篇Rust 开发命令行工具(上)中我们从项目配置/参数获取/解析文件内容/处理错误信息/信息输出处理等方面。一步一步写出来可以构建出在本地,兼容错误提示,并且有很好的输出形式的本地搜索工具。

以防大家遗忘,我们把最终的代码贴到下面。

use anyhow::{Context, Result};
use clap::Parser;
use indicatif::ProgressBar;
use std::fs::File;
use std::io::{self, BufRead, Write};
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
#[derive(Parser)]
struct Cli {
    /// 要查找的模式
    pattern: String,
    /// 要读取的文件的路径
    path: PathBuf,
}
fn main() -> Result<()> {
    let args = Cli::parse();
    // 打开文件并创建一个 BufReader 来逐行读取
    let file = File::open(&args.path).with_context(|| format!("无法打开文件 {:?}", &args.path))?;
    let reader = io::BufReader::new(file);
    let stdout = io::stdout();
    let stdout_lock = stdout.lock();
    let mut handle = io::BufWriter::new(stdout_lock);
    let pb = ProgressBar::new(100);
    for line in reader.lines() {
        do_hard_work();
        pb.println(format!("[+] 查找到了 #{:?}项", line));
        pb.inc(1);
        let line = line.with_context(|| "无法读取行")?;
        if line.contains(&args.pattern) {
            writeln!(handle, "{}", line)?;
        }
    }
    Ok(())
}
fn do_hard_work() {
    thread::sleep(Duration::from_millis(250));
}

但是,作为一个功能完备的项目,我们还需要做单元测试/集成测试打包发布。所以,今天我们就从这两面来继续完善我们的Rust项目。


你能所学到的知识点

  1. 前置知识点
  2. 代码测试
  3. 打包并发布 Rust 项目

好了,天不早了,干点正事哇。


前置知识点

前置知识点,只是做一个概念的介绍,不会做深度解释。因为,这些概念在下面文章中会有出现,为了让行文更加的顺畅,所以将本该在文内的概念解释放到前面来。如果大家对这些概念熟悉,可以直接忽略


同时,由于阅读我文章的群体有很多,所以有些知识点可能我视之若珍宝,尔视只如草芥,弃之如敝履。以下知识点,请酌情使用

单元测试 VS 黑盒测试

单元测试黑盒测试(也叫集成测试)是两种不同的软件测试方法,它们旨在检查和验证软件的质量和功能,但它们的关注点、方法和目标有所不同。

单元测试(Unit Testing)

  1. 焦点单元测试关注测试软件的最小功能单元,通常是一个函数、方法或模块。它的目标是验证这个功能单元是否按照预期工作,而不考虑其他组件。
  2. 测试者:通常由开发人员编写和执行。开发人员编写测试用例,用于检查函数、方法或模块的各种输入和边界条件。
  3. 可见性:单元测试通常具有对代码的白盒访问权限,测试者可以访问和检查被测试单元的内部实现细节,以编写更精确的测试用例。
  4. 目标:主要目标是验证单元的正确性,确保它们按照规范执行,并处理各种输入情况。

黑盒测试(Black Box Testing)

  1. 焦点黑盒测试关注测试整个软件系统的功能,而不考虑内部实现。它的目标是验证系统是否按照规范的需求和功能规范工作。
  2. 测试者:可以由测试工程师或独立的测试团队执行。测试者不需要了解系统的内部实现,只需关注系统的输入和输出。
  3. 可见性:黑盒测试没有对系统的内部实现细节的了解。测试者只能访问系统的外部接口和功能。
  4. 目标:主要目标是验证系统是否满足其规范和需求,以及是否在各种输入和条件下表现正常。

在实际项目中,通常需要同时进行单元测试黑盒测试,以确保软件在各个层面上都具有高质量和可靠性。

Rust trait

Rust中,trait 是一种特殊的类型,它定义了某些类型的共享行为trait 提供了一种方式来抽象和共享方法,类似于其他编程语言中的接口。通过实现trait,你可以为自定义类型定义通用的行为,使其能够与其他类型一起工作,从而提高了Rust代码的可复用性和灵活性。

下面我们简单解释一下trait的使用

  1. 定义trait
    我们可以使用trait关键字来定义一个trait,然后在其中声明方法签名。
trait Printable {
    fn print(&self);
}
  1. 这个示例定义了一个名为Printabletrait,它要求实现该trait的类型必须包含一个名为print的方法。
  2. 实现trait
    要使类型实现一个trait,我们需要在类型的定义中使用impl块来实现trait中声明的方法。
struct MyStruct {
    data: i32,
}
impl Printable for MyStruct {
    fn print(&self) {
        println!("Data: {}", self.data);
    }
}
  1. 在这个示例中,MyStruct类型实现了Printabletrait,提供了print方法的具体实现。
  2. 使用trait
    一旦你实现了一个trait,我们可以在任何实现了该trait的类型上调用trait中定义的方法。例如:
let my_instance = MyStruct { data: 42 };
my_instance.print();
  1. 在这里,我们创建了一个MyStruct的实例并调用了print方法。

总的来说,traitRust中用于实现抽象和共享行为的强大工具,它有助于编写可复用的代码,同时确保类型的安全性和一致性。通过合理使用trait,我们可以编写更清晰、更灵活和更可维护的Rust代码。

更详细的内容,可以参考我们之前写的Rust 泛型、trait 与生命周期

Rust的模块系统

Rust{模块系统|the module system},包括:

  • 包(Packages): Cargo 的一个功能,它允许你构建、测试和分享 crate
  • Crates :一个模块的树形结构,它形成了库或二进制项目。
  • 模块(Modules)和 use: 允许你控制作用域和路径的私有性。
  • 路径(path):一个命名例如结构体、函数或模块等项的方式

包和 crate

  • 包(package) 是**提供一系列功能的一个或者多个 crate。**一个包会包含有一个 Cargo.toml 文件,阐述如何去构建这些 crate
  • crate 是一个二进制项或者库crate root 是一个源文件Rust 编译器以它为起始点,并构成你的 crate 的根模块。

包中所包含的内容由几条规则来确立。

  1. 一个包中至多只能包含一个{库 crate|library crate}
  2. 包中可以包含任意多{二进制 crate|binary crate}
  3. 包中至少包含一个 crate,无论是库的还是二进制的。

输入命令 cargo new xxx:当我们输入了这条命令,Cargo 会给我们的创建一个 Cargo.toml 文件。查看 Cargo.toml 的内容,会发现并没有提到 src/main.rs,因为 Cargo 遵循的一个约定:

  • src/main.rs 就是一个与包同名{二进制 crate|binary crate}crate 根。
  • 同样的,Cargo 知道如果包目录中包含 src/lib.rs,则包带有与其同名的{库 crate|library crate},且 src/lib.rscrate 根。

crate 根文件将由 Cargo 传递给 rustc 来实际构建库或者二进制项目。

如果一个包同时含有src/main.rssrc/lib.rs,则它有两个 crate一个库和一个二进制项,且名字都与包相同

通过将文件放在 src/bin 目录下,一个包可以拥有多个二进制crate:每个 src/bin 下的文件都会被编译成一个独立的{二进制 crate|binary crate}

一个 crate 会将一个作用域内的相关功能分组到一起,使得该功能可以很方便地在多个项目之间共享

关于这块的内容,可以参考之前我们写的Rust之包、Crate和模块

crates.io是个啥?

crates.ioRust 编程语言社区的官方包管理和分发平台。它类似于其他编程语言中的包管理器,如 PythonPyPIJavaScriptnpm,用于帮助 Rust 开发者分享、发布和获取 Rust 代码库(也称为 "crates")。

以下是 crates.io 的一些关键特点和功能:

  1. 包管理器crates.io 提供了一个中央存储库,用于托管 Rust crates。开发者可以使用 cargoRust 的包管理工具,轻松地下载、安装和管理这些 crates
  2. 包发布:任何 Rust 开发者都可以将自己的 Rust 代码库发布到 crates.io 上,供其他人使用。这使得代码共享和开源社区合作更加容易。
  3. 版本控制:每个 crate 都有自己的版本号,允许开发者指定使用特定版本的 crate。这有助于确保代码的稳定性和可靠性。
  4. 依赖管理crates.io 允许 crate 之间建立依赖关系,开发者可以在自己的项目中引入其他 crates 作为依赖项,从而快速构建功能强大的应用程序。
  5. 搜索和浏览crates.io 提供了一个易于使用的网站,允许开发者搜索、浏览和查找他们需要的 Rust crates。网站还提供了有关每个 crate 的详细信息、文档和示例代码。
  6. 社区驱动crates.io 是由 Rust 社区维护和支持的,任何人都可以为平台的发展和改进做出贡献。

总之,crates.ioRust 生态系统的核心组成部分,它使 Rust 开发更加便捷,促进了 Rust 社区的增长和分享代码的文化。开发者可以在上面找到各种各样的 Rust crates,以加速他们的项目开发。


2. 代码测试

为了确保我们的程序按照我们的期望工作,最明智的做法是对其进行测试。

一种简单的方法是编写一个README文件,描述我们的程序应该执行的操作。当我们准备发布新版本时,通过README可以描述我们程序的功能和行为。与此同时,我们还可以通过写下程序应该如何应对错误输入来让我们的程序变的更加严谨。


自动化测试

Rust中,#[test] 是一个属性(attribute),用于标记测试函数。Rust内置了一个测试框架,可以使用这个属性来定义和运行测试。

以下是使用 #[test] 的基本步骤:

  1. 首先,确保我们的Rust项目是一个可测试的项目。通常,Rust项目的测试代码存放在一个名为 tests 的目录中,或者在我们的代码中使用条件编译来区分测试代码和生产代码。它允许构建系统发现这些函数并将其作为测试运行,验证它们不会触发panic
  2. 创建一个测试函数并标记为 #[test]。测试函数必须返回 ()(unit类型),并且通常不带参数。
#[test]
fn test_example() {
    // 在这里编写测试代码
}
  1. 在测试函数中编写测试代码,包括调用我们要测试的函数,并使用断言来检查函数的输出是否与预期值匹配。我们可以使用标准库中的 assert! 宏或其他测试断言宏来进行断言。
#[test]
fn test_addition() {
    assert_eq!(2 + 2, 4);
}
#[test]
fn test_subtraction() {
    assert!(5 - 3 > 0);
}
  1. 运行测试。可以使用 Rust 的测试运行器工具来执行测试。常见的测试命令是 cargo test,它会自动查找和运行项目中的所有测试函数。在项目根目录下运行以下命令:
cargo test
  1. 测试运行结果会显示在终端中。成功的测试将显示为ok,失败的测试将显示为 fail,并提供失败的详细信息,包括测试函数的名称和失败的断言。我们可以根据这些信息来调试和修复代码。
  2. 如果需要更详细的输出,可以使用 --verbose 标志运行测试
cargo test --verbose

我们应该最终得到类似以下的输出:

image.png

通过#[test]我们可以测试我们想测试的核心代码,但是,作为一个CLI通常不仅仅是一个函数,它需要更多的人机交互,例如需要处理用户输入、读取文件和编写输出等,我们不可预知的参数和行为。

相关文章
|
1月前
|
Rust 资源调度 安全
为什么使用 Rust over C++ 进行 IoT 解决方案开发
为什么使用 Rust over C++ 进行 IoT 解决方案开发
65 7
|
3月前
|
Rust 安全 JavaScript
探索Rust在系统编程领域的前景:虚拟机和编译器开发的新篇章
【8月更文挑战第31天】在系统编程领域,性能与安全性至关重要。Rust作为一种新兴语言,凭借其独特的内存安全和并发特性,正逐渐成为虚拟机和编译器开发的首选。本文通过案例分析,探讨Rust在这些领域的应用,例如Facebook的Compiler VM (CVM)项目和实验性的JavaScript JIT编译器Mithril。Rust的静态类型系统和所有权模型确保了高性能和安全性,而其强大的包管理和库生态则简化了虚拟机的开发。随着Rust社区的不断成熟,预计未来将有更多基于Rust的创新项目涌现,推动系统编程的发展。对于追求高性能和安全性的开发者而言,掌握Rust将成为一个重要战略方向。
71 1
|
4月前
|
Rust 程序员 开发者
使用 Rust 开发一款类似于 GitBook 的程序
**Rust新手开发者分享开源项目 Typikon**:模仿MDBook,致力于简单Markdown到在线书的渲染。[GitHub](https://github.com/auula/typikon)上可找到源码,欢迎初学者一同学习与贡献。体验轻松构建静态网站,探索Rust之旅。🌟 加入讨论,共建更易用的GitBook替代品。在线文档见[https://typikonbook.github.io](https://typikonbook.github.io)。
38 1
|
5月前
|
Rust Unix Windows
使用Cargo国内镜像提升Rust开发效率
使用Cargo国内镜像提升Rust开发效率
436 0
|
6月前
|
Rust 安全 程序员
拜登:“一切非 Rust 项目均为非法”,开发界要大变天?
白宫国家网络总监办公室(ONCD,以下简称网总办)在本周一发布的报告中说道:“程序员编写代码并非没有后果,他们的⼯作⽅式于国家利益而言至关重要。”
128 1
|
6月前
|
Rust 前端开发 JavaScript
Rust在前端与全栈开发中的实践探索
随着Rust语言的日渐成熟,其应用场景已经从后端扩展到前端和全栈开发领域。本文将深入探讨Rust语言在前端与全栈开发中的实际应用案例,分析Rust语言在这些领域的优势和面临的挑战,并展望Rust未来的发展趋势。
|
11月前
|
存储 Rust 测试技术
Rust 开发命令行工具(中)(三)
Rust 开发命令行工具(中)(三)
146 0
|
11月前
|
Rust 测试技术 人机交互
Rust 开发命令行工具(中)(二)
Rust 开发命令行工具(中)(二)
|
7天前
|
Rust 安全 Java
探索Rust语言的并发编程模型
探索Rust语言的并发编程模型
|
14天前
|
Rust 安全 区块链
探索Rust语言:系统编程的新选择
【10月更文挑战第27天】Rust语言以其安全性、性能和并发性在系统编程领域受到广泛关注。本文介绍了Rust的核心特性,如内存安全、高性能和强大的并发模型,以及开发技巧和实用工具,展示了Rust如何改变系统编程的面貌,并展望了其在WebAssembly、区块链和嵌入式系统等领域的未来应用。