summaryrefslogtreecommitdiffstats
path: root/src/doc/book/src/ch12-04-testing-the-librarys-functionality.md
diff options
context:
space:
mode:
Diffstat (limited to 'src/doc/book/src/ch12-04-testing-the-librarys-functionality.md')
-rw-r--r--src/doc/book/src/ch12-04-testing-the-librarys-functionality.md245
1 files changed, 245 insertions, 0 deletions
diff --git a/src/doc/book/src/ch12-04-testing-the-librarys-functionality.md b/src/doc/book/src/ch12-04-testing-the-librarys-functionality.md
new file mode 100644
index 000000000..129f835aa
--- /dev/null
+++ b/src/doc/book/src/ch12-04-testing-the-librarys-functionality.md
@@ -0,0 +1,245 @@
+## Developing the Library’s Functionality with Test-Driven Development
+
+Now that we’ve extracted the logic into *src/lib.rs* and left the argument
+collecting and error handling in *src/main.rs*, it’s much easier to write tests
+for the core functionality of our code. We can call functions directly with
+various arguments and check return values without having to call our binary
+from the command line.
+
+In this section, we’ll add the searching logic to the `minigrep` program
+using the test-driven development (TDD) process with the following steps:
+
+1. Write a test that fails and run it to make sure it fails for the reason you
+ expect.
+2. Write or modify just enough code to make the new test pass.
+3. Refactor the code you just added or changed and make sure the tests
+ continue to pass.
+4. Repeat from step 1!
+
+Though it’s just one of many ways to write software, TDD can help drive code
+design. Writing the test before you write the code that makes the test pass
+helps to maintain high test coverage throughout the process.
+
+We’ll test drive the implementation of the functionality that will actually do
+the searching for the query string in the file contents and produce a list of
+lines that match the query. We’ll add this functionality in a function called
+`search`.
+
+### Writing a Failing Test
+
+Because we don’t need them anymore, let’s remove the `println!` statements from
+*src/lib.rs* and *src/main.rs* that we used to check the program’s behavior.
+Then, in *src/lib.rs*, add a `tests` module with a test function, as we did in
+[Chapter 11][ch11-anatomy]<!-- ignore -->. The test function specifies the
+behavior we want the `search` function to have: it will take a query and the
+text to search, and it will return only the lines from the text that contain
+the query. Listing 12-15 shows this test, which won’t compile yet.
+
+<span class="filename">Filename: src/lib.rs</span>
+
+```rust,ignore,does_not_compile
+{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-15/src/lib.rs:here}}
+```
+
+<span class="caption">Listing 12-15: Creating a failing test for the `search`
+function we wish we had</span>
+
+This test searches for the string `"duct"`. The text we’re searching is three
+lines, only one of which contains `"duct"` (Note that the backslash after the
+opening double quote tells Rust not to put a newline character at the beginning
+of the contents of this string literal). We assert that the value returned from
+the `search` function contains only the line we expect.
+
+We aren’t yet able to run this test and watch it fail because the test doesn’t
+even compile: the `search` function doesn’t exist yet! In accordance with TDD
+principles, we’ll add just enough code to get the test to compile and run by
+adding a definition of the `search` function that always returns an empty
+vector, as shown in Listing 12-16. Then the test should compile and fail
+because an empty vector doesn’t match a vector containing the line `"safe,
+fast, productive."`
+
+<span class="filename">Filename: src/lib.rs</span>
+
+```rust,noplayground
+{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-16/src/lib.rs:here}}
+```
+
+<span class="caption">Listing 12-16: Defining just enough of the `search`
+function so our test will compile</span>
+
+Notice that we need to define an explicit lifetime `'a` in the signature of
+`search` and use that lifetime with the `contents` argument and the return
+value. Recall in [Chapter 10][ch10-lifetimes]<!-- ignore --> that the lifetime
+parameters specify which argument lifetime is connected to the lifetime of the
+return value. In this case, we indicate that the returned vector should contain
+string slices that reference slices of the argument `contents` (rather than the
+argument `query`).
+
+In other words, we tell Rust that the data returned by the `search` function
+will live as long as the data passed into the `search` function in the
+`contents` argument. This is important! The data referenced *by* a slice needs
+to be valid for the reference to be valid; if the compiler assumes we’re making
+string slices of `query` rather than `contents`, it will do its safety checking
+incorrectly.
+
+If we forget the lifetime annotations and try to compile this function, we’ll
+get this error:
+
+```console
+{{#include ../listings/ch12-an-io-project/output-only-02-missing-lifetimes/output.txt}}
+```
+
+Rust can’t possibly know which of the two arguments we need, so we need to tell
+it explicitly. Because `contents` is the argument that contains all of our text
+and we want to return the parts of that text that match, we know `contents` is
+the argument that should be connected to the return value using the lifetime
+syntax.
+
+Other programming languages don’t require you to connect arguments to return
+values in the signature, but this practice will get easier over time. You might
+want to compare this example with the [“Validating References with
+Lifetimes”][validating-references-with-lifetimes]<!-- ignore --> section in
+Chapter 10.
+
+Now let’s run the test:
+
+```console
+{{#include ../listings/ch12-an-io-project/listing-12-16/output.txt}}
+```
+
+Great, the test fails, exactly as we expected. Let’s get the test to pass!
+
+### Writing Code to Pass the Test
+
+Currently, our test is failing because we always return an empty vector. To fix
+that and implement `search`, our program needs to follow these steps:
+
+* Iterate through each line of the contents.
+* Check whether the line contains our query string.
+* If it does, add it to the list of values we’re returning.
+* If it doesn’t, do nothing.
+* Return the list of results that match.
+
+Let’s work through each step, starting with iterating through lines.
+
+#### Iterating Through Lines with the `lines` Method
+
+Rust has a helpful method to handle line-by-line iteration of strings,
+conveniently named `lines`, that works as shown in Listing 12-17. Note this
+won’t compile yet.
+
+<span class="filename">Filename: src/lib.rs</span>
+
+```rust,ignore,does_not_compile
+{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-17/src/lib.rs:here}}
+```
+
+<span class="caption">Listing 12-17: Iterating through each line in `contents`
+</span>
+
+The `lines` method returns an iterator. We’ll talk about iterators in depth in
+[Chapter 13][ch13-iterators]<!-- ignore -->, but recall that you saw this way
+of using an iterator in [Listing 3-5][ch3-iter]<!-- ignore -->, where we used a
+`for` loop with an iterator to run some code on each item in a collection.
+
+#### Searching Each Line for the Query
+
+Next, we’ll check whether the current line contains our query string.
+Fortunately, strings have a helpful method named `contains` that does this for
+us! Add a call to the `contains` method in the `search` function, as shown in
+Listing 12-18. Note this still won’t compile yet.
+
+<span class="filename">Filename: src/lib.rs</span>
+
+```rust,ignore,does_not_compile
+{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-18/src/lib.rs:here}}
+```
+
+<span class="caption">Listing 12-18: Adding functionality to see whether the
+line contains the string in `query`</span>
+
+At the moment, we’re building up functionality. To get it to compile, we need
+to return a value from the body as we indicated we would in the function
+signature.
+
+#### Storing Matching Lines
+
+To finish this function, we need a way to store the matching lines that we want
+to return. For that, we can make a mutable vector before the `for` loop and
+call the `push` method to store a `line` in the vector. After the `for` loop,
+we return the vector, as shown in Listing 12-19.
+
+<span class="filename">Filename: src/lib.rs</span>
+
+```rust,ignore
+{{#rustdoc_include ../listings/ch12-an-io-project/listing-12-19/src/lib.rs:here}}
+```
+
+<span class="caption">Listing 12-19: Storing the lines that match so we can
+return them</span>
+
+Now the `search` function should return only the lines that contain `query`,
+and our test should pass. Let’s run the test:
+
+```console
+{{#include ../listings/ch12-an-io-project/listing-12-19/output.txt}}
+```
+
+Our test passed, so we know it works!
+
+At this point, we could consider opportunities for refactoring the
+implementation of the search function while keeping the tests passing to
+maintain the same functionality. The code in the search function isn’t too bad,
+but it doesn’t take advantage of some useful features of iterators. We’ll
+return to this example in [Chapter 13][ch13-iterators]<!-- ignore -->, where
+we’ll explore iterators in detail, and look at how to improve it.
+
+#### Using the `search` Function in the `run` Function
+
+Now that the `search` function is working and tested, we need to call `search`
+from our `run` function. We need to pass the `config.query` value and the
+`contents` that `run` reads from the file to the `search` function. Then `run`
+will print each line returned from `search`:
+
+<span class="filename">Filename: src/lib.rs</span>
+
+```rust,ignore
+{{#rustdoc_include ../listings/ch12-an-io-project/no-listing-02-using-search-in-run/src/lib.rs:here}}
+```
+
+We’re still using a `for` loop to return each line from `search` and print it.
+
+Now the entire program should work! Let’s try it out, first with a word that
+should return exactly one line from the Emily Dickinson poem, “frog”:
+
+```console
+{{#include ../listings/ch12-an-io-project/no-listing-02-using-search-in-run/output.txt}}
+```
+
+Cool! Now let’s try a word that will match multiple lines, like “body”:
+
+```console
+{{#include ../listings/ch12-an-io-project/output-only-03-multiple-matches/output.txt}}
+```
+
+And finally, let’s make sure that we don’t get any lines when we search for a
+word that isn’t anywhere in the poem, such as “monomorphization”:
+
+```console
+{{#include ../listings/ch12-an-io-project/output-only-04-no-matches/output.txt}}
+```
+
+Excellent! We’ve built our own mini version of a classic tool and learned a lot
+about how to structure applications. We’ve also learned a bit about file input
+and output, lifetimes, testing, and command line parsing.
+
+To round out this project, we’ll briefly demonstrate how to work with
+environment variables and how to print to standard error, both of which are
+useful when you’re writing command line programs.
+
+[validating-references-with-lifetimes]:
+ch10-03-lifetime-syntax.html#validating-references-with-lifetimes
+[ch11-anatomy]: ch11-01-writing-tests.html#the-anatomy-of-a-test-function
+[ch10-lifetimes]: ch10-03-lifetime-syntax.html
+[ch3-iter]: ch03-05-control-flow.html#looping-through-a-collection-with-for
+[ch13-iterators]: ch13-02-iterators.html