]> git.proxmox.com Git - rustc.git/blame - src/doc/book/src/ch12-04-testing-the-librarys-functionality.md
New upstream version 1.55.0+dfsg1
[rustc.git] / src / doc / book / src / ch12-04-testing-the-librarys-functionality.md
CommitLineData
13cf67c4
XL
1## Developing the Library’s Functionality with Test-Driven Development
2
3Now that we’ve extracted the logic into *src/lib.rs* and left the argument
4collecting and error handling in *src/main.rs*, it’s much easier to write tests
5for the core functionality of our code. We can call functions directly with
6various arguments and check return values without having to call our binary
7from the command line. Feel free to write some tests for the functionality in
8the `Config::new` and `run` functions on your own.
9
10In this section, we’ll add the searching logic to the `minigrep` program by
11using the Test-driven development (TDD) process. This software development
12technique follows these steps:
13
141. Write a test that fails and run it to make sure it fails for the reason you
15 expect.
162. Write or modify just enough code to make the new test pass.
173. Refactor the code you just added or changed and make sure the tests
18 continue to pass.
194. Repeat from step 1!
20
21This process is just one of many ways to write software, but TDD can help drive
22code design as well. Writing the test before you write the code that makes the
23test pass helps to maintain high test coverage throughout the process.
24
25We’ll test drive the implementation of the functionality that will actually do
26the searching for the query string in the file contents and produce a list of
27lines that match the query. We’ll add this functionality in a function called
28`search`.
29
30### Writing a Failing Test
31
32Because we don’t need them anymore, let’s remove the `println!` statements from
33*src/lib.rs* and *src/main.rs* that we used to check the program’s behavior.
34Then, in *src/lib.rs*, we’ll add a `tests` module with a test function, as we
48663c56
XL
35did in [Chapter 11][ch11-anatomy]<!-- ignore -->. The test function specifies
36the behavior we want the `search` function to have: it will take a query and
37the text to search for the query in, and it will return only the lines from the
38text that contain the query. Listing 12-15 shows this test, which won’t compile
39yet.
13cf67c4
XL
40
41<span class="filename">Filename: src/lib.rs</span>
42
74b04a01
XL
43```rust,ignore,does_not_compile
44{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-15/src/lib.rs:here}}
13cf67c4
XL
45```
46
47<span class="caption">Listing 12-15: Creating a failing test for the `search`
48function we wish we had</span>
49
50This test searches for the string `"duct"`. The text we’re searching is three
136023e0
XL
51lines, only one of which contains `"duct"` (Note that the backslash after the
52opening double quote tells Rust not to put a newline character at the beginning
53of the contents of this string literal). We assert that the value returned from
54the `search` function contains only the line we expect.
13cf67c4
XL
55
56We aren’t able to run this test and watch it fail because the test doesn’t even
57compile: the `search` function doesn’t exist yet! So now we’ll add just enough
58code to get the test to compile and run by adding a definition of the `search`
59function that always returns an empty vector, as shown in Listing 12-16. Then
60the test should compile and fail because an empty vector doesn’t match a vector
61containing the line `"safe, fast, productive."`
62
63<span class="filename">Filename: src/lib.rs</span>
64
fc512014 65```rust,noplayground
74b04a01 66{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-16/src/lib.rs:here}}
13cf67c4
XL
67```
68
69<span class="caption">Listing 12-16: Defining just enough of the `search`
70function so our test will compile</span>
71
72Notice that we need an explicit lifetime `'a` defined in the signature of
73`search` and used with the `contents` argument and the return value. Recall in
48663c56
XL
74[Chapter 10][ch10-lifetimes]<!-- ignore --> that the lifetime parameters
75specify which argument lifetime is connected to the lifetime of the return
76value. In this case, we indicate that the returned vector should contain string
77slices that reference slices of the argument `contents` (rather than the
78argument `query`).
13cf67c4
XL
79
80In other words, we tell Rust that the data returned by the `search` function
81will live as long as the data passed into the `search` function in the
82`contents` argument. This is important! The data referenced *by* a slice needs
83to be valid for the reference to be valid; if the compiler assumes we’re making
84string slices of `query` rather than `contents`, it will do its safety checking
85incorrectly.
86
87If we forget the lifetime annotations and try to compile this function, we’ll
88get this error:
89
f035d41b 90```console
74b04a01 91{{#include ../listings/ch12-an-io-project/output-only-02-missing-lifetimes/output.txt}}
13cf67c4
XL
92```
93
94Rust can’t possibly know which of the two arguments we need, so we need to tell
95it. Because `contents` is the argument that contains all of our text and we
96want to return the parts of that text that match, we know `contents` is the
97argument that should be connected to the return value using the lifetime syntax.
98
99Other programming languages don’t require you to connect arguments to return
9fa01778
XL
100values in the signature. Although this might seem strange, it will get easier
101over time. You might want to compare this example with the [“Validating
102References with Lifetimes”][validating-references-with-lifetimes]<!-- ignore
103--> section in Chapter 10.
13cf67c4
XL
104
105Now let’s run the test:
106
f035d41b 107```console
74b04a01 108{{#include ../listings/ch12-an-io-project/listing-12-16/output.txt}}
13cf67c4
XL
109```
110
111Great, the test fails, exactly as we expected. Let’s get the test to pass!
112
113### Writing Code to Pass the Test
114
115Currently, our test is failing because we always return an empty vector. To fix
116that and implement `search`, our program needs to follow these steps:
117
118* Iterate through each line of the contents.
119* Check whether the line contains our query string.
120* If it does, add it to the list of values we’re returning.
121* If it doesn’t, do nothing.
122* Return the list of results that match.
123
124Let’s work through each step, starting with iterating through lines.
125
126#### Iterating Through Lines with the `lines` Method
127
128Rust has a helpful method to handle line-by-line iteration of strings,
129conveniently named `lines`, that works as shown in Listing 12-17. Note this
9fa01778 130won’t compile yet.
13cf67c4
XL
131
132<span class="filename">Filename: src/lib.rs</span>
133
6a06907d 134```rust,ignore,does_not_compile
74b04a01 135{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-17/src/lib.rs:here}}
13cf67c4
XL
136```
137
138<span class="caption">Listing 12-17: Iterating through each line in `contents`
139</span>
140
141The `lines` method returns an iterator. We’ll talk about iterators in depth in
e74abb32 142[Chapter 13][ch13-iterators]<!-- ignore -->, but recall that you saw this way of using an
48663c56
XL
143iterator in [Listing 3-5][ch3-iter]<!-- ignore -->, where we used a `for` loop
144with an iterator to run some code on each item in a collection.
13cf67c4
XL
145
146#### Searching Each Line for the Query
147
148Next, we’ll check whether the current line contains our query string.
149Fortunately, strings have a helpful method named `contains` that does this for
150us! Add a call to the `contains` method in the `search` function, as shown in
9fa01778 151Listing 12-18. Note this still won’t compile yet.
13cf67c4
XL
152
153<span class="filename">Filename: src/lib.rs</span>
154
6a06907d 155```rust,ignore,does_not_compile
74b04a01 156{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-18/src/lib.rs:here}}
13cf67c4
XL
157```
158
159<span class="caption">Listing 12-18: Adding functionality to see whether the
160line contains the string in `query`</span>
161
162#### Storing Matching Lines
163
164We also need a way to store the lines that contain our query string. For that,
165we can make a mutable vector before the `for` loop and call the `push` method
166to store a `line` in the vector. After the `for` loop, we return the vector, as
9fa01778 167shown in Listing 12-19.
13cf67c4
XL
168
169<span class="filename">Filename: src/lib.rs</span>
170
171```rust,ignore
74b04a01 172{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-19/src/lib.rs:here}}
13cf67c4
XL
173```
174
175<span class="caption">Listing 12-19: Storing the lines that match so we can
176return them</span>
177
178Now the `search` function should return only the lines that contain `query`,
179and our test should pass. Let’s run the test:
180
f035d41b 181```console
74b04a01 182{{#include ../listings/ch12-an-io-project/listing-12-19/output.txt}}
13cf67c4
XL
183```
184
185Our test passed, so we know it works!
186
187At this point, we could consider opportunities for refactoring the
188implementation of the search function while keeping the tests passing to
189maintain the same functionality. The code in the search function isn’t too bad,
190but it doesn’t take advantage of some useful features of iterators. We’ll
e74abb32 191return to this example in [Chapter 13][ch13-iterators]<!-- ignore -->, where we’ll
48663c56 192explore iterators in detail, and look at how to improve it.
13cf67c4
XL
193
194#### Using the `search` Function in the `run` Function
195
196Now that the `search` function is working and tested, we need to call `search`
197from our `run` function. We need to pass the `config.query` value and the
198`contents` that `run` reads from the file to the `search` function. Then `run`
199will print each line returned from `search`:
200
201<span class="filename">Filename: src/lib.rs</span>
202
203```rust,ignore
74b04a01 204{{#rustdoc_include ../listings/ch12-an-io-project/no-listing-02-using-search-in-run/src/lib.rs:here}}
13cf67c4
XL
205```
206
207We’re still using a `for` loop to return each line from `search` and print it.
208
209Now the entire program should work! Let’s try it out, first with a word that
210should return exactly one line from the Emily Dickinson poem, “frog”:
211
f035d41b 212```console
74b04a01 213{{#include ../listings/ch12-an-io-project/no-listing-02-using-search-in-run/output.txt}}
13cf67c4
XL
214```
215
216Cool! Now let’s try a word that will match multiple lines, like “body”:
217
f035d41b 218```console
74b04a01 219{{#include ../listings/ch12-an-io-project/output-only-03-multiple-matches/output.txt}}
13cf67c4
XL
220```
221
222And finally, let’s make sure that we don’t get any lines when we search for a
223word that isn’t anywhere in the poem, such as “monomorphization”:
224
f035d41b 225```console
74b04a01 226{{#include ../listings/ch12-an-io-project/output-only-04-no-matches/output.txt}}
13cf67c4
XL
227```
228
229Excellent! We’ve built our own mini version of a classic tool and learned a lot
230about how to structure applications. We’ve also learned a bit about file input
231and output, lifetimes, testing, and command line parsing.
232
233To round out this project, we’ll briefly demonstrate how to work with
234environment variables and how to print to standard error, both of which are
235useful when you’re writing command line programs.
9fa01778
XL
236
237[validating-references-with-lifetimes]:
238ch10-03-lifetime-syntax.html#validating-references-with-lifetimes
48663c56
XL
239[ch11-anatomy]: ch11-01-writing-tests.html#the-anatomy-of-a-test-function
240[ch10-lifetimes]: ch10-03-lifetime-syntax.html
241[ch3-iter]: ch03-05-control-flow.html#looping-through-a-collection-with-for
e74abb32 242[ch13-iterators]: ch13-02-iterators.html