pull/736/head
KaiserY 1 year ago
parent d157604fed
commit d2cac68d6c

@ -194,7 +194,7 @@ Cargo 编译并运行了测试。可以看到 `running 1 test` 这一行。下
测试捕获到了 bug`it_adds_two` 测试失败,错误信息告诉我们断言失败了,它告诉我们 `` assertion failed: `(left == right)` `` 以及 `left``right` 的值是什么。这个错误信息有助于我们开始调试:它说 `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)` ``。 需要注意的是,在一些语言和测试框架中,断言两个值相等的函数的参数被称为 `expected``actual`,而且指定参数的顺序非常重要。然而在 Rust 中,他们则叫做 `left``right`,同时指定期望的值和被测试代码产生的值的顺序并不重要。这个测试中的断言也可以写成 `assert_eq!(add_two(2), 4)`,这时失败信息仍同样是 `` assertion failed: `(left == right)` ``。
`assert_ne!` 宏在传递给它的两个值不相等时通过,而在相等时失败。在代码按预期运行,我们不确定值 **会** 是什么,不过能确定值绝对 **不会** 是什么的时候,这个宏最有用处。例如,如果一个函数保证会以某种方式改变其输出,不过这种改变方式是由运行测试时是星期几来决定的,这时最好的断言可能就是函数的输出不等于其输入。 `assert_ne!` 宏在传递给它的两个值不相等时通过,而在相等时失败。在代码按预期运行,我们不确定值 **会** 是什么,不过能确定值绝对 **不会** 是什么的时候,这个宏最有用处。例如,如果一个函数保证会以某种方式改变其输出,不过这种改变方式是由运行测试时是星期几来决定的,这时最好的断言可能就是函数的输出不等于其输入。

@ -24,7 +24,7 @@
{{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-01/src/lib.rs}} {{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-01/src/lib.rs}}
``` ```
上述代码就是自动生成的测试模块。`cfg` 属性代表 *configuration* ,它告诉 Rust 其之后的项只应该被包含进特定配置选项中。在这个例子中,配置选项是 `test`,即 Rust 所提供的用于编译和运行测试的配置选项。通过使用 `cfg` 属性Cargo 只会在我们主动使用 `cargo test` 运行测试时才编译测试代码。这包括测试模块中可能存在的帮助函数,以及标注为 #[test] 的函数。 上述代码就是自动生成的测试模块。`cfg` 属性代表*配置**configuration* ,它告诉 Rust接下来的项只有在给定特定配置选项时才会被包含。在这种情况下,配置选项是 `test`,即 Rust 所提供的用于编译和运行测试的配置选项。通过使用 `cfg` 属性Cargo 只会在我们主动使用 `cargo test` 运行测试时才编译测试代码。这包括测试模块中可能存在的帮助函数,以及标注为 `#[test]` 的函数。
#### 测试私有函数 #### 测试私有函数
@ -96,11 +96,9 @@ adder
#### 集成测试中的子模块 #### 集成测试中的子模块
随着集成测试的增加,你可能希望在 `tests` 目录增加更多文件以便更好的组织他们,例如根据测试的功能来将测试分组。正如我们之前提到的,每一个 *tests* 目录中的文件都被编译为单独的 crate 随着集成测试的增加,你可能希望在 `tests` 目录创建更多文件以便更好地组织它们,例如根据测试的功能来将测试分组。如前所述,*tests* 目录中的每一个文件都被编译成一个单独的 crate这有助于创建独立的作用域以便更接近于最终用户使用你的 crate 的方式。但这意味着,*tests* 目录中的文件的行为,和你在第七章中学习如何将代码分为模块和文件时,学到的 *src* 中的文件的行为不一样
将每个集成测试文件当作其自己的 crate 来对待,这更有助于创建单独的作用域,这种单独的作用域能提供更类似与最终使用者使用 crate 的环境。然而,正如你在第七章中学习的如何将代码分为模块和文件的知识,*tests* 目录中的文件不能像 *src* 中的文件那样共享相同的行为。 当你有一些在多个集成测试文件都会用到的帮助函数,而你尝试按照第七章 [“将模块移动到其他文件”][separating-modules-into-files] 部分的步骤将他们提取到一个通用的模块中时, *tests* 目录中文件行为的不同就会凸显出来。例如,如果我们可以创建 一个*tests/common.rs* 文件并创建一个名叫 `setup` 的函数,我们希望这个函数能被多个测试文件的测试函数调用:
当你有一些在多个集成测试文件都会用到的帮助函数,而你尝试按照第七章 [“将模块移动到其他文件”][separating-modules-into-files] 部分的步骤将他们提取到一个通用的模块中时, *tests* 目录中不同文件的行为就会显得很明显。例如,如果我们可以创建 一个*tests/common.rs* 文件并创建一个名叫 `setup` 的函数,我们希望这个函数能被多个测试文件的测试函数调用:
<span class="filename">文件名tests/common.rs</span> <span class="filename">文件名tests/common.rs</span>

@ -146,7 +146,7 @@
通过让 `Config::build` 返回一个 `Err` 值,这就允许 `main` 函数处理 `new` 函数返回的 `Result` 值并在出现错误的情况更明确的结束进程。 通过让 `Config::build` 返回一个 `Err` 值,这就允许 `main` 函数处理 `new` 函数返回的 `Result` 值并在出现错误的情况更明确的结束进程。
#### `Config::build` 调用并处理错误 #### 调用 `Config::build` 并处理错误
为了处理错误情况并打印一个对用户友好的信息,我们需要像示例 12-10 那样更新 `main` 函数来处理现在 `Config::build` 返回的 `Result`。另外还需要手动实现原先由 `panic!`负责的工作,即以非零错误码退出命令行工具的工作。非零的退出状态是一个惯例信号,用来告诉调用程序的进程:该程序以错误状态退出了。 为了处理错误情况并打印一个对用户友好的信息,我们需要像示例 12-10 那样更新 `main` 函数来处理现在 `Config::build` 返回的 `Result`。另外还需要手动实现原先由 `panic!`负责的工作,即以非零错误码退出命令行工具的工作。非零的退出状态是一个惯例信号,用来告诉调用程序的进程:该程序以错误状态退出了。

@ -22,7 +22,7 @@ Rust 的 **闭包***closures*)是可以保存在一个变量中或作为参
`main` 函数中定义的 `store` 还剩有两件蓝衬衫和一件红衬衫可在限量版促销活动中赠送。我们用一个期望获得红衬衫和一个没有期望的用户来调用 `giveaway` 方法。 `main` 函数中定义的 `store` 还剩有两件蓝衬衫和一件红衬衫可在限量版促销活动中赠送。我们用一个期望获得红衬衫和一个没有期望的用户来调用 `giveaway` 方法。
这段代码也可以有多种实现方法。这里为了专注于闭包,我们会坚持使用已经学习过的概念,除了 `giveaway` 方法体中使用了闭包。`giveaway` 方法获取了 `Option<ShirtColor>` 类型作为用户的期望颜色并在 `user_preference` 上调用 `unwrap_or_else` 方法。 [`Option<T>` 上的方法 `unwrap_or_else`][unwrap-or-else] 由标准库定义,它获取一个没有参数、返回值类型为 `T` (与 `Option<T>``Some` 成员所存储的值的类型一样,这里是 `ShirtColor`)的闭包作为参数。如果 `Option<T>``Some` 成员,则 `unwrap_or_else` 返回 `Some` 中的值。 如果 `Option<T>``None` 成员, 则 `unwrap_or_else` 调用闭包并返回闭包的返回值。 再次强调,这段代码可以有多种实现方式。这里为了专注于闭包,我们会继续使用已经学习过的概念,除了 `giveaway` 方法体中使用了闭包。`giveaway` 方法获取了 `Option<ShirtColor>` 类型作为用户的期望颜色并在 `user_preference` 上调用 `unwrap_or_else` 方法。 [`Option<T>` 上的方法 `unwrap_or_else`][unwrap-or-else] 由标准库定义,它获取一个没有参数、返回值类型为 `T` (与 `Option<T>``Some` 成员所存储的值的类型一样,这里是 `ShirtColor`)的闭包作为参数。如果 `Option<T>``Some` 成员,则 `unwrap_or_else` 返回 `Some` 中的值。 如果 `Option<T>``None` 成员, 则 `unwrap_or_else` 调用闭包并返回闭包的返回值。
我们将被闭包表达式 `|| self.most_stocked()` 用作 `unwrap_or_else` 的参数。这是一个本身不获取参数的闭包(如果闭包有参数,它们会出现在两道竖杠之间)。闭包体调用了 `self.most_stocked()`。我们在这里定义了闭包,而 `unwrap_or_else` 的实现会在之后需要其结果的时候执行闭包。 我们将被闭包表达式 `|| self.most_stocked()` 用作 `unwrap_or_else` 的参数。这是一个本身不获取参数的闭包(如果闭包有参数,它们会出现在两道竖杠之间)。闭包体调用了 `self.most_stocked()`。我们在这里定义了闭包,而 `unwrap_or_else` 的实现会在之后需要其结果的时候执行闭包。

@ -44,7 +44,7 @@ for i in 12..buffer.len() {
为了计算 `prediction` 的值,这些代码遍历了 `coefficients` 中的 12 个值,使用 `zip` 方法将系数与 `buffer` 的前 12 个值组合在一起。接着将每一对值相乘,再将所有结果相加,然后将总和右移 `qlp_shift` 位。 为了计算 `prediction` 的值,这些代码遍历了 `coefficients` 中的 12 个值,使用 `zip` 方法将系数与 `buffer` 的前 12 个值组合在一起。接着将每一对值相乘,再将所有结果相加,然后将总和右移 `qlp_shift` 位。
像音频解码器这样的程序通常最看重计算的性能。这里我们创建了一个迭代器使用了两个适配器接着消费了其值。Rust 代码将会被编译为什么样的汇编代码呢?好吧,在编写本书的这个时候,它被编译成与手写的相同的汇编代码。遍历 `coefficients` 的值完全用不到循环Rust 知道这里会迭代 12 次所以它“展开”unroll了循环。展开是一种移除循环控制代码的开销并替换为每个迭代中的重复代码的优化。 像音频解码器这样的程序通常最看重计算的性能。这里,我们创建了一个迭代器,使用了两个适配器,接着消费了其值。那么这段 Rust 代码将会被编译为什么样的汇编代码呢?好吧,在编写本书的这个时候,它被编译成与手写的相同的汇编代码。遍历 `coefficients` 的值完全用不到循环Rust 知道这里会迭代 12 次所以它“展开”unroll了循环。展开是一种移除循环控制代码的开销,并将循环迭代转换为程序中的重复的代码的优化。
所有的系数都被储存在了寄存器中,这意味着访问他们非常快。这里也没有运行时数组访问边界检查。所有这些 Rust 能够提供的优化使得结果代码极为高效。现在知道这些了,请放心大胆的使用迭代器和闭包吧!他们使得代码看起来更高级,但并不为此引入运行时性能损失。 所有的系数都被储存在了寄存器中,这意味着访问他们非常快。这里也没有运行时数组访问边界检查。所有这些 Rust 能够提供的优化使得结果代码极为高效。现在知道这些了,请放心大胆的使用迭代器和闭包吧!他们使得代码看起来更高级,但并不为此引入运行时性能损失。

@ -154,7 +154,7 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini
$ cargo login abcdefghijklmnopqrstuvwxyz012345 $ cargo login abcdefghijklmnopqrstuvwxyz012345
``` ```
这个命令会通知 Cargo 你的 API token 并将其储存在本地的 _~/.cargo/credentials_ 文件中。注意这个 token 是一个 **秘密****secret**)且不应该与其他人共享。如果因为任何原因与他人共享了这个信息,应该立即到 [crates.io](https://crates.io)<!-- ignore --> 重新生成这个 token。 这个命令会通知 Cargo 你的 API token 并将其储存在本地的 _~/.cargo/credentials_ 文件中。注意这个 token 是一个 **秘密****secret**)且不应该与其他人共享。如果因为任何原因与他人共享了这个信息,应该立即到 [crates.io](https://crates.io)<!-- ignore --> 撤销并重新生成一个 token。
### 向新 crate 添加元信息 ### 向新 crate 添加元信息
@ -183,7 +183,7 @@ Caused by:
the remote server responded with an error: missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for how to upload metadata the remote server responded with an error: missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for how to upload metadata
``` ```
这个错误是因为我们缺少一些关键信息:关于该 crate 用途的描述和用户可能在何种条款下使用该 crate 的 license。在 _Cargo.toml_ 中添加描述,通常是一两句话,因为它会出现在 crate 的搜索结果中和 crate 页面里。对于 `license` 字段,你需要一个 **license 标识符值**_license identifier value_。[Linux 基金会的 Software Package Data Exchange (SPDX)][spdx] 列出了可以使用的标识符。例如,为了指定 crate 使用 MIT License增加 `MIT` 标识符: 这个错误是因为我们缺少一些关键信息:关于该 crate 用途的描述和用户可能在何种条款下使用该 crate 的 license。在 _Cargo.toml_ 中添加通常是一两句话的描述,因为它将在搜索结果中和你的 crate 一起显示。对于 `license` 字段,你需要一个 **license 标识符值**_license identifier value_。[Linux 基金会的 Software Package Data Exchange (SPDX)][spdx] 列出了可以使用的标识符。例如,为了指定 crate 使用 MIT License增加 `MIT` 标识符:
<span class="filename">文件名Cargo.toml</span> <span class="filename">文件名Cargo.toml</span>

@ -8,7 +8,7 @@
我们在智能指针上下文中讨论 `Drop` 是因为其功能几乎总是用于实现智能指针。例如,当 `Box<T>` 被丢弃时会释放 box 指向的堆空间。 我们在智能指针上下文中讨论 `Drop` 是因为其功能几乎总是用于实现智能指针。例如,当 `Box<T>` 被丢弃时会释放 box 指向的堆空间。
在其他一些语言中的某些类型,我们不得不记住在每次使用完智能指针实例后调用清理内存或资源的代码。如果忘记的话,运行代码的系统可能会因为负荷过重而崩溃。在 Rust 中,可以指定每当值离开作用域时被执行的代码,编译器会自动插入这些代码。于是我们就不需要在程序中到处编写在实例结束时清理这些变量的代码 —— 而且还不会泄漏资源。 在其他一些语言中的某些类型,我们不得不记住在每次使用完那些类型的智能指针实例后调用清理内存或资源的代码。如果忘记的话,运行代码的系统可能会因为负荷过重而崩溃。在 Rust 中,可以指定每当值离开作用域时被执行的代码,编译器会自动插入这些代码。于是我们就不需要在程序中到处编写在实例结束时清理这些变量的代码 —— 而且还不会泄漏资源。
指定在值离开作用域时应该执行的代码的方式是实现 `Drop` trait。`Drop` trait 要求实现一个叫做 `drop` 的方法,它获取一个 `self` 的可变引用。为了能够看出 Rust 何时调用 `drop`,让我们暂时使用 `println!` 语句实现 `drop` 指定在值离开作用域时应该执行的代码的方式是实现 `Drop` trait。`Drop` trait 要求实现一个叫做 `drop` 的方法,它获取一个 `self` 的可变引用。为了能够看出 Rust 何时调用 `drop`,让我们暂时使用 `println!` 语句实现 `drop`

Loading…
Cancel
Save