]>
Commit | Line | Data |
---|---|---|
cc61c64b XL |
1 | ## How to Write Tests |
2 | ||
3b2f2976 XL |
3 | Tests are Rust functions that verify that the non-test code is functioning in |
4 | the expected manner. The bodies of test functions typically perform some setup, | |
5 | run the code we want to test, then assert whether the results are what we | |
6 | expect. Let’s look at the features Rust provides specifically for writing | |
7 | tests: the `test` attribute, a few macros, and the `should_panic` attribute. | |
cc61c64b XL |
8 | |
9 | ### The Anatomy of a Test Function | |
10 | ||
041b39d2 | 11 | At its simplest, a test in Rust is a function that’s annotated with the `test` |
cc61c64b XL |
12 | attribute. Attributes are metadata about pieces of Rust code: the `derive` |
13 | attribute that we used with structs in Chapter 5 is one example. To make a | |
14 | function into a test function, we add `#[test]` on the line before `fn`. When | |
15 | we run our tests with the `cargo test` command, Rust will build a test runner | |
16 | binary that runs the functions annotated with the `test` attribute and reports | |
17 | on whether each test function passes or fails. | |
18 | ||
cc61c64b XL |
19 | We saw in Chapter 7 that when you make a new library project with Cargo, a test |
20 | module with a test function in it is automatically generated for us. This is to | |
3b2f2976 | 21 | help us get started writing our tests so we don’t have to go look up the |
cc61c64b XL |
22 | exact structure and syntax of test functions every time we start a new project. |
23 | We can add as many additional test functions and as many test modules as we | |
24 | want, though! | |
25 | ||
041b39d2 XL |
26 | We’re going to explore some aspects of how tests work by experimenting with the |
27 | template test generated for us, without actually testing any code. Then we’ll | |
28 | write some real-world tests that call some code that we’ve written and assert | |
cc61c64b XL |
29 | that its behavior is correct. |
30 | ||
041b39d2 | 31 | Let’s create a new library project called `adder`: |
cc61c64b XL |
32 | |
33 | ```text | |
34 | $ cargo new adder | |
35 | Created library `adder` project | |
36 | $ cd adder | |
37 | ``` | |
38 | ||
39 | The contents of the `src/lib.rs` file in your adder library should be as | |
40 | follows: | |
41 | ||
42 | <span class="filename">Filename: src/lib.rs</span> | |
43 | ||
44 | ```rust | |
45 | #[cfg(test)] | |
46 | mod tests { | |
47 | #[test] | |
48 | fn it_works() { | |
49 | } | |
50 | } | |
51 | ``` | |
52 | ||
53 | <span class="caption">Listing 11-1: The test module and function generated | |
3b2f2976 | 54 | automatically for us by `cargo new`</span> |
cc61c64b | 55 | |
041b39d2 | 56 | For now, let’s ignore the top two lines and focus on the function to see how it |
cc61c64b XL |
57 | works. Note the `#[test]` annotation before the `fn` line: this attribute |
58 | indicates this is a test function, so that the test runner knows to treat this | |
59 | function as a test. We could also have non-test functions in the `tests` module | |
60 | to help set up common scenarios or perform common operations, so we need to | |
61 | indicate which functions are tests with the `#[test]` attribute. | |
62 | ||
63 | The function currently has no body, which means there is no code to fail the | |
041b39d2 | 64 | test; an empty test is a passing test! Let’s run it and see that this test |
cc61c64b XL |
65 | passes. |
66 | ||
67 | The `cargo test` command runs all tests we have in our project, as shown in | |
68 | Listing 11-2: | |
69 | ||
70 | ```text | |
71 | $ cargo test | |
72 | Compiling adder v0.1.0 (file:///projects/adder) | |
7cac9316 | 73 | Finished dev [unoptimized + debuginfo] target(s) in 0.22 secs |
cc61c64b XL |
74 | Running target/debug/deps/adder-ce99bcc2479f4607 |
75 | ||
76 | running 1 test | |
77 | test tests::it_works ... ok | |
78 | ||
79 | test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured | |
80 | ||
81 | Doc-tests adder | |
82 | ||
83 | running 0 tests | |
84 | ||
85 | test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured | |
86 | ``` | |
87 | ||
88 | <span class="caption">Listing 11-2: The output from running the one | |
3b2f2976 | 89 | automatically generated test</span> |
cc61c64b XL |
90 | |
91 | Cargo compiled and ran our test. After the `Compiling`, `Finished`, and | |
92 | `Running` lines, we see the line `running 1 test`. The next line shows the name | |
93 | of the generated test function, called `it_works`, and the result of running | |
94 | that test, `ok`. Then we see the overall summary of running the tests: `test | |
95 | result: ok.` means all the tests passed. `1 passed; 0 failed` adds up the | |
96 | number of tests that passed or failed. | |
97 | ||
041b39d2 XL |
98 | We don’t have any tests we’ve marked as ignored, so the summary says `0 |
99 | ignored`. We’re going to talk about ignoring tests in the next section on | |
cc61c64b XL |
100 | different ways to run tests. The `0 measured` statistic is for benchmark tests |
101 | that measure performance. Benchmark tests are, as of this writing, only | |
102 | available in nightly Rust. See Appendix D for more information about nightly | |
103 | Rust. | |
104 | ||
105 | The next part of the test output that starts with `Doc-tests adder` is for the | |
041b39d2 | 106 | results of any documentation tests. We don’t have any documentation tests yet, |
cc61c64b | 107 | but Rust can compile any code examples that appear in our API documentation. |
041b39d2 XL |
108 | This feature helps us keep our docs and our code in sync! We’ll be talking |
109 | about how to write documentation tests in the “Documentation Comments” section | |
110 | of Chapter 14. We’re going to ignore the `Doc-tests` output for now. | |
111 | ||
112 | Let’s change the name of our test and see how that changes the test output. | |
cc61c64b XL |
113 | Give the `it_works` function a different name, such as `exploration`, like so: |
114 | ||
115 | <span class="filename">Filename: src/lib.rs</span> | |
116 | ||
117 | ```rust | |
118 | #[cfg(test)] | |
119 | mod tests { | |
120 | #[test] | |
121 | fn exploration() { | |
122 | } | |
123 | } | |
124 | ``` | |
125 | ||
041b39d2 | 126 | And run `cargo test` again. In the output, we’ll now see `exploration` instead |
cc61c64b XL |
127 | of `it_works`: |
128 | ||
129 | ```text | |
130 | running 1 test | |
131 | test tests::exploration ... ok | |
132 | ||
133 | test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured | |
134 | ``` | |
135 | ||
041b39d2 | 136 | Let’s add another test, but this time we’ll make a test that fails! Tests fail |
3b2f2976 XL |
137 | when something in the test function panics. Each test is run in a new thread, |
138 | and when the main thread sees that a test thread has died, the test is marked | |
139 | as failed. We talked about the simplest way to cause a panic in Chapter 9: call | |
140 | the `panic!` macro! Type in the new test so that your `src/lib.rs` now looks | |
141 | like Listing 11-3: | |
cc61c64b XL |
142 | |
143 | <span class="filename">Filename: src/lib.rs</span> | |
144 | ||
145 | ```rust | |
146 | #[cfg(test)] | |
147 | mod tests { | |
148 | #[test] | |
149 | fn exploration() { | |
150 | } | |
151 | ||
152 | #[test] | |
153 | fn another() { | |
154 | panic!("Make this test fail"); | |
155 | } | |
156 | } | |
157 | ``` | |
158 | ||
159 | <span class="caption">Listing 11-3: Adding a second test; one that will fail | |
3b2f2976 | 160 | since we call the `panic!` macro</span> |
cc61c64b XL |
161 | |
162 | And run the tests again with `cargo test`. The output should look like Listing | |
163 | 11-4, which shows that our `exploration` test passed and `another` failed: | |
164 | ||
165 | ```text | |
166 | running 2 tests | |
167 | test tests::exploration ... ok | |
168 | test tests::another ... FAILED | |
169 | ||
170 | failures: | |
171 | ||
172 | ---- tests::another stdout ---- | |
173 | thread 'tests::another' panicked at 'Make this test fail', src/lib.rs:9 | |
174 | note: Run with `RUST_BACKTRACE=1` for a backtrace. | |
175 | ||
176 | failures: | |
177 | tests::another | |
178 | ||
179 | test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured | |
180 | ||
181 | error: test failed | |
182 | ``` | |
183 | ||
184 | <span class="caption">Listing 11-4: Test results when one test passes and one | |
3b2f2976 | 185 | test fails</span> |
cc61c64b XL |
186 | |
187 | Instead of `ok`, the line `test tests::another` says `FAILED`. We have two new | |
188 | sections between the individual results and the summary: the first section | |
189 | displays the detailed reason for the test failures. In this case, `another` | |
190 | failed because it `panicked at 'Make this test fail'`, which happened on | |
191 | *src/lib.rs* line 9. The next section lists just the names of all the failing | |
192 | tests, which is useful when there are lots of tests and lots of detailed | |
193 | failing test output. We can use the name of a failing test to run just that | |
041b39d2 | 194 | test in order to more easily debug it; we’ll talk more about ways to run tests |
cc61c64b XL |
195 | in the next section. |
196 | ||
197 | Finally, we have the summary line: overall, our test result is `FAILED`. We had | |
198 | 1 test pass and 1 test fail. | |
199 | ||
041b39d2 XL |
200 | Now that we’ve seen what the test results look like in different scenarios, |
201 | let’s look at some macros other than `panic!` that are useful in tests. | |
cc61c64b XL |
202 | |
203 | ### Checking Results with the `assert!` Macro | |
204 | ||
205 | The `assert!` macro, provided by the standard library, is useful when you want | |
206 | to ensure that some condition in a test evaluates to `true`. We give the | |
207 | `assert!` macro an argument that evaluates to a boolean. If the value is `true`, | |
208 | `assert!` does nothing and the test passes. If the value is `false`, `assert!` | |
209 | calls the `panic!` macro, which causes the test to fail. This is one macro that | |
210 | helps us check that our code is functioning in the way we intend. | |
211 | ||
cc61c64b | 212 | Remember all the way back in Chapter 5, Listing 5-9, where we had a `Rectangle` |
041b39d2 | 213 | struct and a `can_hold` method, repeated here in Listing 11-5. Let’s put this |
ea8adc8c | 214 | code in *src/lib.rs* and write some tests for it using the `assert!` macro. |
cc61c64b | 215 | |
cc61c64b XL |
216 | <span class="filename">Filename: src/lib.rs</span> |
217 | ||
218 | ```rust | |
219 | #[derive(Debug)] | |
220 | pub struct Rectangle { | |
221 | length: u32, | |
222 | width: u32, | |
223 | } | |
224 | ||
225 | impl Rectangle { | |
226 | pub fn can_hold(&self, other: &Rectangle) -> bool { | |
227 | self.length > other.length && self.width > other.width | |
228 | } | |
229 | } | |
230 | ``` | |
231 | ||
232 | <span class="caption">Listing 11-5: The `Rectangle` struct and its `can_hold` | |
3b2f2976 | 233 | method from Chapter 5</span> |
cc61c64b | 234 | |
041b39d2 XL |
235 | The `can_hold` method returns a boolean, which means it’s a perfect use case |
236 | for the `assert!` macro. In Listing 11-6, let’s write a test that exercises the | |
cc61c64b XL |
237 | `can_hold` method by creating a `Rectangle` instance that has a length of 8 and |
238 | a width of 7, and asserting that it can hold another `Rectangle` instance that | |
239 | has a length of 5 and a width of 1: | |
240 | ||
241 | <span class="filename">Filename: src/lib.rs</span> | |
242 | ||
243 | ```rust | |
244 | #[cfg(test)] | |
245 | mod tests { | |
246 | use super::*; | |
247 | ||
248 | #[test] | |
249 | fn larger_can_hold_smaller() { | |
250 | let larger = Rectangle { length: 8, width: 7 }; | |
251 | let smaller = Rectangle { length: 5, width: 1 }; | |
252 | ||
253 | assert!(larger.can_hold(&smaller)); | |
254 | } | |
255 | } | |
256 | ``` | |
257 | ||
258 | <span class="caption">Listing 11-6: A test for `can_hold` that checks that a | |
3b2f2976 | 259 | larger rectangle indeed holds a smaller rectangle</span> |
cc61c64b | 260 | |
041b39d2 | 261 | Note that we’ve added a new line inside the `tests` module: `use super::*;`. |
cc61c64b | 262 | The `tests` module is a regular module that follows the usual visibility rules |
041b39d2 XL |
263 | we covered in Chapter 7. Because we’re in an inner module, we need to bring the |
264 | code under test in the outer module into the scope of the inner module. We’ve | |
cc61c64b XL |
265 | chosen to use a glob here so that anything we define in the outer module is |
266 | available to this `tests` module. | |
267 | ||
041b39d2 | 268 | We’ve named our test `larger_can_hold_smaller`, and we’ve created the two |
cc61c64b XL |
269 | `Rectangle` instances that we need. Then we called the `assert!` macro and |
270 | passed it the result of calling `larger.can_hold(&smaller)`. This expression is | |
041b39d2 | 271 | supposed to return `true`, so our test should pass. Let’s find out! |
cc61c64b XL |
272 | |
273 | ```text | |
274 | running 1 test | |
275 | test tests::larger_can_hold_smaller ... ok | |
276 | ||
277 | test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured | |
278 | ``` | |
279 | ||
041b39d2 | 280 | It does pass! Let’s add another test, this time asserting that a smaller |
cc61c64b XL |
281 | rectangle cannot hold a larger rectangle: |
282 | ||
283 | <span class="filename">Filename: src/lib.rs</span> | |
284 | ||
285 | ```rust | |
286 | #[cfg(test)] | |
287 | mod tests { | |
288 | use super::*; | |
289 | ||
290 | #[test] | |
291 | fn larger_can_hold_smaller() { | |
292 | let larger = Rectangle { length: 8, width: 7 }; | |
293 | let smaller = Rectangle { length: 5, width: 1 }; | |
294 | ||
295 | assert!(larger.can_hold(&smaller)); | |
296 | } | |
297 | ||
298 | #[test] | |
041b39d2 | 299 | fn smaller_cannot_hold_larger() { |
cc61c64b XL |
300 | let larger = Rectangle { length: 8, width: 7 }; |
301 | let smaller = Rectangle { length: 5, width: 1 }; | |
302 | ||
303 | assert!(!smaller.can_hold(&larger)); | |
304 | } | |
305 | } | |
306 | ``` | |
307 | ||
308 | Because the correct result of the `can_hold` function in this case is `false`, | |
309 | we need to negate that result before we pass it to the `assert!` macro. This | |
310 | way, our test will pass if `can_hold` returns `false`: | |
311 | ||
312 | ```text | |
313 | running 2 tests | |
041b39d2 | 314 | test tests::smaller_cannot_hold_larger ... ok |
cc61c64b XL |
315 | test tests::larger_can_hold_smaller ... ok |
316 | ||
317 | test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured | |
318 | ``` | |
319 | ||
041b39d2 XL |
320 | Two passing tests! Now let’s see what happens to our test results if we |
321 | introduce a bug in our code. Let’s change the implementation of the `can_hold` | |
322 | method to have a less-than sign when it compares the lengths where it’s | |
cc61c64b XL |
323 | supposed to have a greater-than sign: |
324 | ||
325 | ```rust | |
326 | #[derive(Debug)] | |
327 | pub struct Rectangle { | |
328 | length: u32, | |
329 | width: u32, | |
330 | } | |
331 | ||
332 | impl Rectangle { | |
333 | pub fn can_hold(&self, other: &Rectangle) -> bool { | |
334 | self.length < other.length && self.width > other.width | |
335 | } | |
336 | } | |
337 | ``` | |
338 | ||
339 | Running the tests now produces: | |
340 | ||
341 | ```text | |
342 | running 2 tests | |
041b39d2 | 343 | test tests::smaller_cannot_hold_larger ... ok |
cc61c64b XL |
344 | test tests::larger_can_hold_smaller ... FAILED |
345 | ||
346 | failures: | |
347 | ||
348 | ---- tests::larger_can_hold_smaller stdout ---- | |
349 | thread 'tests::larger_can_hold_smaller' panicked at 'assertion failed: | |
350 | larger.can_hold(&smaller)', src/lib.rs:22 | |
351 | note: Run with `RUST_BACKTRACE=1` for a backtrace. | |
352 | ||
353 | failures: | |
354 | tests::larger_can_hold_smaller | |
355 | ||
356 | test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured | |
357 | ``` | |
358 | ||
359 | Our tests caught the bug! Since `larger.length` is 8 and `smaller.length` is 5, | |
360 | the comparison of the lengths in `can_hold` now returns `false` since 8 is not | |
361 | less than 5. | |
362 | ||
363 | ### Testing Equality with the `assert_eq!` and `assert_ne!` Macros | |
364 | ||
365 | A common way to test functionality is to take the result of the code under test | |
041b39d2 | 366 | and the value we expect the code to return and check that they’re equal. We |
cc61c64b XL |
367 | could do this using the `assert!` macro and passing it an expression using the |
368 | `==` operator. However, this is such a common test that the standard library | |
369 | provides a pair of macros to perform this test more conveniently: `assert_eq!` | |
370 | and `assert_ne!`. These macros compare two arguments for equality or | |
041b39d2 XL |
371 | inequality, respectively. They’ll also print out the two values if the |
372 | assertion fails, so that it’s easier to see *why* the test failed, while the | |
cc61c64b XL |
373 | `assert!` macro only tells us that it got a `false` value for the `==` |
374 | expression, not the values that lead to the `false` value. | |
375 | ||
041b39d2 XL |
376 | In Listing 11-7, let’s write a function named `add_two` that adds two to its |
377 | parameter and returns the result. Then let’s test this function using the | |
cc61c64b XL |
378 | `assert_eq!` macro: |
379 | ||
380 | <span class="filename">Filename: src/lib.rs</span> | |
381 | ||
382 | ```rust | |
383 | pub fn add_two(a: i32) -> i32 { | |
384 | a + 2 | |
385 | } | |
386 | ||
387 | #[cfg(test)] | |
388 | mod tests { | |
389 | use super::*; | |
390 | ||
391 | #[test] | |
392 | fn it_adds_two() { | |
393 | assert_eq!(4, add_two(2)); | |
394 | } | |
395 | } | |
396 | ``` | |
397 | ||
398 | <span class="caption">Listing 11-7: Testing the function `add_two` using the | |
3b2f2976 | 399 | `assert_eq!` macro</span> |
cc61c64b | 400 | |
041b39d2 | 401 | Let’s check that it passes! |
cc61c64b XL |
402 | |
403 | ```text | |
404 | running 1 test | |
405 | test tests::it_adds_two ... ok | |
406 | ||
407 | test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured | |
408 | ``` | |
409 | ||
410 | The first argument we gave to the `assert_eq!` macro, 4, is equal to the result | |
411 | of calling `add_two(2)`. We see a line for this test that says `test | |
412 | tests::it_adds_two ... ok`, and the `ok` text indicates that our test passed! | |
413 | ||
041b39d2 | 414 | Let’s introduce a bug into our code to see what it looks like when a test that |
cc61c64b XL |
415 | uses `assert_eq!` fails. Change the implementation of the `add_two` function to |
416 | instead add 3: | |
417 | ||
418 | ```rust | |
419 | pub fn add_two(a: i32) -> i32 { | |
420 | a + 3 | |
421 | } | |
422 | ``` | |
423 | ||
424 | And run the tests again: | |
425 | ||
426 | ```text | |
427 | running 1 test | |
428 | test tests::it_adds_two ... FAILED | |
429 | ||
430 | failures: | |
431 | ||
432 | ---- tests::it_adds_two stdout ---- | |
433 | thread 'tests::it_adds_two' panicked at 'assertion failed: `(left == | |
434 | right)` (left: `4`, right: `5`)', src/lib.rs:11 | |
435 | note: Run with `RUST_BACKTRACE=1` for a backtrace. | |
436 | ||
437 | failures: | |
438 | tests::it_adds_two | |
439 | ||
440 | test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured | |
441 | ``` | |
442 | ||
443 | Our test caught the bug! The `it_adds_two` test failed with the message `` | |
444 | assertion failed: `(left == right)` (left: `4`, right: `5`) ``. This message is | |
445 | useful and helps us get started debugging: it says the `left` argument to | |
446 | `assert_eq!` was 4, but the `right` argument, where we had `add_two(2)`, was 5. | |
447 | ||
448 | Note that in some languages and test frameworks, the parameters to the | |
449 | functions that assert two values are equal are called `expected` and `actual` | |
450 | and the order in which we specify the arguments matters. However, in Rust, | |
041b39d2 XL |
451 | they’re called `left` and `right` instead, and the order in which we specify |
452 | the value we expect and the value that the code under test produces doesn’t | |
3b2f2976 | 453 | matter. We could write the assertion in this test as |
cc61c64b XL |
454 | `assert_eq!(add_two(2), 4)`, which would result in a failure message that says |
455 | `` assertion failed: `(left == right)` (left: `5`, right: `4`) ``. | |
456 | ||
457 | The `assert_ne!` macro will pass if the two values we give to it are not equal | |
041b39d2 | 458 | and fail if they are equal. This macro is most useful for cases when we’re not |
cc61c64b | 459 | sure exactly what a value *will* be, but we know what the value definitely |
041b39d2 | 460 | *won’t* be, if our code is functioning as we intend. For example, if we have a |
cc61c64b XL |
461 | function that is guaranteed to change its input in some way, but the way in |
462 | which the input is changed depends on the day of the week that we run our | |
463 | tests, the best thing to assert might be that the output of the function is not | |
464 | equal to the input. | |
465 | ||
466 | Under the surface, the `assert_eq!` and `assert_ne!` macros use the operators | |
467 | `==` and `!=`, respectively. When the assertions fail, these macros print their | |
468 | arguments using debug formatting, which means the values being compared must | |
469 | implement the `PartialEq` and `Debug` traits. All of the primitive types and | |
470 | most of the standard library types implement these traits. For structs and | |
041b39d2 XL |
471 | enums that you define, you’ll need to implement `PartialEq` in order to be able |
472 | to assert that values of those types are equal or not equal. You’ll need to | |
cc61c64b XL |
473 | implement `Debug` in order to be able to print out the values in the case that |
474 | the assertion fails. Because both of these traits are derivable traits, as we | |
475 | mentioned in Chapter 5, this is usually as straightforward as adding the | |
476 | `#[derive(PartialEq, Debug)]` annotation to your struct or enum definition. See | |
477 | Appendix C for more details about these and other derivable traits. | |
478 | ||
479 | ### Custom Failure Messages | |
480 | ||
481 | We can also add a custom message to be printed with the failure message as | |
482 | optional arguments to `assert!`, `assert_eq!`, and `assert_ne!`. Any arguments | |
483 | specified after the one required argument to `assert!` or the two required | |
484 | arguments to `assert_eq!` and `assert_ne!` are passed along to the `format!` | |
485 | macro that we talked about in Chapter 8, so you can pass a format string that | |
486 | contains `{}` placeholders and values to go in the placeholders. Custom | |
487 | messages are useful in order to document what an assertion means, so that when | |
488 | the test fails, we have a better idea of what the problem is with the code. | |
489 | ||
041b39d2 | 490 | For example, let’s say we have a function that greets people by name, and we |
cc61c64b XL |
491 | want to test that the name we pass into the function appears in the output: |
492 | ||
493 | <span class="filename">Filename: src/lib.rs</span> | |
494 | ||
495 | ```rust | |
496 | pub fn greeting(name: &str) -> String { | |
497 | format!("Hello {}!", name) | |
498 | } | |
499 | ||
500 | #[cfg(test)] | |
501 | mod tests { | |
502 | use super::*; | |
503 | ||
504 | #[test] | |
505 | fn greeting_contains_name() { | |
506 | let result = greeting("Carol"); | |
507 | assert!(result.contains("Carol")); | |
508 | } | |
509 | } | |
510 | ``` | |
511 | ||
041b39d2 | 512 | The requirements for this program haven’t been agreed upon yet, and we’re |
cc61c64b | 513 | pretty sure the `Hello` text at the beginning of the greeting will change. We |
041b39d2 | 514 | decided we don’t want to have to update the test for the name when that |
cc61c64b | 515 | happens, so instead of checking for exact equality to the value returned from |
041b39d2 | 516 | the `greeting` function, we’re just going to assert that the output contains |
cc61c64b XL |
517 | the text of the input parameter. |
518 | ||
041b39d2 | 519 | Let’s introduce a bug into this code to see what this test failure looks like, |
cc61c64b XL |
520 | by changing `greeting` to not include `name`: |
521 | ||
522 | ```rust | |
523 | pub fn greeting(name: &str) -> String { | |
524 | String::from("Hello!") | |
525 | } | |
526 | ``` | |
527 | ||
528 | Running this test produces: | |
529 | ||
530 | ```text | |
531 | running 1 test | |
532 | test tests::greeting_contains_name ... FAILED | |
533 | ||
534 | failures: | |
535 | ||
536 | ---- tests::greeting_contains_name stdout ---- | |
537 | thread 'tests::greeting_contains_name' panicked at 'assertion failed: | |
538 | result.contains("Carol")', src/lib.rs:12 | |
539 | note: Run with `RUST_BACKTRACE=1` for a backtrace. | |
540 | ||
541 | failures: | |
542 | tests::greeting_contains_name | |
543 | ``` | |
544 | ||
545 | This just tells us that the assertion failed and which line the assertion is | |
546 | on. A more useful failure message in this case would print the value we did get | |
041b39d2 | 547 | from the `greeting` function. Let’s change the test function to have a custom |
cc61c64b XL |
548 | failure message made from a format string with a placeholder filled in with the |
549 | actual value we got from the `greeting` function: | |
550 | ||
551 | ```rust,ignore | |
552 | #[test] | |
553 | fn greeting_contains_name() { | |
554 | let result = greeting("Carol"); | |
555 | assert!( | |
556 | result.contains("Carol"), | |
557 | "Greeting did not contain name, value was `{}`", result | |
558 | ); | |
559 | } | |
560 | ``` | |
561 | ||
041b39d2 | 562 | Now if we run the test again, we’ll get a much more informative error message: |
cc61c64b XL |
563 | |
564 | ```text | |
565 | ---- tests::greeting_contains_name stdout ---- | |
7cac9316 | 566 | thread 'tests::greeting_contains_name' panicked at 'Greeting did not contain |
cc61c64b XL |
567 | name, value was `Hello`', src/lib.rs:12 |
568 | note: Run with `RUST_BACKTRACE=1` for a backtrace. | |
569 | ``` | |
570 | ||
571 | We can see the value we actually got in the test output, which would help us | |
572 | debug what happened instead of what we were expecting to happen. | |
573 | ||
574 | ### Checking for Panics with `should_panic` | |
575 | ||
576 | In addition to checking that our code returns the correct values we expect, | |
041b39d2 | 577 | it’s also important to check that our code handles error conditions as we |
cc61c64b XL |
578 | expect. For example, consider the `Guess` type that we created in Chapter 9 in |
579 | Listing 9-8. Other code that uses `Guess` is depending on the guarantee that | |
580 | `Guess` instances will only contain values between 1 and 100. We can write a | |
581 | test that ensures that attempting to create a `Guess` instance with a value | |
582 | outside that range panics. | |
583 | ||
584 | We can do this by adding another attribute, `should_panic`, to our test | |
585 | function. This attribute makes a test pass if the code inside the function | |
3b2f2976 | 586 | panics, and the test will fail if the code inside the function doesn’t panic. |
cc61c64b | 587 | |
041b39d2 | 588 | Listing 11-8 shows how we’d write a test that checks the error conditions of |
cc61c64b XL |
589 | `Guess::new` happen when we expect: |
590 | ||
591 | <span class="filename">Filename: src/lib.rs</span> | |
592 | ||
593 | ```rust | |
ea8adc8c | 594 | pub struct Guess { |
cc61c64b XL |
595 | value: u32, |
596 | } | |
597 | ||
598 | impl Guess { | |
599 | pub fn new(value: u32) -> Guess { | |
600 | if value < 1 || value > 100 { | |
601 | panic!("Guess value must be between 1 and 100, got {}.", value); | |
602 | } | |
603 | ||
604 | Guess { | |
7cac9316 | 605 | value |
cc61c64b XL |
606 | } |
607 | } | |
608 | } | |
609 | ||
610 | #[cfg(test)] | |
611 | mod tests { | |
612 | use super::*; | |
613 | ||
614 | #[test] | |
615 | #[should_panic] | |
616 | fn greater_than_100() { | |
617 | Guess::new(200); | |
618 | } | |
619 | } | |
620 | ``` | |
621 | ||
622 | <span class="caption">Listing 11-8: Testing that a condition will cause a | |
3b2f2976 | 623 | `panic!`</span> |
cc61c64b XL |
624 | |
625 | The `#[should_panic]` attribute goes after the `#[test]` attribute and before | |
041b39d2 | 626 | the test function it applies to. Let’s see what it looks like when this test |
cc61c64b XL |
627 | passes: |
628 | ||
629 | ```text | |
630 | running 1 test | |
631 | test tests::greater_than_100 ... ok | |
632 | ||
633 | test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured | |
634 | ``` | |
635 | ||
041b39d2 | 636 | Looks good! Now let’s introduce a bug in our code, by removing the condition |
cc61c64b XL |
637 | that the `new` function will panic if the value is greater than 100: |
638 | ||
639 | ```rust | |
ea8adc8c | 640 | # pub struct Guess { |
cc61c64b XL |
641 | # value: u32, |
642 | # } | |
643 | # | |
644 | impl Guess { | |
645 | pub fn new(value: u32) -> Guess { | |
646 | if value < 1 { | |
647 | panic!("Guess value must be between 1 and 100, got {}.", value); | |
648 | } | |
649 | ||
650 | Guess { | |
7cac9316 | 651 | value |
cc61c64b XL |
652 | } |
653 | } | |
654 | } | |
655 | ``` | |
656 | ||
657 | If we run the test from Listing 11-8, it will fail: | |
658 | ||
659 | ```text | |
660 | running 1 test | |
661 | test tests::greater_than_100 ... FAILED | |
662 | ||
663 | failures: | |
664 | ||
665 | failures: | |
666 | tests::greater_than_100 | |
667 | ||
668 | test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured | |
669 | ``` | |
670 | ||
041b39d2 XL |
671 | We don’t get a very helpful message in this case, but once we look at the test |
672 | function, we can see that it’s annotated with `#[should_panic]`. The failure we | |
cc61c64b XL |
673 | got means that the code in the function, `Guess::new(200)`, did not cause a |
674 | panic. | |
675 | ||
676 | `should_panic` tests can be imprecise, however, because they only tell us that | |
677 | the code has caused some panic. A `should_panic` test would pass even if the | |
678 | test panics for a different reason than the one we were expecting to happen. To | |
679 | make `should_panic` tests more precise, we can add an optional `expected` | |
680 | parameter to the `should_panic` attribute. The test harness will make sure that | |
681 | the failure message contains the provided text. For example, consider the | |
682 | modified code for `Guess` in Listing 11-9 where the `new` function panics with | |
683 | different messages depending on whether the value was too small or too large: | |
684 | ||
685 | <span class="filename">Filename: src/lib.rs</span> | |
686 | ||
687 | ```rust | |
ea8adc8c | 688 | pub struct Guess { |
cc61c64b XL |
689 | value: u32, |
690 | } | |
691 | ||
692 | impl Guess { | |
693 | pub fn new(value: u32) -> Guess { | |
694 | if value < 1 { | |
695 | panic!("Guess value must be greater than or equal to 1, got {}.", | |
696 | value); | |
697 | } else if value > 100 { | |
698 | panic!("Guess value must be less than or equal to 100, got {}.", | |
699 | value); | |
700 | } | |
701 | ||
702 | Guess { | |
7cac9316 | 703 | value |
cc61c64b XL |
704 | } |
705 | } | |
706 | } | |
707 | ||
708 | #[cfg(test)] | |
709 | mod tests { | |
710 | use super::*; | |
711 | ||
712 | #[test] | |
713 | #[should_panic(expected = "Guess value must be less than or equal to 100")] | |
714 | fn greater_than_100() { | |
715 | Guess::new(200); | |
716 | } | |
717 | } | |
718 | ``` | |
719 | ||
720 | <span class="caption">Listing 11-9: Testing that a condition will cause a | |
3b2f2976 | 721 | `panic!` with a particular panic message</span> |
cc61c64b XL |
722 | |
723 | This test will pass, because the value we put in the `expected` parameter of | |
724 | the `should_panic` attribute is a substring of the message that the | |
725 | `Guess::new` function panics with. We could have specified the whole panic | |
726 | message that we expect, which in this case would be `Guess value must be less | |
727 | than or equal to 100, got 200.` It depends on how much of the panic message is | |
728 | unique or dynamic and how precise you want your test to be. In this case, a | |
729 | substring of the panic message is enough to ensure that the code in the | |
730 | function that gets run is the `else if value > 100` case. | |
731 | ||
732 | To see what happens when a `should_panic` test with an `expected` message | |
041b39d2 | 733 | fails, let’s again introduce a bug into our code by swapping the bodies of the |
cc61c64b XL |
734 | `if value < 1` and the `else if value > 100` blocks: |
735 | ||
736 | ```rust,ignore | |
737 | if value < 1 { | |
738 | panic!("Guess value must be less than or equal to 100, got {}.", value); | |
739 | } else if value > 100 { | |
740 | panic!("Guess value must be greater than or equal to 1, got {}.", value); | |
741 | } | |
742 | ``` | |
743 | ||
744 | This time when we run the `should_panic` test, it will fail: | |
745 | ||
746 | ```text | |
747 | running 1 test | |
748 | test tests::greater_than_100 ... FAILED | |
749 | ||
750 | failures: | |
751 | ||
752 | ---- tests::greater_than_100 stdout ---- | |
753 | thread 'tests::greater_than_100' panicked at 'Guess value must be greater | |
754 | than or equal to 1, got 200.', src/lib.rs:10 | |
755 | note: Run with `RUST_BACKTRACE=1` for a backtrace. | |
756 | note: Panic did not include expected string 'Guess value must be less than or | |
757 | equal to 100' | |
758 | ||
759 | failures: | |
760 | tests::greater_than_100 | |
761 | ||
762 | test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured | |
763 | ``` | |
764 | ||
765 | The failure message indicates that this test did indeed panic as we expected, | |
ea8adc8c | 766 | but the panic message did not include expected string `'Guess value must be |
cc61c64b XL |
767 | less than or equal to 100'`. We can see the panic message that we did get, |
768 | which in this case was `Guess value must be greater than or equal to 1, got | |
769 | 200.` We could then start figuring out where our bug was! | |
770 | ||
041b39d2 | 771 | Now that we’ve gone over ways to write tests, let’s look at what is happening |
cc61c64b XL |
772 | when we run our tests and talk about the different options we can use with |
773 | `cargo test`. |