update ch11

pull/685/head
KaiserY 2 years ago
parent 1043cdee68
commit a14433ba5f

@ -2,7 +2,7 @@
> [ch11-01-writing-tests.md](https://github.com/rust-lang/book/blob/main/src/ch11-01-writing-tests.md)
> <br>
> commit b9a473ff80e72ed9a77f97a80799b5aff25b594a
> commit 6e2fe7c0f085989cc498cec139e717e2af172cb7
Rust 中的测试函数是用来验证非测试代码是否是按照期望的方式运行的。测试函数体通常执行如下三种操作:
@ -178,9 +178,9 @@ Cargo 编译并运行了测试。可以看到 `running 1 test` 这一行。下
{{#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
在代码中引入一个 bug 来看看使用 `assert_eq!` 的测试失败是什么样的。修改 `add_two` 函数的实现使其加 `3`
```rust,not_desired_behavior,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-04-bug-in-add-two/src/lib.rs:here}}
@ -192,9 +192,9 @@ Cargo 编译并运行了测试。可以看到 `running 1 test` 这一行。下
{{#include ../listings/ch11-writing-automated-tests/no-listing-04-bug-in-add-two/output.txt}}
```
测试捕获到了 bug`it_adds_two` 测试失败,显示信息 `` assertion failed: `(left == right)` `` 并表明 `left``4``right``5`。这个信息有助于我们开始调试:它说 `assert_eq!``left` 参数是 `4`,而 `right` 参数,也就是 `add_two(2)` 的结果,是 `5`
测试捕获到了 bug`it_adds_two` 测试失败,错误信息告诉我们断言失败了,它告诉我们 `` assertion failed: `(left == right)` `` 以及 `left``right` 的值是什么。这个错误信息有助于我们开始调试:它说 `assert_eq!``left` 参数是 `4`,而 `right` 参数,也就是 `add_two(2)` 的结果,是 `5`可以想象当有很多测试在运行时这些信息是多么的有用。
需要注意的是,在一些语言和测试框架中,断言两个值相等的函数的参数叫做 `expected``actual`,而且指定参数的顺序是很关键的。然而在 Rust 中,他们则叫做 `left``right`,同时指定期望的值和被测试代码产生的值的顺序并不重要。这个测试中的断言也可以写成 `assert_eq!(add_two(2), 4)`,这时失败信息会变成 `` assertion failed: `(left == right)` `` 其中 `left``5``right``4`
需要注意的是,在一些语言和测试框架中,断言两个值相等的函数的参数叫做 `expected``actual`,而且指定参数的顺序是很关键的。然而在 Rust 中,他们则叫做 `left``right`,同时指定期望的值和被测试代码产生的值的顺序并不重要。这个测试中的断言也可以写成 `assert_eq!(add_two(2), 4)`,这时失败信息会变成 `` assertion failed: `(left == right)` ``。
`assert_ne!` 宏在传递给它的两个值不相等时通过,而在相等时失败。在代码按预期运行,我们不确定值 **会** 是什么,不过能确定值绝对 **不会** 是什么的时候,这个宏最有用处。例如,如果一个函数保证会以某种方式改变其输出,不过这种改变方式是由运行测试时是星期几来决定的,这时最好的断言可能就是函数的输出不等于其输入。
@ -242,7 +242,7 @@ Cargo 编译并运行了测试。可以看到 `running 1 test` 这一行。下
### 使用 `should_panic` 检查 panic
除了检查代码是否返回期望的正确的值之外,检查代码是否按照期望处理错误也是很重要的。例如,考虑第九章示例 9-10 创建的 `Guess` 类型。其他使用 `Guess` 的代码都是基于 `Guess` 实例仅有的值范围在 1 到 100 的前提。可以编写一个测试来确保创建一个超出范围的值的 `Guess` 实例会 panic。
除了检查返回值之外,检查代码是否按照期望处理错误也是很重要的。例如,考虑第九章示例 9-10 创建的 `Guess` 类型。其他使用 `Guess` 的代码都是基于 `Guess` 实例仅有的值范围在 1 到 100 的前提。可以编写一个测试来确保创建一个超出范围的值的 `Guess` 实例会 panic。
可以通过对函数增加另一个属性 `should_panic` 来实现这些。这个属性在函数中的代码 panic 时会通过,而在其中的代码没有 panic 时失败。
@ -276,7 +276,7 @@ Cargo 编译并运行了测试。可以看到 `running 1 test` 这一行。下
这回并没有得到非常有用的信息,不过一旦我们观察测试函数,会发现它标注了 `#[should_panic]`。这个错误意味着代码中测试函数 `Guess::new(200)` 并没有产生 panic。
然而 `should_panic` 测试结果可能会非常含糊不清,因为它只是告诉我们代码并没有产生 panic。`should_panic` 甚至在一些不是我们期望的原因而导致 panic 时也会通过。为了使 `should_panic` 测试结果更精确,我们可以给 `should_panic` 属性增加一个可选的 `expected` 参数。测试工具会确保错误信息中包含其提供的文本。例如,考虑示例 11-9 中修改过的 `Guess`,这里 `new` 函数根据其值是过大还或者过小而提供不同的 panic 信息:
然而 `should_panic` 测试结果可能会非常含糊不清。`should_panic` 甚至在一些不是我们期望的原因而导致 panic 时也会通过。为了使 `should_panic` 测试结果更精确,我们可以给 `should_panic` 属性增加一个可选的 `expected` 参数。测试工具会确保错误信息中包含其提供的文本。例如,考虑示例 11-9 中修改过的 `Guess`,这里 `new` 函数根据其值是过大还或者过小而提供不同的 panic 信息:
<span class="filename">文件名src/lib.rs</span>

@ -2,11 +2,11 @@
> [ch11-02-running-tests.md](https://github.com/rust-lang/book/blob/main/src/ch11-02-running-tests.md)
> <br>
> commit 1721a106f78c037ca3074d9c2d5a8cf9c9852cf7
> commit 34314c10f699cc882d4e0b06f2a24bd37a5435f2
就像 `cargo run` 会编译代码并运行生成的二进制文件一样,`cargo test` 在测试模式下编译代码并运行生成的测试二进制文件。可以指定命令行参数来改变 `cargo test` 的默认行为。例如,`cargo test` 生成的二进制文件的默认行为是并行的运行所有测试,并截获测试运行过程中产生的输出,阻止他们被显示出来,使得阅读测试结果相关的内容变得更容易。
就像 `cargo run` 会编译代码并运行生成的二进制文件一样,`cargo test` 在测试模式下编译代码并运行生成的测试二进制文件。`cargo test` 产生的二进制文件的默认行为是并发运行所有的测试,并截获测试运行过程中产生的输出,阻止他们被显示出来,使得阅读测试结果相关的内容变得更容易。不过可以指定命令行参数来改变 `cargo test` 的默认行为。
可以将一部分命令行参数传递给 `cargo test`,而将另外一部分传递给生成的测试二进制文件。为了分隔这两种参数,需要先列出传递给 `cargo test` 的参数,接着是分隔符 `--`,再之后是传递给测试二进制文件的参数。运行 `cargo test --help` 会提示 `cargo test` 的有关参数,而运行 `cargo test -- --help` 可以提示在分隔符 `--` 之后使用的有关参数。
可以将一部分命令行参数传递给 `cargo test`,而将另外一部分传递给生成的测试二进制文件。为了分隔这两种参数,需要先列出传递给 `cargo test` 的参数,接着是分隔符 `--`,再之后是传递给测试二进制文件的参数。运行 `cargo test --help` 会提示 `cargo test` 的有关参数,而运行 `cargo test -- --help` 可以提示在分隔符之后使用的有关参数。
### 并行或连续的运行测试
@ -84,7 +84,7 @@ $ cargo test -- --show-output
{{#include ../listings/ch11-writing-automated-tests/output-only-02-single-test/output.txt}}
```
只有名称为 `one_hundred` 的测试被运行了;因为其余两个测试并不匹配这个名称。测试输出在摘要行的结尾显示了 `2 filtered out` 表明还存在比本次所运行的测试更多的测试被过滤掉了
只有名称为 `one_hundred` 的测试被运行了;因为其余两个测试并不匹配这个名称。测试输出在摘要行的结尾显示了 `2 filtered out` 表明还存在比本次所运行的测试更多的测试没有被运行
不能像这样指定多个测试名称;只有传递给 `cargo test` 的第一个值才会被使用。不过有运行多个测试的方法。

@ -2,7 +2,7 @@
> [ch11-03-test-organization.md](https://github.com/rust-lang/book/blob/main/src/ch11-03-test-organization.md)
> <br>
> commit cfb2c3cce7c20d4ad523dafdbf90ae3b25b1ba2c
> commit 654d8902d380dbb8dd94ed2e548dfc0aa80c07cb
本章一开始就提到测试是一个复杂的概念而且不同的开发者也采用不同的技术和组织。Rust 社区倾向于根据测试的两个主要分类来考虑问题:**单元测试***unit tests*)与 **集成测试***integration tests*)。单元测试倾向于更小而更集中,在隔离的环境中一次测试一个模块,或者是测试私有接口。而集成测试对于你的库来说则完全是外部的。它们与其他外部代码一样,通过相同的方式使用你的代码,只测试公有接口而且每个测试都有可能会测试多个模块。
@ -48,7 +48,19 @@
为了编写集成测试,需要在项目根目录创建一个 *tests* 目录,与 *src* 同级。Cargo 知道如何去寻找这个目录中的集成测试文件。接着可以随意在这个目录中创建任意多的测试文件Cargo 会将每一个文件当作单独的 crate 来编译。
让我们来创建一个集成测试。保留示例 11-12 中 *src/lib.rs* 的代码。创建一个 *tests* 目录,新建一个文件 *tests/integration_test.rs*,并输入示例 11-13 中的代码。
让我们来创建一个集成测试。保留示例 11-12 中 *src/lib.rs* 的代码。创建一个 *tests* 目录,新建一个文件 *tests/integration_test.rs*。目录结构应该看起来像这样:
```text
adder
├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
└── integration_test.rs
```
将示例 11-13 中的代码输入到 *tests/integration_test.rs* 文件中。
<span class="filename">文件名tests/integration_test.rs</span>
@ -58,7 +70,7 @@
<span class="caption">示例 11-13一个 `adder` crate 中函数的集成测试</span>
与单元测试不同,我们需要在文件顶部添加 `use adder`。这是因为每一个 `tests` 目录中的测试文件都是完全独立的 crate所以需要在每一个文件中导入库。
因为每一个 `tests` 目录中的测试文件都是完全独立的 crate所以需要在每一个文件中导入库。为此与单元测试不同,我们需要在文件顶部添加 `use adder`
并不需要将 *tests/integration_test.rs* 中的任何代码标注为 `#[cfg(test)]``tests` 文件夹在 Cargo 中是一个特殊的文件夹Cargo 只会在运行 `cargo test` 时编译这个目录中的文件。现在就运行 `cargo test` 试试:
@ -66,11 +78,13 @@
{{#include ../listings/ch11-writing-automated-tests/listing-11-13/output.txt}}
```
现在有了三个部分的输出:单元测试、集成测试和文档测试。第一部分单元测试与我们之前见过的一样:每个单元测试一行(示例 11-12 中有一个叫做 `internal` 的测试),接着是一个单元测试的摘要行。
现在有了三个部分的输出:单元测试、集成测试和文档测试。注意如果一个部分的任何测试失败,之后的部分都不会运行。例如如果一个单元测试失败,则不会有任何集成测试和文档测试的输出,因为这些测试只会在所有单元测试都通过后才会执行。
第一部分单元测试与我们之前见过的一样:每个单元测试一行(示例 11-12 中有一个叫做 `internal` 的测试),接着是一个单元测试的摘要行。
集成测试部分以行 `Running target/debug/deps/integration-test-ce99bcc2479f4607`(在输出最后的哈希值可能不同)开头。接下来每一行是一个集成测试中的测试函数,以及一个位于 `Doc-tests adder` 部分之前的集成测试的摘要行。
集成测试部分以行 `Running tests/integration_test.rs`开头。接下来每一行是一个集成测试中的测试函数,以及一个位于 `Doc-tests adder` 部分之前的集成测试的摘要行。
我们已经知道,单元测试函数越多,单元测试部分的结果行就会越多。同样的,在集成文件中增加的测试函数越多,也会在对应的测试结果部分增加越多的结果行。每一个集成测试文件有对应的测试结果部分,所以如果在 *tests* 目录中增加更多文件,测试结果中就会有更多集成测试结果部分。
每一个集成测试文件有对应的测试结果部分,所以如果在 *tests* 目录中增加更多文件,测试结果中就会有更多集成测试结果部分。
我们仍然可以通过指定测试函数的名称作为 `cargo test` 的参数来运行特定集成测试。也可以使用 `cargo test``--test` 后跟文件的名称来运行某个特定集成测试文件中的所有测试:
@ -86,11 +100,11 @@
将每个集成测试文件当作其自己的 crate 来对待,这更有助于创建单独的作用域,这种单独的作用域能提供更类似与最终使用者使用 crate 的环境。然而,正如你在第七章中学习的如何将代码分为模块和文件的知识,*tests* 目录中的文件不能像 *src* 中的文件那样共享相同的行为。
当你有一些在多个集成测试文件都会用到的帮助函数,而你尝试按照第七章 “将模块移动到其他文件” 部分的步骤将他们提取到一个通用的模块中时, *tests* 目录中不同文件的行为就会显得很明显。例如,如果我们可以创建 一个*tests/common.rs* 文件并创建一个名叫 `setup` 的函数,我们希望这个函数能被多个测试文件的测试函数调用:
当你有一些在多个集成测试文件都会用到的帮助函数,而你尝试按照第七章 [“将模块移动到其他文件”][separating-modules-into-files] 部分的步骤将他们提取到一个通用的模块中时, *tests* 目录中不同文件的行为就会显得很明显。例如,如果我们可以创建 一个*tests/common.rs* 文件并创建一个名叫 `setup` 的函数,我们希望这个函数能被多个测试文件的测试函数调用:
<span class="filename">文件名tests/common.rs</span>
```rust
```rust,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-12-shared-test-code-problem/tests/common.rs}}
```
@ -102,7 +116,20 @@
我们并不想要`common` 出现在测试结果中显示 `running 0 tests` 。我们只是希望其能被其他多个集成测试文件中调用罢了。
为了不让 `common` 出现在测试输出中,我们将创建 *tests/common/mod.rs* ,而不是创建 *tests/common.rs* 。这是一种 Rust 的命名规范,这样命名告诉 Rust 不要将 `common` 看作一个集成测试文件。将 `setup` 函数代码移动到 *tests/common/mod.rs* 并删除 *tests/common.rs* 文件之后,测试输出中将不会出现这一部分。*tests* 目录中的子目录不会被作为单独的 crate 编译或作为一个测试结果部分出现在测试输出中。
为了不让 `common` 出现在测试输出中,我们将创建 *tests/common/mod.rs* ,而不是创建 *tests/common.rs* 。现在项目目录结构看起来像这样:
```text
├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
├── common
│   └── mod.rs
└── integration_test.rs
```
这是一种老的命名规范,正如第七章 [“另一种文件路径”][alt-paths] 中提到的 Rust 仍然理解它们。这样命名告诉 Rust 不要将 `common` 看作一个集成测试文件。将 `setup` 函数代码移动到 *tests/common/mod.rs* 并删除 *tests/common.rs* 文件之后,测试输出中将不会出现这一部分。*tests* 目录中的子目录不会被作为单独的 crate 编译或作为一个测试结果部分出现在测试输出中。
一旦拥有了 *tests/common/mod.rs*,就可以将其作为模块以便在任何集成测试文件中使用。这里是一个 *tests/integration_test.rs* 中调用 `setup` 函数的 `it_adds_two` 测试的例子:
@ -129,3 +156,4 @@ Rust 的测试功能提供了一个确保即使你改变了函数的实现方式
[paths]: ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html
[separating-modules-into-files]:
ch07-05-separating-modules-into-different-files.html
[alt-paths]: ch07-05-separating-modules-into-different-files.html#alternate-file-paths

Loading…
Cancel
Save