update to ch12-06

main
KaiserY 3 days ago
parent 17a41b5b4d
commit fbbdb44f89

@ -1,7 +1,6 @@
## 测试的组织结构
<!-- https://github.com/rust-lang/book/blob/main/src/ch11-03-test-organization.md -->
<!-- commit 5d22a358fb2380aa3f270d7b6269b67b8e44849e -->
[ch11-03-test-organization.md](https://github.com/rust-lang/book/blob/99071589c5358114de6324d9aa2643caeee305bd/src/ch11-03-test-organization.md)
本章一开始就提到测试是一个复杂的概念而且不同的开发者也采用不同的术语和组织。Rust 社区倾向于根据测试的两个主要分类来考虑问题:**单元测试***unit tests*)与**集成测试***integration tests*)。单元测试倾向于更小而更集中,在隔离的环境中一次测试一个模块,并且可以测试私有接口。而**集成测试**对于你的库来说则完全是外部的。它们与其他外部代码一样,通过相同的方式使用你的代码,只测试公有接口而且每个测试都有可能会测试多个模块。
@ -25,7 +24,7 @@
上述代码就是自动生成的测试模块。`cfg` 属性代表**配置***configuration*),它告诉 Rust 接下来的项只有在给定特定配置选项时,才会被包含。在这种情况下,配置选项是 `test`,即 Rust 所提供的用于编译和运行测试的配置选项。通过使用 `cfg` 属性Cargo 只会在我们主动使用 `cargo test` 运行测试时才编译测试代码。这包括测试模块中可能存在的辅助函数,以及标注为 `#[test]` 的函数。
#### 测试私有函数
#### 私有函数测试
测试社区中一直存在关于是否应该对私有函数直接进行测试的论战而在其他语言中想要测试私有函数是一件困难的甚至是不可能的事。不过无论你坚持哪种测试意识形态Rust 的私有性规则确实允许你测试私有函数。考虑示例 11-12 中带有私有函数 `internal_adder` 的代码。
@ -37,7 +36,7 @@
<span class="caption">示例 11-12测试私有函数</span>
注意 `internal_adder` 函数并没有标记为 `pub`。测试也不过是 Rust 代码,同时 `tests` 也仅仅是另一个模块。正如[“路径用于引用模块树中的项”][paths] 部分所说,子模块的项可以使用其上级模块的项。在测试中,我们通过 `use super::*``tests` 模块的父模块的所有项引入了作用域,接着测试调用了 `internal_adder`。如果你并不认为应该测试私有函数Rust 也不会强迫你这么做。
注意 `internal_adder` 函数并没有标记为 `pub`。测试也只是 Rust 代码,而 `tests` 模块也只是另一个模块。正如[“路径用于引用模块树中的项”][paths] 部分所说,子模块中的项可以使用其祖先模块中的项。在这个测试中,我们通过 `use super::*``tests` 模块父模块中的所有项引入作用域,然后测试就可以调用 `internal_adder`。如果你并不认为应该测试私有函数Rust 也不会强迫你这么做。
### 集成测试

@ -1,13 +1,12 @@
# 一个 I/O 项目:构建一个命令行程序
<!-- https://github.com/rust-lang/book/blob/main/src/ch12-00-an-io-project.md -->
<!-- commit 3a30e4c1fbe641afc066b3af9eb01dcdf5ed8b24 -->
[ch12-00-an-io-project.md](https://github.com/rust-lang/book/blob/d7c0e477a22bcb37fdb290c6046058565d6738c2/src/ch12-00-an-io-project.md)
本章既是一个目前所学的多项技能的概括,也是一个更多标准库功能的探索。我们将构建一个与文件和命令行输入/输出交互的命令行工具来练习现在一些你已经掌握的 Rust 概念。
本章既是对目前所学诸多技能的一次回顾,也会进一步探索一些标准库功能。我们将构建一个与文件和命令行输入/输出交互的命令行工具,借此练习一些你现在已经掌握的 Rust 概念。
Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的理想语言,所以我们的项目将创建一个我们自己版本的经典命令行搜索工具:`grep`。grep 是 “**G**lobally search a **R**egular **E**xpression and **P**rint.” 的首字母缩写。`grep` 最简单的使用场景是在特定文件中搜索指定字符串。为此,`grep` 获取一个文件路径和一个字符串作为参数,接着读取文件并找到其中包含字符串参数的行,然后打印出这些行。
在这个过程中,我们会展示如何让我们的命令行工具利用很多命令行工具中用到的终端功能。读取环境变量来使得用户可以配置工具的行为。打印到标准错误控制流(`stderr`)而不是标准输出(`stdout`),例如这样用户可以选择将成功输出重定向到文件中的同时仍然在屏幕上显示错误信息。
在这个过程中,我们还会展示如何让命令行工具使用许多其他命令行工具都会用到的终端特性。我们会读取环境变量的值,让用户能够配置工具的行为;也会将错误信息打印到标准错误流(`stderr`)而不是标准输出(`stdout`),这样一来,例如用户就可以把成功输出重定向到文件,同时仍然在屏幕上看到错误信息。
一位 Rust 社区的成员Andrew Gallant已经创建了一个功能完整且非常快速的 `grep` 版本,名为 `ripgrep`。相比之下,我们的版本将非常简单,本章将教会你一些帮助理解像 `ripgrep` 这样真实项目的背景知识。

@ -1,7 +1,6 @@
## 接受命令行参数
<!-- https://github.com/rust-lang/book/blob/main/src/ch12-01-accepting-command-line-arguments.md -->
<!-- commit 8880eacd339876c9a53d606720176bb02a4e5b3f -->
[ch12-01-accepting-command-line-arguments.md](https://github.com/rust-lang/book/blob/d7c0e477a22bcb37fdb290c6046058565d6738c2/src/ch12-01-accepting-command-line-arguments.md)
一如既往使用 `cargo new` 新建一个项目,我们称之为 `minigrep` 以便与可能已经安装在系统上的 `grep` 工具相区分。
@ -17,7 +16,7 @@ $ cd minigrep
$ cargo run -- searchstring example-filename.txt
```
现在 `cargo new` 生成的程序无法处理传递给它的参数。[Crates.io](https://crates.io/) 上有一些现成的库可以帮助我们接受命令行参数,不过我们正在学习这一概念,就让我们自己实现这个功能。
现在 `cargo new` 生成的程序还不能处理我们传给它的参数。[crates.io](https://crates.io/) 上有一些现成的库可以帮助编写接受命令行参数的程序,不过既然你现在正在学习这个概念,我们就自己来实现这个功能。
### 读取参数值

@ -1,7 +1,6 @@
## 读取文件
<!-- https://github.com/rust-lang/book/blob/main/src/ch12-02-reading-a-file.md -->
<!-- commit 3a30e4c1fbe641afc066b3af9eb01dcdf5ed8b24 -->
[ch12-02-reading-a-file.md](https://github.com/rust-lang/book/blob/d7c0e477a22bcb37fdb290c6046058565d6738c2/src/ch12-02-reading-a-file.md)
现在我们要增加读取由 `file_path` 命令行参数指定的文件的功能。首先,需要一个用来测试的示例文件:我们会用一个拥有多行少量文本且有一些重复单词的文件。示例 12-3 是一首艾米莉·狄金森Emily Dickinson的诗它正适合这个工作在项目根目录创建一个文件 *poem.txt*,并输入诗 "I'm nobody! Who are you?"
@ -13,7 +12,7 @@
<span class="caption">示例 12-3艾米莉·狄金森的诗 “Im nobody! Who are you?”,一个好的测试用例</span>
有了文本后,修改 *src/main.rs* 并增加如示例 12-4 所示的打开文件的代码
有了文本之后,编辑 *src/main.rs* 并添加读取文件的代码,如示例 12-4 所示
<span class="filename">文件名src/main.rs</span>
@ -35,4 +34,4 @@
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-04/output.txt}}
```
好的!代码读取并打印出了文件的内容。虽然它还有一些瑕疵:此时 `main` 函数有着多个职能,通常函数只负责一个功能的话会更简洁并易于维护。另一个问题是没有尽可能的处理错误。虽然我们的程序还很小,这些瑕疵并不是什么大问题,不过随着程序功能的丰富,要干净地修复它们就会越来越困难。在开发程序时,及早开始重构是一个良好实践,因为重构少量代码时要容易的多。接下来我们就来进行重构
太好了!这段代码读取并打印出了文件内容。不过这段代码还有几个缺点。目前 `main` 函数承担了多个职责,而一般来说,如果每个函数只负责一个概念,代码会更清晰,也更容易维护。另一个问题是,我们对错误的处理也还不够完善。虽然程序目前还很小,这些缺点还不算大问题,但随着程序变大,要干净地修复它们就会更困难。在开发程序时,尽早开始重构是一种良好实践,因为重构较少的代码总是更容易一些。接下来我们就这么做

@ -1,7 +1,6 @@
## 重构改进模块性和错误处理
<!-- https://github.com/rust-lang/book/blob/main/src/ch12-03-improving-error-handling-and-modularity.md -->
<!-- commit 3a30e4c1fbe641afc066b3af9eb01dcdf5ed8b24 -->
[ch12-03-improving-error-handling-and-modularity.md](https://github.com/rust-lang/book/blob/d7c0e477a22bcb37fdb290c6046058565d6738c2/src/ch12-03-improving-error-handling-and-modularity.md)
为了改善我们的程序这里有四个问题需要修复,而且它们都与程序的组织方式和如何处理潜在错误有关。第一,`main` 现在进行了两个任务:它解析了参数并打开了文件。然而随着 `main` 中的功能持续增加,`main` 函数处理的独立任务也会增加。当函数承担了更多责任,它就更难以推导,更难以测试,并且更难以在不破坏其他部分的情况下做出修改。最好能分离出功能以便每个函数各司其职。

@ -1,7 +1,6 @@
## 采用测试驱动开发完善库的功能
## 采用测试驱动开发增加功能
<!-- https://github.com/rust-lang/book/blob/main/src/ch12-04-testing-the-librarys-functionality.md -->
<!-- commit 3a30e4c1fbe641afc066b3af9eb01dcdf5ed8b24 -->
[ch12-04-testing-the-librarys-functionality.md](https://github.com/rust-lang/book/blob/d7c0e477a22bcb37fdb290c6046058565d6738c2/src/ch12-04-testing-the-librarys-functionality.md)
现在我们将逻辑提取到了 *src/lib.rs* 并将所有的参数解析和错误处理留在了 *src/main.rs* 中,为代码的核心功能编写测试将更加容易。我们可以直接使用多种参数调用函数并检查返回值而无需从命令行运行二进制文件了。

@ -1,7 +1,6 @@
## 处理环境变量
<!-- https://github.com/rust-lang/book/blob/main/src/ch12-05-working-with-environment-variables.md -->
<!-- commit 56ec353290429e6547109e88afea4de027b0f1a9 -->
[ch12-05-working-with-environment-variables.md](https://github.com/rust-lang/book/blob/d7c0e477a22bcb37fdb290c6046058565d6738c2/src/ch12-05-working-with-environment-variables.md)
我们将增加一个额外的功能来改进 `minigrep`:用户可以通过设置环境变量来设置搜索是否是大小写敏感的选项。当然,我们也可以将其设计为一个命令行参数并要求用户每次需要时都加上它,不过在这里我们将使用环境变量。这允许用户设置环境变量一次之后在整个终端会话中所有的搜索都将是大小写不敏感的。
@ -45,35 +44,35 @@
{{#include ../listings/ch12-an-io-project/listing-12-21/output.txt}}
```
太好了!测试都通过了。现在,让我们在 `run` 函数中实际调用新 `search_case_insensitive` 函数。首先,我们将在 `Config` 结构体中增加一个配置项来切换大小写敏感和大小写不敏感搜索。增加这些字段会导致编译错误,因为我们还没有在任何地方初始化这些字段
太好了!测试都通过了。现在,让我们在 `run` 函数中实际调用新 `search_case_insensitive` 函数。首先,我们将在 `Config` 结构体中增加一个配置项来切换大小写敏感和大小写不敏感搜索。增加这个字段会导致编译错误,因为我们还没有在任何地方初始化它
<span class="filename">文件名src/lib.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-22/src/lib.rs:here}}
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-22/src/main.rs:here}}
```
这里增加了 `ignore_case` 字段来存放一个布尔值。接着我们需要 `run` 函数检查 `case_sensitive` 字段的值并使用它来决定是否调用 `search` 函数或 `search_case_insensitive` 函数,如示例 12-22 所示。注意这还不能编译:
这里增加了 `ignore_case` 字段来存放一个布尔值。接着我们需要 `run` 函数检查 `ignore_case` 字段的值,并用它来决定是否调用 `search` 函数或 `search_case_insensitive` 函数,如示例 12-22 所示。注意这还不能编译:
<span class="filename">文件名src/lib.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-22/src/lib.rs:there}}
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-22/src/main.rs:there}}
```
<span class="caption">示例 12-22根据 `config.ignore_case` 的值调用 `search``search_case_insensitive`</span>
最后需要实际检查环境变量。处理环境变量的函数位于标准库的 `env` 模块中,所以我们需要在 *src/lib.rs* 的开头将这个模块引入作用域中。接着使用 `env` 模块的 `var` 方法来检查一个叫做 `IGNORE_CASE` 的环境变量,如示例 12-23 所示:
最后需要实际检查环境变量。处理环境变量的函数位于标准库的 `env` 模块中,而这个模块已经在 *src/main.rs* 顶部被引入了作用域。接着使用 `env` 模块的 `var` 方法来检查一个叫做 `IGNORE_CASE` 的环境变量,如示例 12-23 所示:
<span class="filename">文件名src/lib.rs</span>
<span class="filename">文件名src/main.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-23/src/lib.rs:here}}
{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-23/src/main.rs:here}}
```
<span class="caption">示例 12-23检查叫做 `IGNORE_CASE` 的环境变量</span>
这里创建了一个新变量 `ignore_case`。为了设置它的值,需要调用 `env::var` 函数并传递我们需要寻找的环境变量名称,`IGNORE_CASE`。`env::var` 返回一个 `Result`,它在环境变量被设置时返回包含其值的 `Ok` 变体,并在环境变量未被设置时返回 `Err` 变体。
这里创建了一个新变量 `ignore_case`。为了设置它的值,需要调用 `env::var` 函数,并把环境变量 `IGNORE_CASE` 的名字传给它。`env::var` 返回一个 `Result`:如果环境变量被设置成任意值,就返回包含该值的成功 `Ok` 变体;如果环境变量没有被设置,就返回 `Err` 变体。
我们使用 `Result``is_ok` 方法来检查环境变量是否被设置,这也就意味着程序应该进行一个大小写不敏感的搜索。如果 `IGNORE_CASE` 环境变量没有被设置为任何值,`is_ok` 会返回 `false` 并将进行大小写敏感的搜索。我们并不关心环境变量所设置的**值**,只关心它是否被设置了,所以检查 `is_ok` 而不是 `unwrap`、`expect` 或任何我们已经见过的 `Result` 的方法。

@ -1,7 +1,6 @@
## 将错误信息输出到标准错误而不是标准输出
## 将错误重定向到标准错误
<!-- https://github.com/rust-lang/book/blob/main/src/ch12-06-writing-to-stderr-instead-of-stdout.md -->
<!-- commit 3a30e4c1fbe641afc066b3af9eb01dcdf5ed8b24 -->
[ch12-06-writing-to-stderr-instead-of-stdout.md](https://github.com/rust-lang/book/blob/d7c0e477a22bcb37fdb290c6046058565d6738c2/src/ch12-06-writing-to-stderr-instead-of-stdout.md)
目前为止,我们将所有的输出都通过 `println!` 写到了终端。大部分终端都提供了两种输出:**标准输出***standard output*`stdout`)对应一般信息,**标准错误***standard error*`stderr`)则用于错误信息。这种区别允许用户选择将程序正常输出定向到一个文件中并仍将错误信息打印到屏幕上。
@ -67,6 +66,6 @@ How dreary to be somebody!
## 总结
在这一章中,我们回顾了目前为止的一些主要章节并涉及了如何在 Rust 环境中进行常规的 I/O 操作。通过使用命令行参数、文件、环境变量和打印错误的 `eprintln!` 宏,现在你已经准备好编写命令行程序了。通过结合前几章的知识,你的代码将会是组织良好的,并能有效的将数据存储到合适的数据结构中、更好的处理错误,并且还是经过良好测试的
这一章回顾了迄今为止你学到的一些主要概念,并介绍了如何在 Rust 中执行常见的 I/O 操作。通过使用命令行参数、文件、环境变量和用于打印错误的 `eprintln!` 宏,你现在已经准备好编写命令行程序了。结合前几章的知识,你的代码将会组织良好,能将数据有效地存储在合适的数据结构中,能妥善处理错误,并且经过良好测试
接下来,让我们探索一些 Rust 中受函数式编程语言影响的功能:闭包和迭代器。

Loading…
Cancel
Save