This document describes best practices for writing tests for Rust code. Please also refer to the components testing guide for instructions on defining test packages and components and running them.
This document is targeted towards developers working inside of fuchsia.git
,
and the workflow described is unlikely to work for IDK consumers.
The source code for this tutorial is available at
//examples/hello_world/rust
.
Unit tests
Adding tests to code
The idiomatic way for adding Rust unit tests works just as well inside of Fuchsia as it does outside, and can be easily accomplished by dropping the following snippet into the bottom of whatever test you want to write:
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(true, true);
}
}
This will cause a new mod named tests
to be created, and this mod will only be
included when building unit tests. Any functions annotated with #[test]
will
be run as a test, and if the function successfully returns then the test passes.
For tests exercising asynchronous code, use the
#[fasync::run_until_stalled(test)]
annotation as an alternative to
using an asynchronous executor.
#[fasync::run_until_stalled(test)]
async fn my_test() {
let some_future = async { 4 };
assert_eq!(some_future.await, 4);
}
Building tests
The unit tests can be automatically built by Rust targets (i.e. either
rustc_binary
or rustc_library
). The approaches are by and large similar.
Building tests for a Rust binary
This section is useful if you are testing a rust binary (i.e. you have a
main.rs
). If you have a library instead, see the next section.
Your BUILD.gn
file first needs to make available the rustc_binary
template
by importing it:
import("//build/rust/rustc_binary.gni")
Unit tests are built by the rustc_binary
GN template only if the setting
with_unit_tests = true
is added:
rustc_binary("bin") {
name = "hello_world_rust"
with_unit_tests = true
edition = "2018"
deps = []
test_deps = [ "//src/lib/fuchsia-async" ]
sources = [ "src/main.rs" ]
}
Setting with_unit_tests = true
causes this build rule to generate two
different executables, one with the provided and one with _bin_test
appended
to the provided name.
In our example here, the executable names that are created are called:
hello_world_rust
; andhello_world_rust_bin_test
.
Building tests for a Rust library
Your BUILD.gn
file first needs to make available the rustc_library
template
by importing it:
import("//build/rust/rustc_library.gni")
Unit tests are built by the rustc_library
GN template only if the setting
with_unit_tests = true
is added, similarly to how it is done in the case
of rustc_binary
above.
In this case, however, a differently named test binary is created:
hello_world_rust_lib_test
. Note that the name of the binary is different from the name generated by the library.
The binary names are important because they will be used in followup steps.
Packaging and running tests
To run the tests that were generated by previous targets, they will need to be packaged first. This is currently a two step process, which includes writing a package manifest and the package build target.
Writing the package manifest
A package manifest is currently required. The manifest is placed in the
meta/
subdirectory, immediately below the directory containing your BUILD.gn
file.
A minimal manifest file is shown below, and must be named the same as the
name
attribute of the target that is being generated by the rustc_binary
or
rustc_library
above.
In case of a manifest for a rustc_binary
given above, the resulting manifest
is:
{
"program": {
"binary": "test/hello_world_rust_bin_test"
}
}
* The binary name is based on the `name = "hello_world_rust"` line on the
`rustc_binary` target.
* The binary is inside a `test/` subdirectory. This placement is implicit in the
`rustc_binary` build rule.
* You may have noticed that the package manifests are somewhat formulaic.
In the future, we may find ourselves able to automatically generate the
package manifests instead of having to write them out by hand.
In case of a manifest for a rustc_library
, the manifest and the naming scheme
are similar. But pay attention to the subtle naming difference in the value
for the stanza program.binary
:
{
"program": {
"binary": "test/hello_world_rust_lib_test"
}
}
The _lib_test
suffix is hard-coded in the rustc_library
build rule, and
hello_world_rust
again comes from the name
attribute in the build rule.
Writing the package build target
For the Hello world binary example, the test package needs to reference the
generated targets, bin_test
(based on target name bin
and the implicit
suffix _test
), and hello_world_rust_bin_test
(based on the value of name
stanza).
If you are building a library instead,
then the library name will be hello_world_rust_lib_test
.
fuchsia_component("hello-world-rust-tests-component") {
testonly = true
# The bin_test target is generated by rustc_binary
# when the with_unit_tests attribute is true.
deps = [ ":bin_test" ]
manifest = "meta/hello_world_rust_bin_test.cml"
component_name = "hello-world-rust-tests"
}
fuchsia_test_package("hello-world-rust-tests") {
# component-url: fuchsia-pkg://fuchsia.com/hello-world-rust-tests#meta/hello-world-rust-tests.cm
test_components = [ ":hello-world-rust-tests-component" ]
}
To run the tests run:
fx test hello_world_rust_tests
For information on packaging and running tests, see Tests as components.
Helpful crates
The following in-tree third-party crates can help you write tests:
matches
: provides the macroassert_matches!
, making pattern assertions ergonomic.pretty_assertions
: provides an alternativeassert_eq!
macro that displays a colored diff when the assertion fails.
These can be included in your BUILD.gn
under test_deps
.
rustc_binary("bin") {
name = "my_test"
with_unit_tests = true
edition = "2018"
test_deps = [
"//third_party/rust_crates:matches",
"//third_party/rust_crates:pretty_assertions",
]
}