You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 11-Unit-testing.md
+88-47Lines changed: 88 additions & 47 deletions
Original file line number
Diff line number
Diff line change
@@ -3,30 +3,30 @@
3
3
## Motivation
4
4
5
5
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 ---
9
9
and make a report for the designers telling them what works and what doesn't.
10
10
11
11
Usually they give you the same prototype back with some fixes for the problems you reported the previous day.
12
12
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.
14
14
Unfortunately, sometimes (as engineers are wont to do) a fix gets added that affects something else in an entirely different spot in the system!
15
15
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.
16
16
Those engineers were *mad*. How could you tell them everything is fine when something was broken?
17
17
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.
20
20
Last month it was just supposed to be for annularity congruification, but then a contract with Statorfile Exceed GmbH. came through
21
21
and now it also has to calabricate the vibrosity of splinal conformities.
22
22
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.
24
24
It seems like every new whizbang adds a half a dozen whatsits and thus a gross more tests[^gross] for you.
25
25
26
26
But the worst thing of all is that you have to do this all by hand, day in and day out.
27
27
Nothing rots the brain faster than the dull monotony of doing something you're ambivalent about.
28
28
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.
30
30
31
31
Fortunately, computers have solved this problem!
32
32
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
35
35
(or at least until it fails and you have to figure out what you broke).
36
36
37
37
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
39
39
at least most of the obvious bugs!
40
40
41
41
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.
43
43
However, Catch is easy to install, easy to write tests in, and downright beautiful compared to Boost's test framework.
44
44
(It's also popular, in case you were wondering.)
45
45
@@ -54,7 +54,7 @@ However, Catch is easy to install, easy to write tests in, and downright beautif
54
54
### Setting up Catch
55
55
56
56
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`.
58
58
59
59
In *exactly one*`.cpp` file, you must include the following lines:
60
60
@@ -64,7 +64,7 @@ In *exactly one* `.cpp` file, you must include the following lines:
64
64
```
65
65
66
66
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.
68
68
69
69
Every other file you write tests in should include Catch:
70
70
@@ -73,14 +73,14 @@ Every other file you write tests in should include Catch:
73
73
```
74
74
75
75
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!
76
77
77
78
### Basic Tests
78
79
79
-
Alright, let's write some unit tests!
80
80
We are going to test a function that generates Fibonacci numbers (1, 1, 2, 3, 5, 8, ...).
81
81
Here's our function:
82
82
83
-
```c++
83
+
```{.cpp .numberLines}
84
84
/* Generate the Nth Fibonacci number */
85
85
intfibonacci(int n)
86
86
{
@@ -102,13 +102,14 @@ Each test case has a name and a tag; generally you'll tag all test cases for a f
102
102
(You can tell Catch to run only tests with specific names or tags if you like.)
103
103
104
104
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.
108
108
109
109
In general, when writing tests, you want to test every path through your code at least once.
110
110
Here's a pretty good test for our Fibonacci function:
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
+
150
152
```
151
153
All tests passed (4 assertions in 1 test case)
152
154
```
153
155
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]
157
159
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.
158
160
For 'and' statements, rather than `CHECK(x && y);`, write `CHECK(x); CHECK(y);`.
159
161
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.)
161
163
162
164
There are also matching assertions `REQUIRE_FALSE` and `CHECK_FALSE` that check to make sure a statement is false, rather than true.
163
165
164
166
### Testing Exceptions
165
167
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}
168
170
#include<stdexcept>// for domain_error
169
171
usingnamespacestd;
170
172
@@ -177,7 +179,7 @@ int fibonacci(int n)
177
179
}
178
180
else if(n <= 1)
179
181
{
180
-
return n;
182
+
return 1;
181
183
}
182
184
else
183
185
{
@@ -197,20 +199,21 @@ As before, each assertion comes in a `CHECK` and a `REQUIRE` flavor.
197
199
For example, we can check that our Fibonacci function properly verifies that its input is in the domain by testing when it throws exceptions
198
200
and what exceptions it throws:
199
201
200
-
```c++
202
+
```{.cpp .numberLines}
201
203
TEST_CASE("Fibonacci Domain", "[Fibonacci]")
202
204
{
203
205
CHECK_NOTHROW(fibonacci(0));
204
206
CHECK_NOTHROW(fibonacci(10));
205
207
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");
207
210
}
208
211
```
209
212
210
213
### Organizing Your Tests
211
214
212
215
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.
214
217
215
218
First, we can't have our `main()` function and Catch's auto-generated `main()` in the same program.
216
219
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.
225
228
226
229
Third, compiling Catch's auto-generated `main()` function takes a while.
227
230
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
229
232
that just contains Catch's `main()`.
230
233
This file looks exactly like this:
231
234
232
-
```c++
235
+
```{.cpp .numberLines}
233
236
#defineCATCH_CONFIG_MAIN
234
237
#include"catch.hpp"
235
238
```
236
239
237
240
Then in `test_fibonacci.cpp`, we just have the following includes:
238
-
```c++
241
+
```{.cpp .numberLines}
239
242
#include"fibonacci.h"
240
243
#include"catch.hpp"
241
244
@@ -245,9 +248,9 @@ Then in `test_fibonacci.cpp`, we just have the following includes:
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
379
384
### Code Coverage
380
385
381
386
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.
383
388
Fortunately, there are tools to check for you!
384
389
We'll use `gcov` to generate code coverage reports for us.
385
390
@@ -401,7 +406,7 @@ Once you have compiled your tests, execute them as normal.
401
406
In addition to running your tests, your executable will also produce a number of files ending in `.gcda` and `.gcno`.
402
407
These are data files for `gcov`. They're binary, so opening them in a text editor will not be particularly enlightening.
403
408
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.)
405
410
406
411
There are a couple of flags that you definitely want to use for `gcov`:
407
412
@@ -454,17 +459,50 @@ The numbers in the left margin are the number of times each line is executed.
454
459
If a line isn't executed, you will see `####` in the left column instead.
455
460
This makes it easy to spot code that isn't covered by your tests!
456
461
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
+
457
495
\newpage
458
496
## Questions
459
497
460
498
Name: `______________________________`
461
499
462
500
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}
464
502
2. What is the difference between the `CHECK` and `REQUIRE` test assertions?
465
-
\vspace{8em}
503
+
\vspace{10em}
466
504
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.
468
506
\newpage
469
507
470
508
## Quick Reference
@@ -492,7 +530,8 @@ String Matchers:
492
530
493
531
Floating Point:
494
532
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
496
535
497
536
### Coverage
498
537
@@ -517,5 +556,7 @@ Floating Point:
517
556
In logical terms, unit tests are a bunch of "there exists" statements; whereas a proof of correctness is a "for all" statement.
518
557
Unfortunately, proving programs correct is a difficult task and the tools to do so are not exactly ready for widespread use yet.
519
558
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.
521
560
[^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