wip: 2024 edition

This commit is contained in:
KaiserY 2025-05-22 23:31:25 +08:00
parent cc6e5e7904
commit ad593901e9
3 changed files with 28 additions and 28 deletions

View File

@ -39,13 +39,13 @@
当你的代码在进行一个使用无效值进行调用时可能将用户置于风险中的操作时,代码应该首先验证值是有效的,并在其无效时 `panic!`。这主要是出于安全的原因:尝试操作无效数据会暴露代码漏洞,这就是标准库在尝试越界访问数组时会 `panic!` 的主要原因:尝试访问不属于当前数据结构的内存是一个常见的安全隐患。函数通常都遵循**契约***contracts*):它们的行为只有在输入满足特定条件时才能得到保证。当违反契约时 panic 是有道理的,因为这通常代表调用方的 bug而且这也不是那种你希望所调用的代码必须处理的错误。事实上所调用的代码也没有合理的方式来恢复而是需要调用方的**开发者**修复其代码。函数的契约,尤其是当违反它会造成 panic 的契约,应该在函数的 API 文档中进行说明。 当你的代码在进行一个使用无效值进行调用时可能将用户置于风险中的操作时,代码应该首先验证值是有效的,并在其无效时 `panic!`。这主要是出于安全的原因:尝试操作无效数据会暴露代码漏洞,这就是标准库在尝试越界访问数组时会 `panic!` 的主要原因:尝试访问不属于当前数据结构的内存是一个常见的安全隐患。函数通常都遵循**契约***contracts*):它们的行为只有在输入满足特定条件时才能得到保证。当违反契约时 panic 是有道理的,因为这通常代表调用方的 bug而且这也不是那种你希望所调用的代码必须处理的错误。事实上所调用的代码也没有合理的方式来恢复而是需要调用方的**开发者**修复其代码。函数的契约,尤其是当违反它会造成 panic 的契约,应该在函数的 API 文档中进行说明。
虽然在所有函数中都拥有许多错误检查是冗长而烦人的。幸运的是,可以利用 Rust 的类型系统(以及编译器的类型检查)为你进行很多检查。如果函数有一个特定类型的参数,可以在知晓编译器已经确保其拥有一个有效值的前提下进行你的代码逻辑。例如,如果你使用了一个并不是 `Option` 的类型,则程序期望它是 **有值** 的并且不是 **空值**。你的代码无需处理 `Some``None` 这两种情况,它只会有一种情况就是绝对会有一个值。尝试向函数传递空值的代码甚至根本不能编译,所以你的函数在运行时没有必要判空。另外一个例子是使用像 `u32` 这样的无符号整型,也会确保它永远不为负。 虽然在所有函数中都拥有许多错误检查是冗长而烦人的。幸运的是,可以利用 Rust 的类型系统(以及编译器的类型检查)为你进行很多检查。如果函数有一个特定类型的参数,可以在知晓编译器已经确保其拥有一个有效值的前提下进行你的代码逻辑。例如,如果你使用了一个并不是 `Option` 的类型,则程序期望它是**有值**的并且不是**空值**。你的代码无需处理 `Some``None` 这两种情况,它只会有一种情况就是绝对会有一个值。尝试向函数传递空值的代码甚至根本不能编译,所以你的函数在运行时没有必要判空。另外一个例子是使用像 `u32` 这样的无符号整型,也会确保它永远不为负。
### 创建自定义类型进行有效性验证 ### 创建自定义类型进行有效性验证
让我们使用 Rust 类型系统的思想来进一步确保值的有效性,并尝试创建一个自定义类型以进行验证。回忆一下第二章的猜猜看游戏,我们的代码要求用户猜测一个 1 到 100 之间的数字在将其与秘密数字做比较之前我们从未验证用户的猜测是位于这两个数字之间的我们只验证它是否为正。在这种情况下其影响并不是很严重“Too high” 或 “Too low” 的输出仍然是正确的。但是这是一个很好的引导用户得出有效猜测的辅助,例如当用户猜测一个超出范围的数字或者输入字母时采取不同的行为。 让我们使用 Rust 类型系统的思想来进一步确保值的有效性,并尝试创建一个自定义类型以进行验证。回忆一下第二章的猜猜看游戏,我们的代码要求用户猜测一个 1 到 100 之间的数字在将其与秘密数字做比较之前我们从未验证用户的猜测是位于这两个数字之间的我们只验证它是否为正。在这种情况下其影响并不是很严重“Too high” 或 “Too low” 的输出仍然是正确的。但是这是一个很好的引导用户得出有效猜测的辅助,例如当用户猜测一个超出范围的数字或者输入字母时采取不同的行为。
一种实现方式是将猜测解析成 `i32` 而不仅仅是 `u32`,来默许输入负数,接着检查数字是否在范围内: 一种实现方式是将猜测解析成 `i32` 而不仅仅是 `u32`,来默许输入负数,接着检查数字是否在范围内,像这样
<span class="filename">文件名src/main.rs</span> <span class="filename">文件名src/main.rs</span>
@ -55,9 +55,9 @@
`if` 表达式检查了值是否超出范围,告诉用户出了什么问题,并调用 `continue` 开始下一次循环,请求另一个猜测。`if` 表达式之后,就可以在知道 `guess` 在 1 到 100 之间的情况下与秘密数字作比较了。 `if` 表达式检查了值是否超出范围,告诉用户出了什么问题,并调用 `continue` 开始下一次循环,请求另一个猜测。`if` 表达式之后,就可以在知道 `guess` 在 1 到 100 之间的情况下与秘密数字作比较了。
然而,这并不是一个理想的解决方案:如果让程序仅仅处理 1 到 100 之间的值是一个绝对需要满足的要求,而且程序中的很多函数都有这样的要求,在每个函数中都有这样的检查将是非常冗余的(并可能潜在影响性能)。 然而,这并不是一个理想的解决方案:如果让程序仅仅处理 1 到 100 之间的值是一个绝对需要满足的要求,而且程序中的很多函数都有这样的要求,在每个函数中都有这样的检查将是非常冗余的(并可能潜在影响性能)。
相反我们可以创建一个新类型来将验证放入创建其实例的函数中,而不是到处重复这些检查。这样就可以安全地在函数签名中使用新类型并相信它们接收到的值。示例 9-13 中展示了一个定义 `Guess` 类型的方法,只有在 `new` 函数接收到 1 到 100 之间的值时才会创建 `Guess` 的实例: 相反我们可以在一个专用的模块中创建一个新类型来将验证放入创建其实例的函数中,而不是到处重复这些检查。这样就可以安全地在函数签名中使用新类型并相信它们接收到的值。示例 9-13 中展示了一个定义 `Guess` 类型的方法,只有在 `new` 函数接收到 1 到 100 之间的值时才会创建 `Guess` 的实例:
<span class="filename">文件名src/lib.rs</span> <span class="filename">文件名src/lib.rs</span>
@ -67,13 +67,13 @@
<span class="caption">示例 9-13一个 `Guess` 类型,它只在值位于 1 和 100 之间时才继续</span> <span class="caption">示例 9-13一个 `Guess` 类型,它只在值位于 1 和 100 之间时才继续</span>
首先,我们定义一个包含 `i32` 类型字段 `value` 的结构体 `Guess`。这里是储存猜测值的地方。 首先,我们创建一个名为 `guessing_game` 的新模块。接着定义一个包含 `i32` 类型字段 `value` 的结构体 `Guess`。这里是储存猜测值的地方。
接着在 `Guess` 上实现了一个叫做 `new` 的关联函数来创建 `Guess` 的实例。`new` 定义为接收一个 `i32` 类型的参数 `value` 并返回一个 `Guess`。`new` 函数中代码的测试确保了其值是在 1 到 100 之间的。如果 `value` 没有通过测试则调用 `panic!`,这会警告调用这个函数的程序员有一个需要修改的 bug因为创建一个 `value` 超出范围的 `Guess` 将会违反 `Guess::new` 所遵循的契约。`Guess::new` 会出现 panic 的条件应该在其公有 API 文档中被提及;第十四章会涉及到在 API 文档中表明 `panic!` 可能性的相关规则。如果 `value` 通过了测试,我们新建一个 `Guess`,其字段 `value` 将被设置为参数 `value` 的值,接着返回这个 `Guess` 接着在 `Guess` 上实现了一个叫做 `new` 的关联函数来创建 `Guess` 的实例。`new` 定义为接收一个 `i32` 类型的参数 `value` 并返回一个 `Guess`。`new` 函数中代码的测试确保了其值是在 1 到 100 之间的。如果 `value` 没有通过测试则调用 `panic!`,这会警告调用这个函数的程序员有一个需要修改的 bug因为创建一个 `value` 超出范围的 `Guess` 将会违反 `Guess::new` 所遵循的契约。`Guess::new` 会出现 panic 的条件应该在其公有 API 文档中被提及;第十四章会涉及到在 API 文档中表明 `panic!` 可能性的相关规则。如果 `value` 通过了测试,我们新建一个 `Guess`,其字段 `value` 将被设置为参数 `value` 的值,接着返回这个 `Guess`
接着,我们实现了一个借用了 `self` 的方法 `value`,它没有任何其他参数并返回一个 `i32`。这类方法有时被称为 *getter*,因为它的目的就是返回对应字段的数据。这样的公有方法是必要的,因为 `Guess` 结构体的 `value` 字段是私有的。私有的字段 `value` 是很重要的,这样使用 `Guess` 结构体的代码将不允许直接设置 `value` 的值:调用者 **必须** 使用 `Guess::new` 方法来创建一个 `Guess` 的实例,这就确保了不会存在一个 `value` 没有通过 `Guess::new` 函数的条件检查的 `Guess` 接着,我们实现了一个借用了 `self` 的方法 `value`,它没有任何其他参数并返回一个 `i32`。这类方法有时被称为 *getter*,因为它的目的就是返回对应字段的数据。这样的公有方法是必要的,因为 `Guess` 结构体的 `value` 字段是私有的。私有的字段 `value` 是很重要的,这样使用 `Guess` 结构体的代码将不允许直接设置 `value` 的值:调用者**必须**使用 `Guess::new` 方法来创建一个 `Guess` 的实例,这就确保了不会存在一个 `value` 没有通过 `Guess::new` 函数的条件检查的 `Guess`
于是,一个接收(或返回)1 到 100 之间数字的函数就可以声明为接收(或返回) `Guess`的实例,而不是 `i32`,同时其函数体中也无需进行任何额外的检查。 于是,一个接收或返回 1 到 100 之间数字的函数就可以声明为接收(或返回) `Guess` 的实例,而不是 `i32`,同时其函数体中也无需进行任何额外的检查。
## 总结 ## 总结

View File

@ -1,14 +1,13 @@
# 编写自动化测试 # 编写自动化测试
> [ch11-00-testing.md](https://github.com/rust-lang/book/blob/main/src/ch11-00-testing.md) <!-- https://github.com/rust-lang/book/blob/main/src/ch11-00-testing.md -->
> <br> <!-- commit 5d22a358fb2380aa3f270d7b6269b67b8e44849e -->
> 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.”)这并不意味着我们不尽可能地测试软件! 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`。无论何时对代码进行修改,都可以运行测试来确保任何现存的正确行为没有被改变。 我们可以编写测试断言,比如说,当传递 `3``add_two` 函数时,返回值是 `5`。无论何时对代码进行修改,都可以运行测试来确保任何现存的正确行为没有被改变。

View File

@ -1,8 +1,7 @@
## 如何编写测试 ## 如何编写测试
> [ch11-01-writing-tests.md](https://github.com/rust-lang/book/blob/main/src/ch11-01-writing-tests.md) <!-- https://github.com/rust-lang/book/blob/main/src/ch11-01-writing-tests.md -->
> <br> <!-- commit 02e053cdbbb3bf9edd9ad32ed49eb533404350a9 -->
> commit fcfac818c722cf97f1327f296bbd6fd1d6f2a022
Rust 中的测试函数是用来验证非测试代码是否是按照期望的方式运行的。测试函数体通常执行如下三种操作: Rust 中的测试函数是用来验证非测试代码是否是按照期望的方式运行的。测试函数体通常执行如下三种操作:
@ -28,7 +27,7 @@ $ cargo new adder --lib
$ cd adder $ cd adder
``` ```
adder 库中 _src/lib.rs_ 的内容应该看起来如示例 11-1 所示: `adder` 库中 _src/lib.rs_ 的内容应该看起来如示例 11-1 所示:
<span class="filename">文件名src/lib.rs</span> <span class="filename">文件名src/lib.rs</span>
@ -38,9 +37,11 @@ adder 库中 _src/lib.rs_ 的内容应该看起来如示例 11-1 所示:
<span class="caption">示例 11-1`cargo new` 自动生成的测试模块和函数</span> <span class="caption">示例 11-1`cargo new` 自动生成的测试模块和函数</span>
文件以一个示例 `add` 函数开头,这样我们就有东西可以测试。
现在让我们只关注 `it_works` 函数本身。注意 `fn` 行之前的 `#[test]`:这个属性表明这是一个测试函数,这样测试执行者就知道将其作为测试处理。`tests` 模块中也可以有非测试的函数来帮助我们建立通用场景或进行常见操作,必须每次都标明哪些函数是测试。 现在让我们只关注 `it_works` 函数本身。注意 `fn` 行之前的 `#[test]`:这个属性表明这是一个测试函数,这样测试执行者就知道将其作为测试处理。`tests` 模块中也可以有非测试的函数来帮助我们建立通用场景或进行常见操作,必须每次都标明哪些函数是测试。
示例函数体通过使用 `assert_eq!` 宏来断言 `result` (其中包含 2 加 2 的结果)等于 4。一个典型的测试的格式就是像这个例子中的断言一样。接下来运行就可以看到测试通过。 示例函数体通过使用 `assert_eq!` 宏来断言 `result`(其中包含 2 加 2 的结果)等于 4。这个断言示例展示了典型测试的格式。接下来运行就可以看到测试通过。
`cargo test` 命令会运行项目中所有的测试,如示例 11-2 所示: `cargo test` 命令会运行项目中所有的测试,如示例 11-2 所示:
@ -50,13 +51,13 @@ adder 库中 _src/lib.rs_ 的内容应该看起来如示例 11-1 所示:
<span class="caption">示例 11-2运行自动生成测试的输出</span> <span class="caption">示例 11-2运行自动生成测试的输出</span>
Cargo 编译并运行了测试。可以看到 `running 1 test` 这一行。下一行显示了生成的测试函数的名称,它是 `it_works`,以及测试的运行结果,`ok`。接着可以看到全体测试运行结果的摘要:`test result: ok.` 意味着所有测试都通过了。`1 passed; 0 failed` 表示通过或失败的测试数量。 Cargo 编译并运行了测试。可以看到 `running 1 test` 这一行。下一行显示了生成的测试函数的名称,`tests::it_works`,以及测试的运行结果,`ok`。接着可以看到全体测试运行结果的摘要:`test result: ok.` 意味着所有测试都通过了。`1 passed; 0 failed` 表示通过或失败的测试数量。
可以将一个测试标记为忽略这样在特定情况下它就不会运行;本章之后的[“除非特别指定否则忽略某些测试”][ignoring]部分会介绍它。因为之前我们并没有将任何测试标记为忽略,所以摘要中会显示 `0 ignored` 可以将一个测试标记为忽略以便在特定情况下它就不会运行;本章之后的[“除非特别指定否则忽略某些测试”][ignoring]部分会介绍它。因为之前我们并没有将任何测试标记为忽略,所以摘要中会显示 `0 ignored`
`0 measured` 统计是针对性能测试的。性能测试benchmark tests在编写本书时仍只能用于 Rust 开发版nightly Rust。请查看 [性能测试的文档][bench] 了解更多。 `0 measured` 统计是针对性能测试的。性能测试benchmark tests在编写本书时仍只能用于 Rust 开发版nightly Rust。请查看 [性能测试的文档][bench] 了解更多。
我们可以将参数传递给 `cargo test` 命令,以便只运行名称与字符串匹配的测试;这就是所谓的 _过滤__filtering_我们会在 [“通过指定名字来运行部分测试”][subset] 讨论这一点。这里我们没有过滤需要运行的测试,所以摘要中会显示`0 filtered out`。 我们可以将参数传递给 `cargo test` 命令,以便只运行名称与字符串匹配的测试;这就是所谓的**过滤**_filtering_我们会在 [“通过指定名字来运行部分测试”][subset] 讨论这一点。这里我们没有过滤需要运行的测试,所以摘要中会显示`0 filtered out`。
测试输出中的以 `Doc-tests adder` 开头的这一部分是所有文档测试的结果。我们现在并没有任何文档测试,不过 Rust 会编译任何在 API 文档中的代码示例。这个功能帮助我们使文档和代码保持同步!在第十四章的 [“文档注释作为测试”][doc-comments] 部分会讲到如何编写文档测试。现在我们将忽略 `Doc-tests` 部分的输出。 测试输出中的以 `Doc-tests adder` 开头的这一部分是所有文档测试的结果。我们现在并没有任何文档测试,不过 Rust 会编译任何在 API 文档中的代码示例。这个功能帮助我们使文档和代码保持同步!在第十四章的 [“文档注释作为测试”][doc-comments] 部分会讲到如何编写文档测试。现在我们将忽略 `Doc-tests` 部分的输出。
@ -144,23 +145,23 @@ Cargo 编译并运行了测试。可以看到 `running 1 test` 这一行。下
{{#include ../listings/ch11-writing-automated-tests/no-listing-02-adding-another-rectangle-test/output.txt}} {{#include ../listings/ch11-writing-automated-tests/no-listing-02-adding-another-rectangle-test/output.txt}}
``` ```
两个通过的测试!现在让我们看看如果引入一个 bug 的话测试结果会发生什么。将 `can_hold` 方法中比较宽度时本应使用大于号的地方改成小于号: 两个测试通过了!现在让我们看看如果引入一个 bug 的话测试结果会发生什么。将 `can_hold` 方法中比较宽度时本应使用大于号的地方改成小于号:
```rust,not_desired_behavior,noplayground ```rust,not_desired_behavior,noplayground
{{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-03-introducing-a-bug/src/lib.rs:here}} {{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-03-introducing-a-bug/src/lib.rs:here}}
``` ```
现在运行测试会产生: 现在运行测试会产生以下结果
```console ```console
{{#include ../listings/ch11-writing-automated-tests/no-listing-03-introducing-a-bug/output.txt}} {{#include ../listings/ch11-writing-automated-tests/no-listing-03-introducing-a-bug/output.txt}}
``` ```
我们的测试捕获了 bug因为 `larger.width` 是 8 而 `smaller.width` 是 5`can_hold` 中的宽度比较现在因为 8 不小于 5 而返回 `false` 我们的测试捕获了 bug因为 `larger.width` 是 8 而 `smaller.width` 是 5`can_hold` 中的宽度比较现在因为 8 不小于 5 而返回 `false`8 并不小于 5
### 使用 `assert_eq!``assert_ne!`测试相等 ### 使用 `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!` 宏测试这个函数。 示例 11-7 中,让我们编写一个对其参数加二并返回结果的函数 `add_two`。接着使用 `assert_eq!` 宏测试这个函数。
@ -192,11 +193,11 @@ Cargo 编译并运行了测试。可以看到 `running 1 test` 这一行。下
{{#include ../listings/ch11-writing-automated-tests/no-listing-04-bug-in-add-two/output.txt}} {{#include ../listings/ch11-writing-automated-tests/no-listing-04-bug-in-add-two/output.txt}}
``` ```
测试捕获到了 bug`it_adds_two` 测试失败,错误信息告诉我们断言失败了,它告诉我们 `` assertion failed: `(left == right)` `` 以及 `left``right` 的值是什么。这个错误信息有助于我们开始调试:它说 `assert_eq!``left` 参数(也就是 `add_two(2)` 的结果)是 `5`,而 `right` 参数是 `4`。可以想象当有很多测试在运行时这些信息是多么的有用。 测试捕获到了 bug`it_adds_two` 测试失败,错误信息告诉我们断言失败了,它告诉我们 ``assertion `left == right` failed`` 以及 `left``right` 的值是什么。这个错误信息有助于我们开始调试:它说 `assert_eq!``left` 参数(也就是 `add_two(2)` 的结果)是 `5`,而 `right` 参数是 `4`。可以想象当有很多测试在运行时这些信息是多么的有用。
需要注意的是,在一些语言和测试框架中,断言两个值相等的函数的参数被称为 `expected``actual`,而且指定参数的顺序非常重要。然而在 Rust 中,它们则叫做 `left``right`,同时指定期望的值和被测试代码产生的值的顺序并不重要。这个测试中的断言也可以写成 `assert_eq!(4, result)`,这时失败信息仍同样是 `` assertion failed: `(left == right)` ``。 需要注意的是,在一些语言和测试框架中,断言两个值相等的函数的参数被称为 `expected``actual`,而且指定参数的顺序非常重要。然而在 Rust 中,它们则叫做 `left``right`,同时指定期望的值和被测试代码产生的值的顺序并不重要。这个测试中的断言也可以写成 `assert_eq!(add_two(2), result)`,这时失败信息仍同样是 `` assertion failed: `(left == right)` ``。
`assert_ne!` 宏在传递给它的两个值不相等时通过,而在相等时失败。当我们不确定值 _会_ 是什么,不过能确定值绝对 _不会_ 是什么的时候,这个宏最有用处。例如,如果一个函数保证会以某种方式改变其输入,不过这种改变方式是由运行测试时是星期几来决定的,这时最好的断言可能就是函数的输出不等于其输入。 `assert_ne!` 宏在传递给它的两个值不相等时通过,而在相等时失败。当我们不确定值**会**是什么,不过能确定值绝对**不会**_ **是什么的时候,这个宏最有用处。例如,如果一个函数保证会以某种方式改变其输入,不过这种改变方式是由运行测试时是星期几来决定的,这时最好的断言可能就是函数的输出不等于其输入。
`assert_eq!``assert_ne!` 宏在底层分别使用了 `==``!=`。当断言失败时,这些宏会使用调试格式打印出其参数,这意味着被比较的值必须实现了 `PartialEq``Debug` trait。所有的基本类型和大部分标准库类型都实现了这些 trait。对于自定义的结构体和枚举需要实现 `PartialEq` 才能断言它们的值是否相等。需要实现 `Debug` 才能在断言失败时打印它们的值。因为这两个 trait 都是派生 trait如第五章示例 5-12 所提到的,通常可以直接在结构体或枚举上添加 `#[derive(PartialEq, Debug)]` 注解。附录 C [“可派生 trait”][derivable-traits] 中有更多关于这些和其他派生 trait 的详细信息。 `assert_eq!``assert_ne!` 宏在底层分别使用了 `==``!=`。当断言失败时,这些宏会使用调试格式打印出其参数,这意味着被比较的值必须实现了 `PartialEq``Debug` trait。所有的基本类型和大部分标准库类型都实现了这些 trait。对于自定义的结构体和枚举需要实现 `PartialEq` 才能断言它们的值是否相等。需要实现 `Debug` 才能在断言失败时打印它们的值。因为这两个 trait 都是派生 trait如第五章示例 5-12 所提到的,通常可以直接在结构体或枚举上添加 `#[derive(PartialEq, Debug)]` 注解。附录 C [“可派生 trait”][derivable-traits] 中有更多关于这些和其他派生 trait 的详细信息。