update ch11-00 & ch11-01 wip

This commit is contained in:
KaiserY 2023-01-19 23:32:22 +08:00
parent 3a3d8b3d42
commit 6031abb39f
2 changed files with 19 additions and 16 deletions

View File

@ -2,13 +2,13 @@
> [ch11-00-testing.md](https://github.com/rust-lang/book/blob/main/src/ch11-00-testing.md)
> <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit 765318b844569a642ceef7bf1adab9639cbf6af3
Edsger W. Dijkstra 在其 1972 年的文章【谦卑的程序员】“The Humble Programmer”中说到 “软件测试是证明 bug 存在的有效方法而证明其不存在时则显得令人绝望的不足。”“Program testing can be a very effective way to show the presence of bugs, but it is hopelessly inadequate for showing their absence.”)这并不意味着我们不该尽可能地测试软件!
程序的正确性意味着代码如我们期望的那样运行。Rust 是一个相当注重正确性的编程语言不过正确性是一个难以证明的复杂主题。Rust 的类型系统在此问题上下了很大的功夫,不过它不可能捕获所有种类的错误。为此Rust 也在语言本身包含了编写软件测试的支持。
程序的正确性意味着代码如我们期望的那样运行。Rust 是一个相当注重正确性的编程语言不过正确性是一个难以证明的复杂主题。Rust 的类型系统在此问题上下了很大的功夫,不过类型系统不可能捕获所有问题。为此Rust 包含了编写自动化软件测试的功能支持。
例如,我们可以编写一个叫做 `add_two` 的将传递给它的值加二的函数。它的签名有一个整型参数并返回一个整型值。当实现和编译这个函数时Rust 会进行所有目前我们已经见过的类型检查和借用检查,例如,这些检查会确保我们不会传递 `String` 或无效的引用给这个函数。Rust 所 **不能** 检查的是这个函数是否会准确的完成我们期望的工作:返回参数加二后的值,而不是比如说参数加 10 或减 50 的值!这也就是测试出场的地方。
假设我们可以编写一个叫做 `add_two` 的将传递给它的值加二的函数。它的签名有一个整型参数并返回一个整型值。当实现和编译这个函数时Rust 会进行所有目前我们已经见过的类型检查和借用检查,例如,这些检查会确保我们不会传递 `String` 或无效的引用给这个函数。Rust 所 **不能** 检查的是这个函数是否会准确的完成我们期望的工作:返回参数加二后的值,而不是比如说参数加 10 或减 50 的值!这也就是测试出场的地方。
我们可以编写测试断言,比如说,当传递 `3``add_two` 函数时,返回值是 `5`。无论何时对代码进行修改,都可以运行测试来确保任何现存的正确行为没有被改变。

View File

@ -14,13 +14,13 @@ Rust 中的测试函数是用来验证非测试代码是否是按照期望的方
### 测试函数剖析
作为最简单例子Rust 中的测试就是一个带有 `test` 属性注解的函数。属性attribute是关于 Rust 代码片段的元数据;第五章中结构体中用到的 `derive` 属性就是一个例子。为了将一个函数变成测试函数,需要在 `fn` 行之前加上 `#[test]`。当使用 `cargo test` 命令运行测试时Rust 会构建一个测试执行程序用来调用标记了 `test` 属性的函数,并报告每一个测试是通过还是失败。
作为最简单例子Rust 中的测试就是一个带有 `test` 属性注解的函数。属性attribute是关于 Rust 代码片段的元数据;第五章中结构体中用到的 `derive` 属性就是一个例子。为了将一个函数变成测试函数,需要在 `fn` 行之前加上 `#[test]`。当使用 `cargo test` 命令运行测试时Rust 会构建一个测试执行程序用来调用被标注的函数,并报告每一个测试是通过还是失败。
第七章当使用 Cargo 新建一个库项目时,它会自动为我们生成一个测试模块和一个测试函数。这有助于我们开始编写测试,因为这样每次开始新项目时不必去查找测试函数的具体结构和语法了。当然你也可以额外增加任意多的测试函数以及测试模块!
每次使用 Cargo 新建一个库项目时,它会自动为我们生成一个测试模块和一个测试函数。这个模块提供了一个编写测试的模板,为此每次开始新项目时不必去查找测试函数的具体结构和语法了。因为这样当然你也可以额外增加任意多的测试函数以及测试模块!
在实际编写测试代码之前,让我们先通过尝试那些自动生成的测试模版来探索测试是如何工作的。接着,我们会写一些真正的测试,调用我们编写的代码并断言他们的行为的正确性。
让我们创建一个新的库项目 `adder`
让我们创建一个新的库项目 `adder`,它会将两个数字相加
```console
$ cargo new adder --lib
@ -38,9 +38,9 @@ adder 库中 `src/lib.rs` 的内容应该看起来如示例 11-1 所示:
<span class="caption">示例 11-1`cargo new` 自动生成的测试模块和函数</span>
现在让我们暂时忽略 `tests` 模块和 `#[cfg(test)]` 注解,并只关注函数来了解其如何工作。注意 `fn` 行之前的 `#[test]`:这个属性表明这是一个测试函数,这样测试执行者就知道将其作为测试处理。因为也可以在 `tests` 模块中拥有非测试的函数来帮助我们建立通用场景或进行常见操作,所以需要使用 `#[test]` 属性标明哪些函数是测试。
现在让我们暂时忽略 `tests` 模块和 `#[cfg(test)]` 注解并只关注函数本身。注意 `fn` 行之前的 `#[test]`:这个属性表明这是一个测试函数,这样测试执行者就知道将其作为测试处理。`tests` 模块中也可以有非测试的函数来帮助我们建立通用场景或进行常见操作,必须每次都标明哪些函数是测试。
函数体通过使用 `assert_eq!` 宏来断言 2 加 2 等于 4。一个典型的测试的格式就是像这个例子中的断言一样。接下来运行就可以看到测试通过。
示例函数体通过使用 `assert_eq!` 宏来断言 2 加 2 等于 4。一个典型的测试的格式就是像这个例子中的断言一样。接下来运行就可以看到测试通过。
`cargo test` 命令会运行项目中所有的测试,如示例 11-2 所示:
@ -50,17 +50,17 @@ adder 库中 `src/lib.rs` 的内容应该看起来如示例 11-1 所示:
<span class="caption">示例 11-2运行自动生成测试的输出</span>
Cargo 编译并运行了测试。`Compiling`、`Finished` 和 `Running` 这几行之后,可以看到 `running 1 test` 这一行。下一行显示了生成的测试函数的名称,它是 `it_works`,以及测试的运行结果,`ok`。接着可以看到全体测试运行结果的摘要:`test result: ok.` 意味着所有测试都通过了。`1 passed; 0 failed` 表示通过或失败的测试数量。
Cargo 编译并运行了测试。可以看到 `running 1 test` 这一行。下一行显示了生成的测试函数的名称,它是 `it_works`,以及测试的运行结果,`ok`。接着可以看到全体测试运行结果的摘要:`test result: ok.` 意味着所有测试都通过了。`1 passed; 0 failed` 表示通过或失败的测试数量。
因为之前我们并没有将任何测试标记为忽略,所以摘要中会显示 `0 ignored`。我们也没有过滤需要运行的测试,所以摘要中会显示`0 filtered out`。在下一部分 [“控制测试如何运行”][controlling-how-tests-are-run] 会讨论忽略和过滤测试。
可以将一个测试标记为忽略这样在特定情况下它就不会运行;本章之后的[“除非特别指定否则忽略某些测试”][ignoring]部分会介绍它。因为之前我们并没有将任何测试标记为忽略,所以摘要中会显示 `0 ignored`。我们也没有过滤需要运行的测试,所以摘要中会显示`0 filtered out`。在下一部分 [“控制测试如何运行”][controlling-how-tests-are-run] 会讨论忽略和过滤测试。
`0 measured` 统计是针对性能测试的。性能测试benchmark tests在编写本书时仍只能用于 Rust 开发版nightly Rust。请查看 [性能测试的文档][bench] 了解更多。
[bench]: https://doc.rust-lang.org/unstable-book/library-features/test.html
测试输出中的以 `Doc-tests adder` 开头的这一部分是所有文档测试的结果。我们现在并没有任何文档测试,不过 Rust 会编译任何在 API 文档中的代码示例。这个功能帮助我们使文档和代码保持同步!在第十四章的 [“文档注释作为测试”][doc-comments] 部分会讲到如何编写文档测试。现在我们将忽略 `Doc-tests` 部分的输出。
让我们改变测试的名称并看看这如何改变测试的输出。`it_works` 函数起个不同的名字,比如 `exploration`,像这样:
让我们开始自定义测试来满足我们的需求。首先`it_works` 函数起个不同的名字,比如 `exploration`,像这样:
<span class="filename">文件名src/lib.rs</span>
@ -74,7 +74,7 @@ Cargo 编译并运行了测试。在 `Compiling`、`Finished` 和 `Running` 这
{{#include ../listings/ch11-writing-automated-tests/no-listing-01-changing-test-name/output.txt}}
```
让我们增加另一个测试,不过这一次是一个会失败的测试!当测试函数中出现 panic 时测试就失败了。每一个测试都在一个新线程中运行,当主线程发现测试线程异常了,就将对应测试标记为失败。第九章讲到了最简单的造成 panic 的方法:调用 `panic!` 宏。写入新测试 `another` 后, `src/lib.rs` 现在看起来如示例 11-3 所示:
现在让我们增加另一个测试,不过这一次是一个会失败的测试!当测试函数中出现 panic 时测试就失败了。每一个测试都在一个新线程中运行,当主线程发现测试线程异常了,就将对应测试标记为失败。第九章讲到了最简单的造成 panic 的方法:调用 `panic!` 宏。写入新测试 `another` 后, `src/lib.rs` 现在看起来如示例 11-3 所示:
<span class="filename">文件名src/lib.rs</span>
@ -92,7 +92,7 @@ Cargo 编译并运行了测试。在 `Compiling`、`Finished` 和 `Running` 这
<span class="caption">示例 11-4一个测试通过和一个测试失败的测试结果</span>
`test tests::another` 这一行是 `FAILED` 而不是 `ok` 了。在单独测试结果和摘要之间多了两个新的部分:第一个部分显示了测试失败的详细原因。在这个例子中,`another` 因为在*src/lib.rs* 的第 10 行 `panicked at 'Make this test fail'` 而失败。下一部分列出了所有失败的测试,这在有很多测试和很多失败测试的详细输出时很有帮助。我们可以通过使用失败测试的名称来只运行这个测试,以便调试;下一部分 [“控制测试如何运行”][controlling-how-tests-are-run] 会讲到更多运行测试的方法。
`test tests::another` 这一行是 `FAILED` 而不是 `ok` 了。在单独测试结果和摘要之间多了两个新的部分:第一个部分显示了测试失败的详细原因。在这个例子中,我们看到 `another` 因为在 *src/lib.rs* 的第 10 行 `panicked at 'Make this test fail'` 而失败的详细信息。下一部分列出了所有失败的测试,这在有很多测试和很多失败测试的详细输出时很有帮助。我们可以通过使用失败测试的名称来只运行这个测试,以便调试;下一部分 [“控制测试如何运行”][controlling-how-tests-are-run] 会讲到更多运行测试的方法。
最后是摘要行:总体上讲,测试结果是 `FAILED`。有一个测试通过和一个测试失败。
@ -160,7 +160,7 @@ Cargo 编译并运行了测试。在 `Compiling`、`Finished` 和 `Running` 这
### 使用 `assert_eq!``assert_ne!` 宏来测试相等
测试功能的一个常用方法是将需要测试代码的值与期望值做比较,并检查是否相等。可以通过向 `assert!` 宏传递一个使用 `==` 运算符的表达式来做到。不过这个操作实在是太常见了,以至于标准库提供了一对宏来更方便的处理这些操作 —— `assert_eq!``assert_ne!`。这两个宏分别比较两个值是相等还是不相等。当断言失败时他们也会打印出这两个值具体是什么,以便于观察测试 **为什么** 失败,而 `assert!` 只会打印出它从 `==` 表达式中得到了 `false` 值,而不是导致 `false` 的两个值。
测试功能的一个常用方法是将需要测试代码的值与期望值做比较,并检查是否相等。可以通过向 `assert!` 宏传递一个使用 `==` 运算符的表达式来做到。不过这个操作实在是太常见了,以至于标准库提供了一对宏来更方便的处理这些操作 —— `assert_eq!``assert_ne!`。这两个宏分别比较两个值是相等还是不相等。当断言失败时他们也会打印出这两个值具体是什么,以便于观察测试 **为什么** 失败,而 `assert!` 只会打印出它从 `==` 表达式中得到了 `false` 值,而不是打印导致 `false` 的两个值。
示例 11-7 中,让我们编写一个对其参数加二并返回结果的函数 `add_two`。接着使用 `assert_eq!` 宏测试这个函数。
@ -178,7 +178,7 @@ Cargo 编译并运行了测试。在 `Compiling`、`Finished` 和 `Running` 这
{{#include ../listings/ch11-writing-automated-tests/listing-11-07/output.txt}}
```
传递给 `assert_eq!` 宏的第一个参数 `4` ,等于调用 `add_two(2)` 的结果。测试中的这一行 `test tests::it_adds_two ... ok``ok` 表明测试通过!
我们传递给 `assert_eq!` 宏的第一个参数 `4` ,等于调用 `add_two(2)` 的结果。测试中的这一行 `test tests::it_adds_two ... ok``ok` 表明测试通过!
在代码中引入一个 bug 来看看使用 `assert_eq!` 的测试失败是什么样的。修改 `add_two` 函数的实现使其加 3
@ -320,6 +320,9 @@ Cargo 编译并运行了测试。在 `Compiling`、`Finished` 和 `Running` 这
[concatenation-with-the--operator-or-the-format-macro]:
ch08-02-strings.html#使用--运算符或-format-宏拼接字符串
[bench]: https://doc.rust-lang.org/unstable-book/library-features/test.html
[ignoring]: ch11-02-running-tests.html#ignoring-some-tests-unless-specifically-requested
[subset]: ch11-02-running-tests.html#running-a-subset-of-tests-by-name
[controlling-how-tests-are-run]:
ch11-02-running-tests.html#控制测试如何运行
[derivable-traits]: appendix-03-derivable-traits.html