Skip to content

Commit bb9c5c4

Browse files
committed
auto merge of #11052 : jvns/rust/testing-tutorial, r=brson
There's no explanation anywhere right now of how to do testing with Rust, so here's a basic explanation of how to write and run a test.
2 parents fb46225 + f032237 commit bb9c5c4

File tree

3 files changed

+272
-0
lines changed

3 files changed

+272
-0
lines changed

doc/tutorial-testing.md

+263
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
% Rust Testing Tutorial
2+
3+
# Quick start
4+
5+
To create test functions, add a `#[test]` attribute like this:
6+
7+
```rust
8+
fn return_two() -> int {
9+
2
10+
}
11+
12+
#[test]
13+
fn return_two_test() {
14+
let x = return_two();
15+
assert!(x == 2);
16+
}
17+
```
18+
19+
To run these tests, use `rustc --test`:
20+
21+
```
22+
$ rustc --test foo.rs; ./foo
23+
running 1 test
24+
test return_two_test ... ok
25+
26+
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
27+
```
28+
29+
`rustc foo.rs` will *not* compile the tests, since `#[test]` implies
30+
`#[cfg(test)]`. The `--test` flag to `rustc` implies `--cfg test`.
31+
32+
33+
# Unit testing in Rust
34+
35+
Rust has built in support for simple unit testing. Functions can be
36+
marked as unit tests using the 'test' attribute.
37+
38+
```rust
39+
#[test]
40+
fn return_none_if_empty() {
41+
// ... test code ...
42+
}
43+
```
44+
45+
A test function's signature must have no arguments and no return
46+
value. To run the tests in a crate, it must be compiled with the
47+
'--test' flag: `rustc myprogram.rs --test -o myprogram-tests`. Running
48+
the resulting executable will run all the tests in the crate. A test
49+
is considered successful if its function returns; if the task running
50+
the test fails, through a call to `fail!`, a failed `check` or
51+
`assert`, or some other (`assert_eq`, `assert_approx_eq`, ...) means,
52+
then the test fails.
53+
54+
When compiling a crate with the '--test' flag '--cfg test' is also
55+
implied, so that tests can be conditionally compiled.
56+
57+
```rust
58+
#[cfg(test)]
59+
mod tests {
60+
#[test]
61+
fn return_none_if_empty() {
62+
// ... test code ...
63+
}
64+
}
65+
```
66+
67+
Additionally #[test] items behave as if they also have the
68+
#[cfg(test)] attribute, and will not be compiled when the --test flag
69+
is not used.
70+
71+
Tests that should not be run can be annotated with the 'ignore'
72+
attribute. The existence of these tests will be noted in the test
73+
runner output, but the test will not be run. Tests can also be ignored
74+
by configuration so, for example, to ignore a test on windows you can
75+
write `#[ignore(cfg(target_os = "win32"))]`.
76+
77+
Tests that are intended to fail can be annotated with the
78+
'should_fail' attribute. The test will be run, and if it causes its
79+
task to fail then the test will be counted as successful; otherwise it
80+
will be counted as a failure. For example:
81+
82+
```rust
83+
#[test]
84+
#[should_fail]
85+
fn test_out_of_bounds_failure() {
86+
let v: [int] = [];
87+
v[0];
88+
}
89+
```
90+
91+
A test runner built with the '--test' flag supports a limited set of
92+
arguments to control which tests are run: the first free argument
93+
passed to a test runner specifies a filter used to narrow down the set
94+
of tests being run; the '--ignored' flag tells the test runner to run
95+
only tests with the 'ignore' attribute.
96+
97+
## Parallelism
98+
99+
By default, tests are run in parallel, which can make interpreting
100+
failure output difficult. In these cases you can set the
101+
`RUST_TEST_TASKS` environment variable to 1 to make the tests run
102+
sequentially.
103+
104+
## Benchmarking
105+
106+
The test runner also understands a simple form of benchmark execution.
107+
Benchmark functions are marked with the `#[bench]` attribute, rather
108+
than `#[test]`, and have a different form and meaning. They are
109+
compiled along with `#[test]` functions when a crate is compiled with
110+
`--test`, but they are not run by default. To run the benchmark
111+
component of your testsuite, pass `--bench` to the compiled test
112+
runner.
113+
114+
The type signature of a benchmark function differs from a unit test:
115+
it takes a mutable reference to type `test::BenchHarness`. Inside the
116+
benchmark function, any time-variable or "setup" code should execute
117+
first, followed by a call to `iter` on the benchmark harness, passing
118+
a closure that contains the portion of the benchmark you wish to
119+
actually measure the per-iteration speed of.
120+
121+
For benchmarks relating to processing/generating data, one can set the
122+
`bytes` field to the number of bytes consumed/produced in each
123+
iteration; this will used to show the throughput of the benchmark.
124+
This must be the amount used in each iteration, *not* the total
125+
amount.
126+
127+
For example:
128+
129+
```rust
130+
extern mod extra;
131+
use std::vec;
132+
133+
#[bench]
134+
fn bench_sum_1024_ints(b: &mut extra::test::BenchHarness) {
135+
let v = vec::from_fn(1024, |n| n);
136+
b.iter(|| {v.iter().fold(0, |old, new| old + *new);} );
137+
}
138+
139+
#[bench]
140+
fn initialise_a_vector(b: &mut extra::test::BenchHarness) {
141+
b.iter(|| {vec::from_elem(1024, 0u64);} );
142+
b.bytes = 1024 * 8;
143+
}
144+
```
145+
146+
The benchmark runner will calibrate measurement of the benchmark
147+
function to run the `iter` block "enough" times to get a reliable
148+
measure of the per-iteration speed.
149+
150+
Advice on writing benchmarks:
151+
152+
- Move setup code outside the `iter` loop; only put the part you
153+
want to measure inside
154+
- Make the code do "the same thing" on each iteration; do not
155+
accumulate or change state
156+
- Make the outer function idempotent too; the benchmark runner is
157+
likely to run it many times
158+
- Make the inner `iter` loop short and fast so benchmark runs are
159+
fast and the calibrator can adjust the run-length at fine
160+
resolution
161+
- Make the code in the `iter` loop do something simple, to assist in
162+
pinpointing performance improvements (or regressions)
163+
164+
To run benchmarks, pass the `--bench` flag to the compiled
165+
test-runner. Benchmarks are compiled-in but not executed by default.
166+
167+
## Examples
168+
169+
### Typical test run
170+
171+
```
172+
> mytests
173+
174+
running 30 tests
175+
running driver::tests::mytest1 ... ok
176+
running driver::tests::mytest2 ... ignored
177+
... snip ...
178+
running driver::tests::mytest30 ... ok
179+
180+
result: ok. 28 passed; 0 failed; 2 ignored
181+
```
182+
183+
### Test run with failures
184+
185+
```
186+
> mytests
187+
188+
running 30 tests
189+
running driver::tests::mytest1 ... ok
190+
running driver::tests::mytest2 ... ignored
191+
... snip ...
192+
running driver::tests::mytest30 ... FAILED
193+
194+
result: FAILED. 27 passed; 1 failed; 2 ignored
195+
```
196+
197+
### Running ignored tests
198+
199+
```
200+
> mytests --ignored
201+
202+
running 2 tests
203+
running driver::tests::mytest2 ... failed
204+
running driver::tests::mytest10 ... ok
205+
206+
result: FAILED. 1 passed; 1 failed; 0 ignored
207+
```
208+
209+
### Running a subset of tests
210+
211+
```
212+
> mytests mytest1
213+
214+
running 11 tests
215+
running driver::tests::mytest1 ... ok
216+
running driver::tests::mytest10 ... ignored
217+
... snip ...
218+
running driver::tests::mytest19 ... ok
219+
220+
result: ok. 11 passed; 0 failed; 1 ignored
221+
```
222+
223+
### Running benchmarks
224+
225+
```
226+
> mytests --bench
227+
228+
running 2 tests
229+
test bench_sum_1024_ints ... bench: 709 ns/iter (+/- 82)
230+
test initialise_a_vector ... bench: 424 ns/iter (+/- 99) = 19320 MB/s
231+
232+
test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured
233+
```
234+
235+
## Saving and ratcheting metrics
236+
237+
When running benchmarks or other tests, the test runner can record
238+
per-test "metrics". Each metric is a scalar `f64` value, plus a noise
239+
value which represents uncertainty in the measurement. By default, all
240+
`#[bench]` benchmarks are recorded as metrics, which can be saved as
241+
JSON in an external file for further reporting.
242+
243+
In addition, the test runner supports _ratcheting_ against a metrics
244+
file. Ratcheting is like saving metrics, except that after each run,
245+
if the output file already exists the results of the current run are
246+
compared against the contents of the existing file, and any regression
247+
_causes the testsuite to fail_. If the comparison passes -- if all
248+
metrics stayed the same (within noise) or improved -- then the metrics
249+
file is overwritten with the new values. In this way, a metrics file
250+
in your workspace can be used to ensure your work does not regress
251+
performance.
252+
253+
Test runners take 3 options that are relevant to metrics:
254+
255+
- `--save-metrics=<file.json>` will save the metrics from a test run
256+
to `file.json`
257+
- `--ratchet-metrics=<file.json>` will ratchet the metrics against
258+
the `file.json`
259+
- `--ratchet-noise-percent=N` will override the noise measurements
260+
in `file.json`, and consider a metric change less than `N%` to be
261+
noise. This can be helpful if you are testing in a noisy
262+
environment where the benchmark calibration loop cannot acquire a
263+
clear enough signal.

doc/tutorial.md

+2
Original file line numberDiff line numberDiff line change
@@ -3220,6 +3220,7 @@ tutorials on individual topics.
32203220
* [Error-handling and Conditions][conditions]
32213221
* [Packaging up Rust code][rustpkg]
32223222
* [Documenting Rust code][rustdoc]
3223+
* [Testing Rust code][testing]
32233224

32243225
There is further documentation on the [wiki], however those tend to be even
32253226
more out of date than this document.
@@ -3231,6 +3232,7 @@ more out of date than this document.
32313232
[container]: tutorial-container.html
32323233
[conditions]: tutorial-conditions.html
32333234
[rustpkg]: tutorial-rustpkg.html
3235+
[testing]: tutorial-testing.html
32343236
[rustdoc]: rustdoc.html
32353237

32363238
[wiki]: https://github.com/mozilla/rust/wiki/Docs

mk/docs.mk

+7
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,13 @@ doc/tutorial-ffi.html: tutorial-ffi.md doc/version_info.html doc/rust.css \
133133
$(Q)$(CFG_NODE) $(S)doc/prep.js --highlight $< | \
134134
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
135135

136+
DOCS += doc/tutorial-testing.html
137+
doc/tutorial-testing.html: tutorial-testing.md doc/version_info.html doc/rust.css \
138+
doc/favicon.inc
139+
@$(call E, pandoc: $@)
140+
$(Q)$(CFG_NODE) $(S)doc/prep.js --highlight $< | \
141+
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
142+
136143
DOCS += doc/tutorial-borrowed-ptr.html
137144
doc/tutorial-borrowed-ptr.html: tutorial-borrowed-ptr.md doc/version_info.html doc/rust.css \
138145
doc/favicon.inc

0 commit comments

Comments
 (0)