mirror of
https://github.com/rust-lang-cn/book-cn.git
synced 2025-02-03 07:48:41 +08:00
initial import/re-write of testing chapter
This commit is contained in:
parent
0097f8c6dc
commit
a85af065be
@ -1 +1,280 @@
|
|||||||
# Testing
|
# Testing
|
||||||
|
|
||||||
|
> 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, "The Humble Programmer" (1972)
|
||||||
|
|
||||||
|
Rust is a programming language that cares a lot about correctness. But
|
||||||
|
correctness is a complex topic, and isn't exactly easy to get right. Rust
|
||||||
|
places a lot of weight on its type system to help ensure that our programs do
|
||||||
|
what we intend, but it cannot help with everything. As such, Rust also includes
|
||||||
|
support for writing software tests in the language itself.
|
||||||
|
|
||||||
|
Testing is a skill, and we cannot hope to learn everything about how to write
|
||||||
|
good tests in one chapter of a book. What we can learn, however, are the
|
||||||
|
mechanics of Rust's testing facilities. That's what we'll focus on in this
|
||||||
|
chapter.
|
||||||
|
|
||||||
|
## The `test` attribute
|
||||||
|
|
||||||
|
At its simplest, a test in Rust is a function that's annotated with the `test`
|
||||||
|
attribute. Let's make a new project with Cargo called `adder`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cargo new adder
|
||||||
|
Created library `adder` project
|
||||||
|
$ cd adder
|
||||||
|
```
|
||||||
|
|
||||||
|
Cargo will automatically generate a simple test when you make a new project.
|
||||||
|
Here's the contents of `src/lib.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For now, let's remove the `mod` bit, and focus on just the function:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the `#[test]`. This attribute indicates that this is a test function. It
|
||||||
|
currently has no body. That's good enough to pass! We can run the tests with
|
||||||
|
`cargo test`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cargo test
|
||||||
|
Compiling adder v0.1.0 (file:///projects/adder)
|
||||||
|
Finished debug [unoptimized + debuginfo] target(s) in 0.22 secs
|
||||||
|
Running target/debug/deps/adder-ce99bcc2479f4607
|
||||||
|
|
||||||
|
running 1 test
|
||||||
|
test it_works ... ok
|
||||||
|
|
||||||
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
|
||||||
|
Doc-tests adder
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
```
|
||||||
|
|
||||||
|
Cargo compiled and ran our tests. There are two sets of output here: one
|
||||||
|
for the test we wrote, and another for documentation tests. We'll talk about
|
||||||
|
documentation tests later. For now, see this line:
|
||||||
|
|
||||||
|
```text
|
||||||
|
test it_works ... ok
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the `it_works`. This comes from the name of our function:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn it_works() {
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
We also get a summary line:
|
||||||
|
|
||||||
|
```text
|
||||||
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
```
|
||||||
|
|
||||||
|
## The `assert!` macro
|
||||||
|
|
||||||
|
So why does our do-nothing test pass? Any test which doesn't `panic!` passes,
|
||||||
|
and any test that does `panic!` fails. Let's make our test fail:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert!(false);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`assert!` is a macro provided by Rust which takes one argument: if the argument
|
||||||
|
is `true`, nothing happens. If the argument is `false`, it will `panic!`. Let's
|
||||||
|
run our tests again:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cargo test
|
||||||
|
Compiling adder v0.1.0 (file:///projects/adder)
|
||||||
|
Finished debug [unoptimized + debuginfo] target(s) in 0.22 secs
|
||||||
|
Running target/debug/deps/adder-ce99bcc2479f4607
|
||||||
|
|
||||||
|
running 1 test
|
||||||
|
test it_works ... FAILED
|
||||||
|
|
||||||
|
failures:
|
||||||
|
|
||||||
|
---- it_works stdout ----
|
||||||
|
thread 'it_works' panicked at 'assertion failed: false', src/lib.rs:5
|
||||||
|
note: Run with `RUST_BACKTRACE=1` for a backtrace.
|
||||||
|
|
||||||
|
|
||||||
|
failures:
|
||||||
|
it_works
|
||||||
|
|
||||||
|
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
|
||||||
|
|
||||||
|
error: test failed
|
||||||
|
```
|
||||||
|
|
||||||
|
Rust indicates that our test failed:
|
||||||
|
|
||||||
|
```text
|
||||||
|
test it_works ... FAILED
|
||||||
|
```
|
||||||
|
|
||||||
|
And that's reflected in the summary line:
|
||||||
|
|
||||||
|
```text
|
||||||
|
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
|
||||||
|
```
|
||||||
|
|
||||||
|
## Inverting failure with `should_panic`
|
||||||
|
|
||||||
|
We can invert our test's failure with another attribute: `should_panic`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn it_works() {
|
||||||
|
assert!(false);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This test will now succeed if we `panic!` and fail if we complete.
|
||||||
|
|
||||||
|
`should_panic` tests can be fragile, as it's hard to guarantee that the test
|
||||||
|
didn't fail for an unexpected reason. To help with this, an optional `expected`
|
||||||
|
parameter can be added to the `should_panic` attribute. The test harness will
|
||||||
|
make sure that the failure message contains the provided text. A safer version
|
||||||
|
of the example above would be:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "assertion failed")]
|
||||||
|
fn it_works() {
|
||||||
|
assert!(false);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing equality
|
||||||
|
|
||||||
|
Rust provides a pair of macros, `assert_eq!` and `assert_ne!`, that compares
|
||||||
|
two arguments for equality:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!("Hello", "Hello");
|
||||||
|
|
||||||
|
assert_ne!("Hello", "world");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These macros expand to something like this:
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
// assert_eq
|
||||||
|
if left_val == right_val {
|
||||||
|
panic!("message goes here")
|
||||||
|
}
|
||||||
|
|
||||||
|
// assert_ne
|
||||||
|
if left_val =! right_val {
|
||||||
|
panic!("message goes here")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
But they're a bit more convenient than writing this out by hand. These macros
|
||||||
|
are often used to call some function with some known arguments and compare it
|
||||||
|
to the expected output, like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn add_two(a: i32) -> i32 {
|
||||||
|
a + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!(4, add_two(2));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## The `ignore` attribute
|
||||||
|
|
||||||
|
Sometimes a few specific tests can be very time-consuming to execute. These
|
||||||
|
can be disabled by default by using the `ignore` attribute:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn add_two(a: i32) -> i32 {
|
||||||
|
a + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!(4, add_two(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn expensive_test() {
|
||||||
|
// code that takes an hour to run
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we run our tests and see that `it_works` is run, but `expensive_test` is
|
||||||
|
not:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cargo test
|
||||||
|
Compiling adder v0.1.0 (file:///projects/adder)
|
||||||
|
Finished debug [unoptimized + debuginfo] target(s) in 0.24 secs
|
||||||
|
Running target/debug/deps/adder-ce99bcc2479f4607
|
||||||
|
|
||||||
|
running 2 tests
|
||||||
|
test expensive_test ... ignored
|
||||||
|
test it_works ... ok
|
||||||
|
|
||||||
|
test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured
|
||||||
|
|
||||||
|
Doc-tests adder
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
```
|
||||||
|
|
||||||
|
The expensive tests can be run explicitly using `cargo test -- --ignored`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cargo test -- --ignored
|
||||||
|
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
|
||||||
|
Running target/debug/deps/adder-ce99bcc2479f4607
|
||||||
|
|
||||||
|
running 1 test
|
||||||
|
test expensive_test ... ok
|
||||||
|
|
||||||
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
|
||||||
|
Doc-tests adder
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--ignored` argument is an argument to the test binary, and not to Cargo,
|
||||||
|
which is why the command is `cargo test -- --ignored`.
|
||||||
|
@ -1 +1,131 @@
|
|||||||
# Unit testing
|
# Unit testing
|
||||||
|
|
||||||
|
As we mentioned before, testing is a large discipline, and so different people
|
||||||
|
can sometimes use different terminology. For our purposes, we tend to place
|
||||||
|
tests into two main categories: *unit tests* and *integration tests*. Unit
|
||||||
|
tests tend to be smaller, and more focused. In Rust, they can also test
|
||||||
|
non-public interfaces. Let's talk more about how to do unit testing in Rust.
|
||||||
|
|
||||||
|
## The tests module and `cfg(test)`
|
||||||
|
|
||||||
|
Remember when we generated our new project in the last section? Cargo had
|
||||||
|
generated some stuff for us:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We deleted the module stuff so we could learn more about the mechanics of
|
||||||
|
tests. But there's a reason that Cargo generated this module for us: it's the
|
||||||
|
idiomatic way to organize unit tests in Rust. That is, unit tests are:
|
||||||
|
|
||||||
|
* Stored inside of the same tree as your source code.
|
||||||
|
* Placed inside their own module.
|
||||||
|
|
||||||
|
For a more realistic example of how this works, consider our `add_two` function
|
||||||
|
from before:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn add_two(a: i32) -> i32 {
|
||||||
|
a + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use add_two;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!(4, add_two(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
First of all, there's a new attribute, `cfg`. The `cfg` attribute lets us
|
||||||
|
declare that something should only be included given a certain configuration.
|
||||||
|
Rust provides the `test` configuration when compiling and running tests. By
|
||||||
|
using this attribute, Cargo only compiles our test code if we're currently
|
||||||
|
trying to run the tests. Given that they're not compiled at all during a
|
||||||
|
regular `cargo build`, this can save compile time. It also ensures that our
|
||||||
|
tests are entirely left out of the binary, saving space in a non-testing
|
||||||
|
context.
|
||||||
|
|
||||||
|
You'll notice one more change: the `use` declaration. The `tests` module is
|
||||||
|
only a convention, it's nothing that Rust understands directly. As such, we
|
||||||
|
have to follow the usual visibility rules. Because we're in an inner module,
|
||||||
|
we need to bring our test function into scope. This can be annoying if you have
|
||||||
|
a large module, and so this is a common use of globs. Let's change our
|
||||||
|
`src/lib.rs` to make use of it:
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
pub fn add_two(a: i32) -> i32 {
|
||||||
|
a + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!(4, add_two(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the different `use` line. Now we run our tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cargo test
|
||||||
|
Compiling adder v0.1.0 (file:///projects/adder)
|
||||||
|
Finished debug [unoptimized + debuginfo] target(s) in 0.27 secs
|
||||||
|
Running target/debug/deps/adder-ce99bcc2479f4607
|
||||||
|
|
||||||
|
running 1 test
|
||||||
|
test tests::it_works ... ok
|
||||||
|
|
||||||
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
|
||||||
|
Doc-tests adder
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
```
|
||||||
|
|
||||||
|
It works!
|
||||||
|
|
||||||
|
## Testing internal functions
|
||||||
|
|
||||||
|
There's controversy within the testing community about unit testing private
|
||||||
|
functions. Regardless of which testing ideology you adhere to, Rust does allow
|
||||||
|
you to test them, due to the way that the privacy rules work. Consider this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn add_two(a: i32) -> i32 {
|
||||||
|
internal_adder(a, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn internal_adder(a: i32, b: i32) -> i32 {
|
||||||
|
a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use internal_adder;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn internal() {
|
||||||
|
assert_eq!(4, internal_adder(2, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In this scenario, we have a non-`pub` function, `internal_adder`. Because tests
|
||||||
|
are just Rust code, and the `tests` module is just another module, we can
|
||||||
|
import and call `internal_adder` in a test just fine.
|
||||||
|
@ -1 +1,163 @@
|
|||||||
# Integration testing
|
# Integration testing
|
||||||
|
|
||||||
|
In the last section, we talked about unit tests. But there's still that other
|
||||||
|
category: integration testing. In Rust, an integration test is a test that is
|
||||||
|
entirely external to your library. It uses it in the same way any other code
|
||||||
|
would.
|
||||||
|
|
||||||
|
Cargo has support for integration tests through the `tests` directory. If you
|
||||||
|
make one, and put `.rs` files inside, Cargo will compile each of them as an
|
||||||
|
individual crate. Let's give it a try! First, make a `tests` directory at the
|
||||||
|
top level of your project, next to `src`. Then, make a new file,
|
||||||
|
`tests/integration_test.rs`, and put this inside:
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
extern crate adder;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!(4, adder::add_two(2));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
There's some small changes from our previous tests. We now have an `extern
|
||||||
|
crate adder` at the top. This is because each test in the `tests` directory is
|
||||||
|
an entirely separate crate, and so we need to import our library. This is also
|
||||||
|
why `tests` is a suitable place to write integration-style tests: they use the
|
||||||
|
library like any other consumer of it would.
|
||||||
|
|
||||||
|
Let's run them:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cargo test
|
||||||
|
Compiling adder v0.1.0 (file:///home/steve/tmp/adder)
|
||||||
|
Running target/adder-91b3e234d4ed382a
|
||||||
|
|
||||||
|
running 1 test
|
||||||
|
test tests::it_works ... ok
|
||||||
|
|
||||||
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
|
||||||
|
Running target/lib-c18e7d3494509e74
|
||||||
|
|
||||||
|
running 1 test
|
||||||
|
test it_works ... ok
|
||||||
|
|
||||||
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
|
||||||
|
Doc-tests adder
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we have three sections: our previous test is also run, as well as our new
|
||||||
|
one.
|
||||||
|
|
||||||
|
That's all there is to the `tests` directory. The `tests` module isn't needed
|
||||||
|
here, since the whole thing is focused on tests.
|
||||||
|
|
||||||
|
## Submodules in integration tests
|
||||||
|
|
||||||
|
As your integration tests grow, you may want to make more than one file in the
|
||||||
|
`tests` directory. As we mentioned before, that works well, given that Cargo
|
||||||
|
treats every file as its own crate. But there's one small trap that can happen.
|
||||||
|
|
||||||
|
Imagine we wanted some common helper functions to be shared across our tests.
|
||||||
|
So we change our test to have a `common` module:
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
extern crate adder;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
common::helper();
|
||||||
|
|
||||||
|
assert_eq!(4, adder::add_two(2));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And then, we create a `tests/common.rs` file to hold our common helpers:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn helper() {
|
||||||
|
// no implementation for now
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's try running this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cargo test
|
||||||
|
Compiling adder v0.1.0 (file:///home/steve/tmp/adder)
|
||||||
|
Finished debug [unoptimized + debuginfo] target(s) in 0.25 secs
|
||||||
|
Running target/debug/deps/adder-ce99bcc2479f4607
|
||||||
|
|
||||||
|
running 1 test
|
||||||
|
test tests::internal ... ok
|
||||||
|
|
||||||
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
|
||||||
|
Running target/debug/common-c3635c69f3aeef92
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
|
||||||
|
Running target/debug/integration_tests-6d6e12b4680b0368
|
||||||
|
|
||||||
|
running 1 test
|
||||||
|
test it_works ... ok
|
||||||
|
|
||||||
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
|
||||||
|
Doc-tests adder
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
```
|
||||||
|
|
||||||
|
Wait a minute. Now we have four sections?
|
||||||
|
|
||||||
|
```text
|
||||||
|
Running target/debug/common-c3635c69f3aeef92
|
||||||
|
```
|
||||||
|
|
||||||
|
Because `common.rs` is in our `tests` directory, Cargo is also compiling it as
|
||||||
|
its own crate. Because `common.rs` is so simple, we didn't get an error, but
|
||||||
|
with more complex code, this might not work. So what can we do?
|
||||||
|
|
||||||
|
The key is, always use the `common/mod.rs` form over the `common.rs` form when
|
||||||
|
making modules in integration tests. If we move `tests/common.rs` to
|
||||||
|
`tests/common/mod.rs`, we'll go back to our expected output:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mkdir tests/common
|
||||||
|
$ mv tests/common.rs tests/common/mod.rs
|
||||||
|
$ cargo test
|
||||||
|
Compiling adder v0.1.0 (file:///home/steve/tmp/adder)
|
||||||
|
Finished debug [unoptimized + debuginfo] target(s) in 0.24 secs
|
||||||
|
Running target/debug/deps/adder-ce99bcc2479f4607
|
||||||
|
|
||||||
|
running 1 test
|
||||||
|
test tests::internal ... ok
|
||||||
|
|
||||||
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
|
||||||
|
Running target/debug/integration_tests-6d6e12b4680b0368
|
||||||
|
|
||||||
|
running 1 test
|
||||||
|
test it_works ... ok
|
||||||
|
|
||||||
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
|
||||||
|
Doc-tests adder
|
||||||
|
|
||||||
|
running 0 tests
|
||||||
|
|
||||||
|
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
```
|
||||||
|
@ -1 +1,302 @@
|
|||||||
# Documentation Tests
|
# Documentation Tests
|
||||||
|
|
||||||
|
Nothing is better than documentation with examples. Nothing is worse than
|
||||||
|
examples that don't actually work, because the code has changed since the
|
||||||
|
documentation has been written. To this end, Rust supports automatically
|
||||||
|
running examples in your documentation for library crates. Here's a fleshed-out
|
||||||
|
`src/lib.rs` with examples:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
//! The `adder` crate provides functions that add numbers to other numbers.
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! assert_eq!(4, adder::add_two(2));
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
/// This function adds two to its argument.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use adder::add_two;
|
||||||
|
///
|
||||||
|
/// assert_eq!(4, add_two(2));
|
||||||
|
/// ```
|
||||||
|
pub fn add_two(a: i32) -> i32 {
|
||||||
|
a + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!(4, add_two(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the module-level documentation with `//!` and the function-level
|
||||||
|
documentation with `///`. Rust's documentation supports Markdown in comments,
|
||||||
|
and so triple graves mark code blocks. It is conventional to include the
|
||||||
|
`# Examples` section, exactly like that, with examples following.
|
||||||
|
|
||||||
|
Let's run the tests again:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cargo test
|
||||||
|
Compiling adder v0.1.0 (file:///projects/adder)
|
||||||
|
Running target/adder-91b3e234d4ed382a
|
||||||
|
|
||||||
|
running 1 test
|
||||||
|
test tests::it_works ... ok
|
||||||
|
|
||||||
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
|
||||||
|
Running target/lib-c18e7d3494509e74
|
||||||
|
|
||||||
|
running 1 test
|
||||||
|
test it_works ... ok
|
||||||
|
|
||||||
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
|
||||||
|
Doc-tests adder
|
||||||
|
|
||||||
|
running 2 tests
|
||||||
|
test add_two_0 ... ok
|
||||||
|
test _0 ... ok
|
||||||
|
|
||||||
|
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we have all three kinds of tests running! Note the names of the
|
||||||
|
documentation tests: the `_0` is generated for the module test, and `add_two_0`
|
||||||
|
for the function test. These will auto increment with names like `add_two_1` as
|
||||||
|
you add more examples.
|
||||||
|
|
||||||
|
## Automatic `main` insertion
|
||||||
|
|
||||||
|
Let's discuss our sample example documentation:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// ```
|
||||||
|
/// println!("Hello, world");
|
||||||
|
/// ```
|
||||||
|
# fn foo() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll notice that you don't need a `fn main()` or anything here. `rustdoc`
|
||||||
|
will automatically add a `main()` wrapper around your code, using heuristics to
|
||||||
|
attempt to put it in the right place. For example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// ```
|
||||||
|
/// use std::rc::Rc;
|
||||||
|
///
|
||||||
|
/// let five = Rc::new(5);
|
||||||
|
/// ```
|
||||||
|
# fn foo() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
This will end up testing:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
use std::rc::Rc;
|
||||||
|
let five = Rc::new(5);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's the full algorithm rustdoc uses to preprocess examples:
|
||||||
|
|
||||||
|
1. Any leading `#![foo]` attributes are left intact as crate attributes.
|
||||||
|
2. Some common `allow` attributes are inserted, including
|
||||||
|
`unused_variables`, `unused_assignments`, `unused_mut`,
|
||||||
|
`unused_attributes`, and `dead_code`. Small examples often trigger
|
||||||
|
these lints.
|
||||||
|
3. If the example does not contain `extern crate`, then `extern crate
|
||||||
|
<mycrate>;` is inserted (note the lack of `#[macro_use]`).
|
||||||
|
4. Finally, if the example does not contain `fn main`, the remainder of the
|
||||||
|
text is wrapped in `fn main() { your_code }`.
|
||||||
|
|
||||||
|
This generated `fn main` can be a problem! If you have `extern crate` or a
|
||||||
|
`mod` statements in the example code that are referred to by `use` statements,
|
||||||
|
they will fail to resolve unless you include at least `fn main() {}` to inhibit
|
||||||
|
step 4. `#[macro_use] extern crate` also does not work except at the crate
|
||||||
|
root, so when testing macros an explicit `main` is always required. It doesn't
|
||||||
|
have to clutter up your docs, though — keep reading!
|
||||||
|
|
||||||
|
## Hiding extraneous code with `#`
|
||||||
|
|
||||||
|
Sometimes this algorithm isn't enough, though. For example, all of these code
|
||||||
|
samples with `///` we've been talking about? The raw text:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/// Some documentation.
|
||||||
|
# fn foo() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
looks different than the output:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Some documentation.
|
||||||
|
# fn foo() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
Yes, that's right: we can add lines that start with `# `, and they will be
|
||||||
|
hidden from the output, but will be used when compiling our code. We can use
|
||||||
|
this to our advantage. In this case, documentation comments need to apply to
|
||||||
|
some kind of function, so if We want to show off a documentation comment, I
|
||||||
|
need to add a little function definition below it. At the same time, it's only
|
||||||
|
there to satisfy the compiler, so hiding it makes the example more clear. We
|
||||||
|
can use this technique to explain longer examples in detail, while still
|
||||||
|
preserving the testability of our documentation.
|
||||||
|
|
||||||
|
For example, imagine that we wanted to document this code:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let x = 5;
|
||||||
|
let y = 6;
|
||||||
|
println!("{}", x + y);
|
||||||
|
```
|
||||||
|
|
||||||
|
We might want the documentation to end up looking like this:
|
||||||
|
|
||||||
|
> First, we set `x` to five:
|
||||||
|
>
|
||||||
|
> ```rust
|
||||||
|
> let x = 5;
|
||||||
|
> # let y = 6;
|
||||||
|
> # println!("{}", x + y);
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Next, we set `y` to six:
|
||||||
|
>
|
||||||
|
> ```rust
|
||||||
|
> # let x = 5;
|
||||||
|
> let y = 6;
|
||||||
|
> # println!("{}", x + y);
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Finally, we print the sum of `x` and `y`:
|
||||||
|
>
|
||||||
|
> ```rust
|
||||||
|
> # let x = 5;
|
||||||
|
> # let y = 6;
|
||||||
|
> println!("{}", x + y);
|
||||||
|
> ```
|
||||||
|
|
||||||
|
To keep each code block testable, we want the whole program in each block, but
|
||||||
|
we don't want the reader to see every line every time. Here's what we put in
|
||||||
|
our source code:
|
||||||
|
|
||||||
|
```text
|
||||||
|
First, we set `x` to five:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let x = 5;
|
||||||
|
# let y = 6;
|
||||||
|
# println!("{}", x + y);
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, we set `y` to six:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# let x = 5;
|
||||||
|
let y = 6;
|
||||||
|
# println!("{}", x + y);
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, we print the sum of `x` and `y`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# let x = 5;
|
||||||
|
# let y = 6;
|
||||||
|
println!("{}", x + y);
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
By repeating all parts of the example, we can ensure that our example still
|
||||||
|
compiles, while only showing the parts that are relevant to that part of our
|
||||||
|
explanation.
|
||||||
|
|
||||||
|
Another case where the use of `#` is handy is when you want to ignore
|
||||||
|
error handling. Lets say you want the following,
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
/// use std::io;
|
||||||
|
/// let mut input = String::new();
|
||||||
|
/// try!(io::stdin().read_line(&mut input));
|
||||||
|
```
|
||||||
|
|
||||||
|
The problem is that `try!` returns a `Result<T, E>` and test functions
|
||||||
|
don't return anything so this will give a mismatched types error.
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
/// A doc test using try!
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::io;
|
||||||
|
/// # fn foo() -> io::Result<()> {
|
||||||
|
/// let mut input = String::new();
|
||||||
|
/// try!(io::stdin().read_line(&mut input));
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
# fn foo() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can get around this by wrapping the code in a function. This catches
|
||||||
|
and swallows the `Result<T, E>` when running tests on the docs. This
|
||||||
|
pattern appears regularly in the standard library.
|
||||||
|
|
||||||
|
## Adding attributes to control documentation testing.
|
||||||
|
|
||||||
|
In the first part of the chapter, we talked about attributes that help with
|
||||||
|
testing:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn it_works() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[should_panic]
|
||||||
|
fn it_works() {
|
||||||
|
assert!(false);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We can use these annotations in documentation tests as well:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// fn foo() {
|
||||||
|
/// ```
|
||||||
|
fn foo() {}
|
||||||
|
|
||||||
|
/// ```rust,should_panic
|
||||||
|
/// assert!(false);
|
||||||
|
/// ```
|
||||||
|
fn bar() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
## The `no_run` attribute
|
||||||
|
|
||||||
|
There's one attribute that's specific to documentation tests:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// loop {
|
||||||
|
/// println!("Hello, world");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
# fn foo() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `no_run` attribute will compile your code, but not run it. This is
|
||||||
|
important for examples such as "Here's how to start up a network service,"
|
||||||
|
which you would want to make sure compile, but might run in an infinite loop!
|
||||||
|
Loading…
Reference in New Issue
Block a user