Skip to content

Commit c0b82b5

Browse files
committed
initial import/re-write of testing chapter
1 parent 1b76d4d commit c0b82b5

4 files changed

Lines changed: 872 additions & 0 deletions

File tree

src/chXX-00-testing.md

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,280 @@
11
# Testing
2+
3+
> Program testing can be a very effective way to show the presence of bugs, but
4+
> it is hopelessly inadequate for showing their absence.
5+
>
6+
> Edsger W. Dijkstra, "The Humble Programmer" (1972)
7+
8+
Rust is a programming language that cares a lot about correctness. But
9+
correctness is a complex topic, and isn't exactly easy to get right. Rust
10+
places a lot of weight on its type system to help ensure that our programs do
11+
what we intend, but it cannot help with everything. As such, Rust also includes
12+
support for writing software tests in the language itself.
13+
14+
Testing is a skill, and we cannot hope to learn everything about how to write
15+
good tests in one chapter of a book. What we can learn, however, are the
16+
mechanics of Rust's testing facilities. That's what we'll focus on in this
17+
chapter.
18+
19+
## The `test` attribute
20+
21+
At its simplest, a test in Rust is a function that's annotated with the `test`
22+
attribute. Let's make a new project with Cargo called `adder`:
23+
24+
```bash
25+
$ cargo new adder
26+
Created library `adder` project
27+
$ cd adder
28+
```
29+
30+
Cargo will automatically generate a simple test when you make a new project.
31+
Here's the contents of `src/lib.rs`:
32+
33+
```rust
34+
#[cfg(test)]
35+
mod tests {
36+
#[test]
37+
fn it_works() {
38+
}
39+
}
40+
```
41+
42+
For now, let's remove the `mod` bit, and focus on just the function:
43+
44+
```rust
45+
#[test]
46+
fn it_works() {
47+
}
48+
```
49+
50+
Note the `#[test]`. This attribute indicates that this is a test function. It
51+
currently has no body. That's good enough to pass! We can run the tests with
52+
`cargo test`:
53+
54+
```bash
55+
$ cargo test
56+
Compiling adder v0.1.0 (file:///projects/adder)
57+
Finished debug [unoptimized + debuginfo] target(s) in 0.22 secs
58+
Running target/debug/deps/adder-ce99bcc2479f4607
59+
60+
running 1 test
61+
test it_works ... ok
62+
63+
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
64+
65+
Doc-tests adder
66+
67+
running 0 tests
68+
69+
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
70+
```
71+
72+
Cargo compiled and ran our tests. There are two sets of output here: one
73+
for the test we wrote, and another for documentation tests. We'll talk about
74+
documentation tests later. For now, see this line:
75+
76+
```text
77+
test it_works ... ok
78+
```
79+
80+
Note the `it_works`. This comes from the name of our function:
81+
82+
```rust
83+
fn it_works() {
84+
# }
85+
```
86+
87+
We also get a summary line:
88+
89+
```text
90+
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
91+
```
92+
93+
## The `assert!` macro
94+
95+
So why does our do-nothing test pass? Any test which doesn't `panic!` passes,
96+
and any test that does `panic!` fails. Let's make our test fail:
97+
98+
```rust
99+
#[test]
100+
fn it_works() {
101+
assert!(false);
102+
}
103+
```
104+
105+
`assert!` is a macro provided by Rust which takes one argument: if the argument
106+
is `true`, nothing happens. If the argument is `false`, it will `panic!`. Let's
107+
run our tests again:
108+
109+
```bash
110+
$ cargo test
111+
Compiling adder v0.1.0 (file:///projects/adder)
112+
Finished debug [unoptimized + debuginfo] target(s) in 0.22 secs
113+
Running target/debug/deps/adder-ce99bcc2479f4607
114+
115+
running 1 test
116+
test it_works ... FAILED
117+
118+
failures:
119+
120+
---- it_works stdout ----
121+
thread 'it_works' panicked at 'assertion failed: false', src/lib.rs:5
122+
note: Run with `RUST_BACKTRACE=1` for a backtrace.
123+
124+
125+
failures:
126+
it_works
127+
128+
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
129+
130+
error: test failed
131+
```
132+
133+
Rust indicates that our test failed:
134+
135+
```text
136+
test it_works ... FAILED
137+
```
138+
139+
And that's reflected in the summary line:
140+
141+
```text
142+
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
143+
```
144+
145+
## Inverting failure with `should_panic`
146+
147+
We can invert our test's failure with another attribute: `should_panic`:
148+
149+
```rust
150+
#[test]
151+
#[should_panic]
152+
fn it_works() {
153+
assert!(false);
154+
}
155+
```
156+
157+
This test will now succeed if we `panic!` and fail if we complete.
158+
159+
`should_panic` tests can be fragile, as it's hard to guarantee that the test
160+
didn't fail for an unexpected reason. To help with this, an optional `expected`
161+
parameter can be added to the `should_panic` attribute. The test harness will
162+
make sure that the failure message contains the provided text. A safer version
163+
of the example above would be:
164+
165+
```rust
166+
#[test]
167+
#[should_panic(expected = "assertion failed")]
168+
fn it_works() {
169+
assert!(false);
170+
}
171+
```
172+
173+
## Testing equality
174+
175+
Rust provides a pair of macros, `assert_eq!` and `assert_ne!`, that compares
176+
two arguments for equality:
177+
178+
```rust
179+
#[test]
180+
fn it_works() {
181+
assert_eq!("Hello", "Hello");
182+
183+
assert_ne!("Hello", "world");
184+
}
185+
```
186+
187+
These macros expand to something like this:
188+
189+
```rust,ignore
190+
// assert_eq
191+
if left_val == right_val {
192+
panic!("message goes here")
193+
}
194+
195+
// assert_ne
196+
if left_val =! right_val {
197+
panic!("message goes here")
198+
}
199+
```
200+
201+
But they're a bit more convenient than writing this out by hand. These macros
202+
are often used to call some function with some known arguments and compare it
203+
to the expected output, like this:
204+
205+
```rust
206+
pub fn add_two(a: i32) -> i32 {
207+
a + 2
208+
}
209+
210+
#[test]
211+
fn it_works() {
212+
assert_eq!(4, add_two(2));
213+
}
214+
```
215+
216+
## The `ignore` attribute
217+
218+
Sometimes a few specific tests can be very time-consuming to execute. These
219+
can be disabled by default by using the `ignore` attribute:
220+
221+
```rust
222+
pub fn add_two(a: i32) -> i32 {
223+
a + 2
224+
}
225+
226+
#[test]
227+
fn it_works() {
228+
assert_eq!(4, add_two(2));
229+
}
230+
231+
#[test]
232+
#[ignore]
233+
fn expensive_test() {
234+
// code that takes an hour to run
235+
}
236+
```
237+
238+
Now we run our tests and see that `it_works` is run, but `expensive_test` is
239+
not:
240+
241+
```bash
242+
$ cargo test
243+
Compiling adder v0.1.0 (file:///projects/adder)
244+
Finished debug [unoptimized + debuginfo] target(s) in 0.24 secs
245+
Running target/debug/deps/adder-ce99bcc2479f4607
246+
247+
running 2 tests
248+
test expensive_test ... ignored
249+
test it_works ... ok
250+
251+
test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured
252+
253+
Doc-tests adder
254+
255+
running 0 tests
256+
257+
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
258+
```
259+
260+
The expensive tests can be run explicitly using `cargo test -- --ignored`:
261+
262+
```bash
263+
$ cargo test -- --ignored
264+
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
265+
Running target/debug/deps/adder-ce99bcc2479f4607
266+
267+
running 1 test
268+
test expensive_test ... ok
269+
270+
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
271+
272+
Doc-tests adder
273+
274+
running 0 tests
275+
276+
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
277+
```
278+
279+
The `--ignored` argument is an argument to the test binary, and not to Cargo,
280+
which is why the command is `cargo test -- --ignored`.

0 commit comments

Comments
 (0)