@ -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