测试 Rust 代码

本文档描述了为 Rust 代码编写测试的最佳做法。 也请参考组件测试指南中关于定义测试包和组件以及运行它们的介绍。

本文档面向的是在fuchsia.git中工作的开发人员,对工作流的描述不太可能适用于啥也不懂的消费者。

本教程的源代码可以在//examples/hello_world/rust中找到。

单元测试

为代码增加测试

在外部为 Rust 增加单元测试的惯用方式在 Fuchsia 里同样适用,可以把下边的代码片段放到任何你想写的测试的底部,就可以轻易的达到目的:

  1. {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/hello_world/rust/src/main.rs" region_tag="test_mod" adjust_indentation="auto" %}

这会生成一个新的、命名为 tests 的 mod, 这个 mod 将仅在构建单元测试的时候被包含。任何带有 #[test] 注解的方法都会被当成一个测试来运行,如果方法成功返回,就意味着测试通过。

对于异步代码测试,在使用异步执行器的时候,可以选择使用 #[fasync::run_until_stalled(test)] 注解。

  1. {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/hello_world/rust/src/main.rs" region_tag="async_test" adjust_indentation="auto" %}

构建测试

单元测试可以由 Rust 目标自动生成(即,rustc_binary 或者 rustc_library)。这些方法大体上是相似的。

为 Rust 二进制构建测试

如果你正在测试一个 rust 二进制 (即,你有一个 main.rs),这个章节会对你很有用。如果你是在测试库 ,请看下一章节

你的 BUILD.gn 文件首先需要通过导入 rust_binary 模板来使其可用:

  1. import("//build/rust/rustc_binary.gni")

只有在设置中加入了 with_unit_tests = true 时,单元测试才能被 rustc_binary GN 模板构建:

  1. {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/hello_world/rust/BUILD.gn" region_tag="rustc_tests" adjust_indentation="auto" %}

设置 with_unit_tests = true 会导致这个构建规则生成两个不同的可执行程序,一个原本提供的,另一个在其名字后追加了 _bin_test

在我们这里的示例中,生成的可执行程序命名如下:

  • hello_world_rust; 和
  • hello_world_rust_bin_test.

为 Rust 库构建测试

你的 BUILD.gn 文件首先需要通过导入 rust_library 模板来使其可用:

  1. import("//build/rust/rustc_library.gni")

只有在设置中加入了 with_unit_tests = true 时,单元测试才能被 rustc_library GN 模板构建,和上边 rustc_binary 例子一样。

然而在这个示例中,会生成一个 名称不同 的二进制测试程序:

  • hello_world_rust_lib_test。注意库生成的名字和二进制生成的名字是不一样的。

二进制文件的名称很重要,因为它们会在下边的步骤中使用。

打包和运行测试

要运行由之前的目标生成的测试,首先需要将它们打包。目前这个打包进程包含两个步骤,写一个包清单和包构建目标。

编写包清单

一个包清单目前是必要的。清单位于 meta/ 子目录下,直接位于包含你的 BUILD.gn 文件的目录下。 一个最简清单文件如下所示,并且文件的名称必须和上述由 rustc_binaryrustc_library 生成目标的 name 属性相同。

在上述 rustc_binary 的情况下,相应的清单应为:

  1. {
  2. "program": {
  3. "binary": "test/hello_world_rust_bin_test"
  4. }
  5. }

注意:以下几点需要考虑:

  • 二进制文件名基于 rustc_binary 目标中的 name = "hello_world_rust" 一行。
  • 二进制文件在一个 test 的子目录中。位置隐含在 rustc_binary 构建规则中。
  • 你可能已经注意到包清单有些公式化。 未来,我们将可以自动化生成包清单而不再需要手写。

rustc_library 的情况下,清单和命名方案是一样的。但是需要留意在 program.binary 小节中值的命名上的微小区别。

  1. {
  2. "program": {
  3. "binary": "test/hello_world_rust_lib_test"
  4. }
  5. }

_lib_test 后缀在 rustc_library 构建规则中是硬编码,hello_world_rust 还是从构建规则中的 name 属性中得来。

编写包构建目标

对于 Hello world 例子,测试包需要关联到生成的目标上, bin_test (基于目标名称 bin 和隐含的 _test 后缀),hello_world_rust_bin_test (基于 name 小节中的值)。

如果你正在构建的是一个库,则库的名称会是 hello_world_rust_lib_test

  1. {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/hello_world/rust/BUILD.gn" indented_block="^fuchsia_component\(\"hello-world-rust-tests-component\"\) {" %}
  1. {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/hello_world/rust/BUILD.gn" indented_block="^fuchsia_test_package\(\"hello-world-rust-tests\"\) {" %}

要运行测试,执行:

  1. fx test hello_world_rust_tests

注意:为了使用 fx test,你不能在你的 package 中重写 package_name="..."test_package 声明。这个问题可以追踪 fxbug.dev/3143

更多关于打包和运行测试的信息,请查看测试作为组件

有用的 crates

下列树内第三方 crate 可以帮助你测试:

  • matches: 提供 assert_matches! 宏,使模式断言符合工效学。
  • [pretty_asssertions]:提供了一个可选的 assert_eq! 宏,在断言失败时显示彩色的差异。

这些可以包含在你的 BUILD.gn 中的 test_deps 下。

  1. rustc_binary("bin") {
  2. name = "my_test"
  3. with_unit_tests = true
  4. edition = "2018"
  5. test_deps = [
  6. "//third_party/rust_crates:matches",
  7. "//third_party/rust_crates:pretty_assertions",
  8. ]
  9. }