Skip to content

Commit d18229e

Browse files
committed
Chapter 11 edits
1 parent e433aef commit d18229e

File tree

1 file changed

+88
-47
lines changed

1 file changed

+88
-47
lines changed

11-Unit-testing.md

Lines changed: 88 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,30 @@
33
## Motivation
44

55
You're the quality control manager for the Confabulator, a hot new product in development by Acme Inc. LLC.
6-
Every day, engineers give you new prototype confabulators to test and make sure they meet the ill-defined
7-
and entirely made-up requirements upper management has set for the product.
8-
You put each confabulator through its paces to the best of your memory of what those paces are
6+
Every day, engineers give you new prototype confabulators to test and make sure they meet the ill--defined
7+
and entirely made--up requirements upper management has set for the product.
8+
You put each confabulator through its paces --- to the best of your memory of what those paces are ---
99
and make a report for the designers telling them what works and what doesn't.
1010

1111
Usually they give you the same prototype back with some fixes for the problems you reported the previous day.
1212
Testing confabulators takes up time you could otherwise use productively, maybe for complaining about life to your coworkers or taking extended lunch breaks.
13-
So you don't always put the fixed-up prototype through the full battery of tests--you just test the stuff that got fixed.
13+
So you don't always put the fixed-up prototype through the full battery of tests --- you just test the stuff that got fixed.
1414
Unfortunately, sometimes (as engineers are wont to do) a fix gets added that affects something else in an entirely different spot in the system!
1515
You'll never forget the endless meetings after it took you a week to discover that a fix to the frobnicator interfered with the rotagration arm.
1616
Those engineers were *mad*. How could you tell them everything is fine when something was broken?
1717

18-
Even worse, it's hard to remember exactly what all tests you do each time, and management seems to keep changing their minds
19-
on exactly what all features a confabulator is supposed to do.
18+
Even worse, it's hard to remember exactly what tests you're supposed to do each time, and management seems to keep changing their minds
19+
on exactly what features a confabulator is supposed to have.
2020
Last month it was just supposed to be for annularity congruification, but then a contract with Statorfile Exceed GmbH. came through
2121
and now it also has to calabricate the vibrosity of splinal conformities.
2222
The number of things you have to check for just seems to get bigger and bigger, and of course once you add on a vibrous harmonicator,
23-
you have to check that it works regarless of whether the radiometer intensimission is engaged or disengaged.
23+
you have to check that it works regardless of whether the radiometer intensimission is engaged or disengaged.
2424
It seems like every new whizbang adds a half a dozen whatsits and thus a gross more tests[^gross] for you.
2525

2626
But the worst thing of all is that you have to do this all by hand, day in and day out.
2727
Nothing rots the brain faster than the dull monotony of doing something you're ambivalent about.
2828
Heck, if you're going to be experiencing dull monotony either way, the least your boss could do is let you watch some reality TV.
29-
But nooooo, apparently all the monotony must be job-inflicted.
29+
But nooooo, apparently all the monotony must be job--inflicted.
3030

3131
Fortunately, computers have solved this problem!
3232
Instead of manually testing your program, you can write *unit tests* that test each piece for you.
@@ -35,11 +35,11 @@ Rather than mind-numbingly checking everything by hand, you can experience the m
3535
(or at least until it fails and you have to figure out what you broke).
3636

3737
Unit testing is widely used in industry because it is quite effective at keeping bugs out of code.[^pedantry]
38-
You can even measure how much of your code is tested by unit tests--100% code coverage means that you've found
38+
You can even measure how much of your code is tested by unit tests --- 100% code coverage means that you've found
3939
at least most of the obvious bugs!
4040

4141
This chapter will focus on the Catch unit testing framework for C++.
42-
There are a number of popular unit testing frameworks; Boost has one (of course it does), Google makes one called `gtest`, etc.
42+
There are a number of popular unit testing frameworks; Boost has one,[^sink] Google makes one called `gtest`, etc.
4343
However, Catch is easy to install, easy to write tests in, and downright beautiful compared to Boost's test framework.
4444
(It's also popular, in case you were wondering.)
4545

@@ -54,7 +54,7 @@ However, Catch is easy to install, easy to write tests in, and downright beautif
5454
### Setting up Catch
5555

5656
Catch is distributed as a single `.hpp` file that you can download and include in your project.
57-
Download it from [github](https://github.com/philsquared/Catch) -- the link to the single header is in the `README`.
57+
Download it from [GitHub](https://github.com/philsquared/Catch) --- the link to the single header is in the `README`.
5858

5959
In *exactly one* `.cpp` file, you must include the following lines:
6060

@@ -64,7 +64,7 @@ In *exactly one* `.cpp` file, you must include the following lines:
6464
```
6565

6666
This generates a `main()` function that runs your unit tests.
67-
You will have two programs now -- your actual program, and a program that runs your unit tests.
67+
You will have two programs now --- your actual program, and a program that runs your unit tests.
6868

6969
Every other file you write tests in should include Catch:
7070

@@ -73,14 +73,14 @@ Every other file you write tests in should include Catch:
7373
```
7474

7575
Later, we'll discuss how best to organize your tests, so don't worry too much about the "right" place to put the main function yet.
76+
For now, just throw it at the top of a `test.cpp` file, and let's write some tests!
7677

7778
### Basic Tests
7879

79-
Alright, let's write some unit tests!
8080
We are going to test a function that generates Fibonacci numbers (1, 1, 2, 3, 5, 8, ...).
8181
Here's our function:
8282

83-
```c++
83+
```{.cpp .numberLines}
8484
/* Generate the Nth Fibonacci number */
8585
int fibonacci(int n)
8686
{
@@ -102,13 +102,14 @@ Each test case has a name and a tag; generally you'll tag all test cases for a f
102102
(You can tell Catch to run only tests with specific names or tags if you like.)
103103
104104
Inside a test case, you can put one or more `REQUIRE` or `CHECK` assertions.
105-
A `REQUIRE` statement checks that a certain condition holds and if it does not, it reports a test failure and stops the execution of that test case.
106-
`CHECK` is similar to require, but if the condition does not hold, it reports a test failure but keeps running the test case.
107-
Usually you use `REQUIRE` when something is broken enough that it does not make sense to keep going with the test.
105+
A `REQUIRE` statement checks that a certain condition holds; if it does not, it reports a test failure and stops the execution of that test case.
106+
`CHECK` is similar to require, but if the condition does not hold, it reports a test failure and keeps running the test case.
107+
Usually, you use `REQUIRE` when a failure indicates that the code is broken enough that it does not make sense to keep going with the test.
108108
109109
In general, when writing tests, you want to test every path through your code at least once.
110110
Here's a pretty good test for our Fibonacci function:
111-
```c++
111+
112+
```{.cpp .numberLines}
112113
#define CATCH_CONFIG_MAIN
113114
#include "catch.hpp"
114115
@@ -145,26 +146,27 @@ assertions: 4 | 1 passed | 3 failed
145146
```
146147

147148
Oh no! We have a bug![^hand]
148-
In fact, it is in the `return n;` statement in our function--it should be `return 1;` instead.
149-
If we fix that and re-run our tests, everything is kosher:
149+
In fact, it is in the `return n;` statement in our function on line 6 --- it should be `return 1;` instead.
150+
If we fix that and re--run our tests, everything is kosher:
151+
150152
```
151153
All tests passed (4 assertions in 1 test case)
152154
```
153155

154-
Now, you may notice that Catch expands the thing inside the CHECK function -- it prints the value that `fibonacci` returns.
155-
It does this by using *template magic*.
156-
This magic is only so powerful.
156+
Now, you may notice that Catch expands the thing inside the `CHECK` function --- it prints the value that `fibonacci` returns.
157+
It does this by the power of *template magic*.
158+
This magic is only so powerful.[^errors]
157159
So, if you want to write a more complex expression, you'll need to either break it into individual assertions or tell Catch to not attempt to expand it.
158160
For 'and' statements, rather than `CHECK(x && y);`, write `CHECK(x); CHECK(y);`.
159161
For 'or' statements, enclose your expression in an extra pair of parentheses: `CHECK((x || y));`.
160-
(The extra parens tell Catch to not attempt to expand the expression; you can do this with 'and' statements as well, but expansion is nice to have.)
162+
(The extra parentheses tell Catch to not attempt to expand the expression; you can do this with 'and' statements as well, but expansion is nice to have.)
161163

162164
There are also matching assertions `REQUIRE_FALSE` and `CHECK_FALSE` that check to make sure a statement is false, rather than true.
163165

164166
### Testing Exceptions
165167

166-
Let's modify our Fibonacci function to throw an exception if the user passes us a number that's not within the range our function works for.
167-
```c++
168+
Let's modify our Fibonacci function to throw an exception if the user passes us a number that's not within the range of our function.
169+
```{.cpp .numberLines}
168170
#include<stdexcept> // for domain_error
169171
using namespace std;
170172

@@ -177,7 +179,7 @@ int fibonacci(int n)
177179
}
178180
else if(n <= 1)
179181
{
180-
return n;
182+
return 1;
181183
}
182184
else
183185
{
@@ -197,20 +199,21 @@ As before, each assertion comes in a `CHECK` and a `REQUIRE` flavor.
197199
For example, we can check that our Fibonacci function properly verifies that its input is in the domain by testing when it throws exceptions
198200
and what exceptions it throws:
199201
200-
```c++
202+
```{.cpp .numberLines}
201203
TEST_CASE("Fibonacci Domain", "[Fibonacci]")
202204
{
203205
CHECK_NOTHROW(fibonacci(0));
204206
CHECK_NOTHROW(fibonacci(10));
205207
CHECK_THROWS_AS(fibonacci(-1), domain_error);
206-
CHECK_THROWS_WITH(fibonacci(-1), "Fibonacci not defined for negative indices");
208+
CHECK_THROWS_WITH(fibonacci(-1), "Fibonacci not defined for"
209+
"negative indices");
207210
}
208211
```
209212

210213
### Organizing Your Tests
211214

212215
At this point you know enough to start writing tests for functions.
213-
Before you go too hog-wild, shoving test cases every which where, let's talk about how to organize tests so they're easy to find and use.
216+
Before you go too hog--wild, shoving test cases every which where, let's talk about how to organize tests so they're easy to find and use.
214217

215218
First, we can't have our `main()` function and Catch's auto-generated `main()` in the same program.
216219
You'll need to organize your code so that you can compile your test cases without including your `main()` function.
@@ -225,17 +228,17 @@ that contains our unit tests.
225228

226229
Third, compiling Catch's auto-generated `main()` function takes a while.
227230
This is doubly annoying because it never changes!
228-
Rather than rebuilding it all the time, we can harness the power of incremental compilation by making a separate `test_main.cpp` file
231+
Rather than rebuilding it all the time, we can harness the power of makefiles and incremental compilation by making a separate `test_main.cpp` file
229232
that just contains Catch's `main()`.
230233
This file looks exactly like this:
231234

232-
```c++
235+
```{.cpp .numberLines}
233236
#define CATCH_CONFIG_MAIN
234237
#include "catch.hpp"
235238
```
236239

237240
Then in `test_fibonacci.cpp`, we just have the following includes:
238-
```c++
241+
```{.cpp .numberLines}
239242
#include "fibonacci.h"
240243
#include "catch.hpp"
241244

@@ -245,9 +248,9 @@ Then in `test_fibonacci.cpp`, we just have the following includes:
245248
Building this code is done as follows:
246249

247250
```
248-
$ g++ -c test_main.cpp
249-
$ g++ -c test_fibonacci.cpp
250-
$ g++ test_main.o test_fibonacci.o -o testsuite
251+
$ g++ -c test_main.cpp # Compile Catch's main()
252+
$ g++ -c test_fibonacci.cpp # Compile Fibonacci tests
253+
$ g++ test_main.o test_fibonacci.o -o testsuite # Link testsuite
251254
```
252255

253256
Now you can add new unit tests and just recompile `test_fibonacci.cpp` and re-link the test suite.
@@ -262,7 +265,7 @@ You'll still write `TEST_CASE`s with various `CHECK` and `REQUIRE` statements.
262265
However, when testing classes, it's common to need to set up a class instance to run a bunch of tests on.
263266
For example, let's suppose we have a Vector class with the following declaration:
264267

265-
```c++
268+
```{.cpp .numberLines}
266269
template<class T>
267270
class Vector
268271
{
@@ -302,7 +305,7 @@ For each section, Catch runs the test case from the beginning but only executes
302305
303306
We can use this to set up a test vector once to test the constructor and accessor functions:
304307
305-
```c++
308+
```{.cpp .numberLines}
306309
TEST_CASE("Vector Elements", "[vector]")
307310
{
308311
Vector<int> v; // Re-initialized for each section
@@ -341,6 +344,7 @@ TEST_CASE("Vector Elements", "[vector]")
341344
}
342345
```
343346

347+
In this example, Catch runs lines 1--16, then starts over and runs lines 1--8 and 18--26, then lines 1--8, 18, and 28--35.
344348
Since we get a fresh `v` vector for each section, the code inside each section can mutate `v` however it likes without impacting any of the other
345349
sections' tests!
346350
Even better, we can add more setup as we go through the test case; our `copy` vector is only created for the sections that test the copy constructor.
@@ -363,10 +367,11 @@ REQUIRE_THAT(my_string, StartsWith("Dear Prudence"));
363367
```
364368

365369
In addition to the `StartsWith` matcher, there is an `EndsWidth` matcher and a `Contains` matcher.
366-
These matchers can be combined using logical operators, for example:
370+
These matchers can be combined using logical operators; for example:
367371

368372
```
369-
REQUIRE_THAT(my_string, StartsWith("Dear Prudence") && !Contains("Sincerely"));
373+
REQUIRE_THAT(my_string, StartsWith("Dear Prudence") &&
374+
!Contains("Sincerely"));
370375
```
371376

372377
These matchers can also be used in the `THROWS_WITH` assertions!
@@ -379,7 +384,7 @@ For a more precise comparison, you can set the `epsilon` to a smaller percentage
379384
### Code Coverage
380385

381386
Unit tests are most valuable when all your important code is tested.
382-
You can check this by hand, but that's no fun especially on a big codebase.
387+
You can check this by hand, but that's no fun, especially on a big codebase.
383388
Fortunately, there are tools to check for you!
384389
We'll use `gcov` to generate code coverage reports for us.
385390

@@ -401,7 +406,7 @@ Once you have compiled your tests, execute them as normal.
401406
In addition to running your tests, your executable will also produce a number of files ending in `.gcda` and `.gcno`.
402407
These are data files for `gcov`. They're binary, so opening them in a text editor will not be particularly enlightening.
403408
To get meaningful coverage statistics, you run `gcov` and give it a list of `.cpp` files whose behavior you want to see.
404-
(Generally this will be all your `.cpp` files.)
409+
(Generally this will be all your test `.cpp` files.)
405410
406411
There are a couple of flags that you definitely want to use for `gcov`:
407412
@@ -454,17 +459,50 @@ The numbers in the left margin are the number of times each line is executed.
454459
If a line isn't executed, you will see `####` in the left column instead.
455460
This makes it easy to spot code that isn't covered by your tests!
456461
462+
### Writing Quality Unit Tests
463+
464+
Alright, now that you know how to write unit tests, let's talk about the philosophy of writing good unit tests.
465+
Unit tests are most useful if you write them as you go, rather than writing a big chunk of code and then writing tests.
466+
Some people prefer to write their tests first, then write the code needed to make the tests pass.
467+
Others write the code first and then the tests.
468+
Either way, testing as you go will help you think through how your program ought to work and help you spot bugs
469+
that come from changing or refactoring your code.
470+
471+
Tests should be small and designed to test the smallest amount of functionality possible --- typically a single function,
472+
or a single feature of a function.
473+
Typically, more code means more bugs; we do not want our unit tests to be complex enough to introduce their own bugs!
474+
Tests should be obviously correct as much as is possible.
475+
Start by testing basic functions, such as accessors and mutators.
476+
Once those have been tested, you can use them in more complex functionality tests;
477+
if one of those tests fails, you know that the bug does lies somewhere other than your basic functions.
478+
479+
If you come across a bug in your program, write a unit test that reproduces it,
480+
then fix your code so that the test passes.
481+
This way, you won't accidentally reintroduce the bug later on!
482+
483+
When writing tests, think about the different ways your code can be executed.
484+
Consider your `if` statements and loops --- how might each be executed or not?
485+
What side effects does your code have? Does it modify member variables? Write to a file?
486+
Thorough tests cover as many of these possible execution paths as is feasible.
487+
Think about your preconditions and postconditions and write tests that verify your interface conforms to its documentation!
488+
Test your edge cases, not just the "happy path" that normal execution would take.
489+
490+
Finally, a word on code coverage.
491+
Like any metric, code coverage is not a perfect measure of your tests' quality.
492+
Practically speaking, 100% code coverage is difficult to achieve; it is better to have 90% coverage and well thought out tests
493+
than 100% coverage with tests that don't reflect how your code will actually be used.
494+
457495
\newpage
458496
## Questions
459497
460498
Name: `______________________________`
461499
462500
1. In your own words, what is the goal of unit testing? How do you know you have written good tests?
463-
\vspace{8em}
501+
\vspace{10em}
464502
2. What is the difference between the `CHECK` and `REQUIRE` test assertions?
465-
\vspace{8em}
503+
\vspace{10em}
466504
3. Write the test assertion you would use if you wanted to assert that a call to `frobnicate()` throws an exception of type `bad_joke`
467-
and to bail out of the test case if it does not?
505+
and to bail out of the test case if it does not.
468506
\newpage
469507
470508
## Quick Reference
@@ -492,7 +530,8 @@ String Matchers:
492530
493531
Floating Point:
494532
495-
- `Approx`: Perform approximate floaing point comparison (by default up to 0.001% error)
533+
- `Approx`: Perform approximate floating point comparison (by default up to 0.001% error)
534+
- `epsilon`: Set the precision of the comparison
496535
497536
### Coverage
498537
@@ -517,5 +556,7 @@ Floating Point:
517556
In logical terms, unit tests are a bunch of "there exists" statements; whereas a proof of correctness is a "for all" statement.
518557
Unfortunately, proving programs correct is a difficult task and the tools to do so are not exactly ready for widespread use yet.
519558
In the meantime, while we wait for math and logic to catch up to the needs of engineering, we'll have to settle for thorough unit testing.
520-
[^matcher]: You can also use a string matcher; we'll talk about these later in the chapter.
559+
[^matcher]: You can also use a string matcher in place of the string if you want to match multiple strings; we'll talk about these later in the chapter.
521560
[^hand]: Yes, you with your hand up in the back? You saw the bug before the test failed? Yes, yes, you're very clever.
561+
[^sink]: Of course it would --- Boost even has a kitchen sink library.
562+
[^errors]: It could be made more powerful, but the downside is that the compiler errors from misuse would become even more terrifying.

0 commit comments

Comments
 (0)