CSE 331: Coverage
Course motto: Your job is to deliver code that you know works.
Tests from the specification alone
Lecture 4 chose every test's input from the specification. Here is a specification, and eleven inputs chosen that way:
/**
* Decides whether a number is prime.
*
* @param n the number to decide
* @requires 0 < n
* @return true if n is prime, and false if it is not
*/
static boolean isPrime(int n)
Test 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, and 12. Each has an expected answer anyone can work out. The specification describes two cases, prime and not prime, and the inputs cover each several times over, so this looks like a thorough set. Now read the implementation:1
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
SMALL, declared on line 9, is a table of the answers for every
number below 100, filled once by a method lower in the file. Every
one of our eleven inputs is below 100, so every one of our tests
is the lookup on line 20. The loop never ran. It has a mistake:
line 22 stops one step early, k * k < n where it should be
k * k <= n, so a square of a prime passes as prime. Our eleven
tests pass. The method is wrong. The file's main prints
isPrime of the number on the command line, and bc is a
calculator:
$ javac IsPrime.java
$ java IsPrime 121
true
$ echo '11 * 11' | bc
121
Nothing in the specification says to look past 100. The code does.
So the specification is not the only thing to read when choosing tests. Today we read the code too. Reading it answers what Lecture 4 left open: how many tests are enough, and which ones.
How many tests are enough?
Our eleven tests of isPrime looked like plenty. They were not,
because they all did the same thing. Each input was below 100, so
each test ran the if on line 19, took the lookup on line 20, and
returned. The loop on line 22 never ran. Fifty tests on numbers
below 100 would be no better. Each would take the same path, and
the loop would still never run. So the number of tests is not what
matters. What matters is which parts of the method the tests run.
Testing every input would be different. Lecture 4's first tip was
to test only the inputs the specification allows. Those are the
allowed inputs: for isPrime, every n with 0 < n. If the
tests ran isPrime on every allowed input and all passed, the
method would be correct, because the specification is a claim
about every run and we would have checked every run. Lecture 4's
third tip says to do this when possible. When is it possible? A
Java int has \(2^{32}\) values. The specification requires
0 < n, which leaves \(2^{31} - 1\) allowed inputs. A computer can
run that many. The problem is step 2 of Lecture 4's four steps:
the expected answer for each test comes from the specification.
Nobody works out two billion answers by hand. A second program
could, but then we would have to know that the second program is
right. And two int arguments make \(2^{64}\) inputs, which would
take one computer centuries.
Lecture 2's check
came close to testing every input. It ran four implementations of
indexOf against five specifications, on every small array: up to
four elements, each 0, 1, or 2, with every range and every x from
0 to 3. That is 6,196 calls to each implementation. Within that
bound, nothing was left out. Outside it, an array can be any
length and hold any int, and the check tested none of those.
Exhaustive testing in practice means exhaustive within a bound.
One test is never enough. Suppose isPrime always returned
true. A test on 7 expects true, and it passes. A single test
cannot catch a method that always returns the same answer,
because that answer may be the one the test expects. A second
test on 8 expects false, and it fails. So every method needs at
least two tests.2
Between two tests and all of them, the number says nothing more. What we ask of a set of tests is which parts of the method it has run. The specification cannot answer that, since it does not mention the parts. The code can. Lecture 4 chose inputs by reading the specification. Today we choose them by reading the code, and we measure a set of tests by how much of the code it has run. The word for that is coverage. Testers call choosing tests from the code clear-box testing, or white-box testing, and choosing them from the specification alone black-box testing. A method's tests need both. The next three sections give three measures of coverage, each catching what the one before it misses.
Statement coverage
Our eleven tests of isPrime ran lines 19 and 20, and no test
ran the loop on lines 22 to 27. The simplest measure of a set of
tests asks for exactly what the eleven lacked: that every
statement in the method ran during some test.
Definition: statement coverage
A set of tests has statement coverage of a method if every statement that some allowed input reaches is run by some test.
The box says some allowed input rather than any input because of Lecture 4's first tip. A statement that only a disallowed input reaches, such as a check that the precondition holds, is not a gap in coverage, since no test may call the method that way.
By this measure the eleven tests were not enough. The test to add
is one whose input reaches the loop, any n of 100 or more. The
run on 121 above was such a test, and it showed the mistake. The
measure does not pick 121, though. It asks only that every
statement run, and three tests do that: 2 runs the lookup, 100
runs the return false inside the loop, and 101 runs the
return true after it. Those three are better tests than the
eleven, since every line of the method has now run. All three
pass. The mistake is still there.
Now a smaller method, and one test of it:
/**
* Picks the smaller of two numbers.
*
* @param a one number
* @param b the other number
* @return the smaller of a and b
*/
static int min(int a, int b) {
int r = a;
if (a <= b) {
r = a;
}
return r;
}
@Test
void firstIsSmaller() {
assertEquals(1, Min.min(1, 2));
}
Run the test in your head. r becomes 1, the condition is true,
r becomes 1 again, and the method returns 1. Every statement
ran, so this one test has statement coverage, and it passes. Is
the method right? Try min(2, 1). The condition is false, the if
does nothing, and the method returns 2, the larger number. A
second test finds the mistake:
@Test
void secondIsSmaller() {
assertEquals(1, Min.min(2, 1));
}
│ ├─ firstIsSmaller() ✔
│ └─ secondIsSmaller() ✘ expected: <1> but was: <2>
The first test never ran the if with its condition false.
Statement coverage never asked for that: the code after an if
with no else runs either way, so every statement can run while
the condition is never false. The next measure asks for the false
case.
Branch coverage
Every if has two branches, the one taken when its condition is
true and the one taken when it is false. An if with no else
still has a false branch. That branch is empty.
Definition: branch coverage
A set of tests has branch coverage of a method if every branch that some allowed input takes is run by some test.
Branch coverage asks for the test that found the mistake in
min, the second one. Our two tests have it: the first runs the
true branch and the second the false one. Now a method with two
ifs, and two tests of it:
/**
* Gives the quadrant of a point that is on neither axis.
*
* y
* 2 | 1
* --------+-------- x
* 3 | 4
*
* @param x the point's x coordinate
* @param y the point's y coordinate
* @requires x != 0 and y != 0
* @return 1 if x > 0 and y > 0,
* 2 if x < 0 and y > 0,
* 3 if x < 0 and y < 0, and
* 4 if x > 0 and y < 0
*/
static int quadrant(int x, int y) {
int q = 1;
if (x < 0) {
q = 2;
}
if (y < 0) {
q = q + 1;
}
return q;
}
@Test
void upperRight() {
assertEquals(1, Quadrant.quadrant(1, 1));
}
@Test
void lowerLeft() {
assertEquals(3, Quadrant.quadrant(-1, -1));
}
On (1, 1) both conditions are false, and on (-1, -1) both are
true. So the two tests run both branches of both ifs: they have
branch coverage, and both pass. Is the method right? Try (1, -1).
The first condition is false, the second is true, and q becomes
2 where the specification promises 4. A third test, on that
input, finds the mistake:
@Test
void lowerRight() {
assertEquals(4, Quadrant.quadrant(1, -1));
}
│ ├─ upperRight() ✔
│ ├─ lowerLeft() ✔
│ └─ lowerRight() ✘ expected: <4> but was: <2>
The two passing tests ran every branch, but never this combination
of branches. A path through the method is one such
combination, and quadrant has four of them. Asking for every
path would have found the mistake. We do not ask for it, because
paths multiply: a method with ten ifs in a row has \(2^{10}\)
paths, over a thousand, and a loop adds a path for every number
of times its body can run. The next section adds one more
measure, for loops, which branches do not measure.
Loop coverage
Here is Lecture 4's indexOf again, with specification D and
implementation 1, and its four tests, from the file
IndexOfTest:
/**
* Searches for a value in part of an array.
*
* @param a the array to search; must be non-null
* @param lo the index where the range to search begins
* @param hi the index just past where the range to search ends
* @param x the value to search for
* @requires 0 <= lo <= hi <= a.length
* @return some index i with lo <= i < hi and a[i] == x,
* or -1 if there is none
*/
static int indexOf(int[] a, int lo, int hi, int x) {
for (int i = lo; i < hi; i++) {
if (a[i] == x) {
return i;
}
}
return -1;
}
@Test
void findsTheOnlyMatch() {
int[] a = {3, 0, 7};
assertEquals(2, IndexOf.indexOf(a, 0, 3, 7));
}
@Test
void returnsMinusOneWhenAbsent() {
int[] a = {3, 0, 7};
assertEquals(-1, IndexOf.indexOf(a, 0, 3, 42));
}
@Test
void findsTheFirstMatch() {
int[] a = {5, 1, 5, 2, 5};
assertEquals(0, IndexOf.indexOf(a, 0, 5, 5));
}
@Test
void findsSomeMatch() {
int[] a = {5, 1, 5, 2, 5};
int i = IndexOf.indexOf(a, 0, 5, 5);
assertTrue(0 <= i && i < 5 && a[i] == 5);
}
Measure the four tests. findsTheOnlyMatch returns from inside
the loop. returnsMinusOneWhenAbsent returns after it. Between
the two, every statement runs, and the if takes both branches.
So the four tests have statement coverage and branch coverage.
Neither measure asks how many times the loop's body ran.
Count how many times the body runs in each test, by running each test in your head:
findsTheOnlyMatch:lois 0 andhiis 3, soitakes 0, 1, and 2. The body runs three times, and the third run returns 2.returnsMinusOneWhenAbsent: the same range, with no 42 in it. The body runs three times, and the method returns -1 after the loop.findsTheFirstMatchandfindsSomeMatch: one input between them, with 5 at index 0. The body runs once, and that run returns 0.
No test runs the body zero times. The input that does is an empty
range, with lo == hi. On {3, 0, 7} with lo and hi both 1,
i starts at 1, the condition i < hi is false at once, and the
method returns -1 without running the body. Lecture 4's
exercise 1 listed that input. IndexOfTest left it out.
The third measure asks for that test:
Definition: loop coverage
A set of tests has loop coverage of a method if, for every loop in it, some test runs the loop's body zero times, some test runs it once, and some test runs it more than once.
Why those three counts? Zero times, because the code after a loop has to be right when the loop did nothing, a case that is easy to forget. Once, because the body has to be right on its own. More than once, because of what a loop carries from one run of its body to the next. Here is a method whose loop carries a value, and two tests of it:
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
@Test
void loopZeroTimes() {
assertEquals(1, Pow.pow(2, 0));
}
@Test
void loopOnce() {
assertEquals(2, Pow.pow(2, 1));
}
Run both in your head:
loopZeroTimes: n is 0, so the loop does nothing, and the method returns 1, which is right.loopOnce: n is 1, so the body runs once,rbecomesb, and the method returnsb, right again.
Between the two, every statement ran, and the loop's condition
was both true and false. So the two tests have statement coverage
and branch coverage, and both pass. Is the method right? Try
pow(2, 3).
Line 19 should read r = r * b;. It reads r = b;, the kind of
slip a hurried edit leaves behind. The first run of the body
leaves r holding b, which is right. The second run should
multiply that by b, and instead sets r to b again. So on
pow(2, 3) the method returns 2 where the specification
promises 8. A third test finds the mistake:
@Test
void loopManyTimes() {
assertEquals(8, Pow.pow(2, 3));
}
│ ├─ loopOnce() ✔
│ ├─ loopZeroTimes() ✔
│ └─ loopManyTimes() ✘ expected: <8> but was: <2>
JUnit lists the tests in its own order, not the file's. A value written in one run of the body and read in the next is checked only by a test that runs the body at least twice. That is what "more than once" is for.
Recursion
A method that calls itself carries values the same way, through
the arguments of each call and the result each call returns, so
the same rule applies, counting calls instead of runs of a loop
body. For every recursive method, some test makes zero recursive
calls, some test makes one, and some test makes more than
one.2 Here is pow again, by recursion, and two tests of
it, on the same inputs:
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | |
@Test
void zeroCalls() {
assertEquals(1, Pow.powRec(2, 0));
}
@Test
void oneCall() {
assertEquals(2, Pow.powRec(2, 1));
}
Run both in your head:
zeroCalls: n is 0, so the method returns 1 without calling itself, which is right.oneCall: n is 1, so the method calls itself once, with 0. That call returns 1, and the method returnsb, right again.
Again the two tests have statement coverage and branch coverage,
and both pass. Is the method right? Try powRec(2, 3).
Lines 36 and 37 should be one line, return b * powRec(b, n - 1);.
They make the call and throw away what it returns, so whenever n
is at least 1 the method returns b. On powRec(2, 3) the method
calls itself three times and returns 2 where the specification
promises 8. A third test finds the mistake:
@Test
void manyCalls() {
assertEquals(8, Pow.powRec(2, 3));
}
│ ├─ oneCall() ✔
│ ├─ zeroCalls() ✔
│ └─ manyCalls() ✘ expected: <8> but was: <2>
With one recursive call, the call returns 1, and b times 1 is
b, so throwing the result away changes nothing. The result can
differ from 1 only when that call calls the method again, so only
a test that makes at least two calls finds the mistake.
When to stop
A method's tests are enough when there are at least two of them and, between them, they have run all that an allowed input can reach:2
- every statement;
- both branches of every
if; - every loop's body zero times, once, and more than once;
- every method that calls itself, with zero, one, and more than one recursive call.
Until the list is complete, it says which test to write next: one that runs an item on the list that no test has run yet. When the list is complete, you may stop. That is how many tests are enough, and which ones.
Three things about the list:
- Only allowed inputs count. Lecture 4's first tip says that tests never call a method on an input its specification forbids. So a statement or branch that only such an input reaches is not on the list. The usual case is a check that the precondition holds. No test runs it, and the list does not ask for it.
- The measures nest. The tests that give a method loop coverage usually give it branch coverage too, and the tests that give it branch coverage give it statement coverage. So in practice you plan for loops and branches, and statements come free.
- The measures are in wide use. Tools that measure coverage
report statement and branch coverage on whole programs. Loop
coverage you count yourself, as we did for
IndexOfTest. Homework in this course holds a method's tests to the whole list.
The list says when you may stop writing tests. It does not say the method is correct. The next section shows tests that complete the list and still miss a mistake.
Coverage is not correctness
The list asked for the tests that found the mistakes in min,
pow, and powRec. That is what the list is for. It is not a
promise that tests which complete it find every mistake.
quadrant already showed that: its two tests completed the list
and passed, and only a third test, which the list did not ask
for, found the mistake. isPrime shows it again. Add two inputs
to our eleven: 100 and 10,007, which is prime. Here is what the
method returns on each, and how many times the loop's body runs:
isPrime(100): returns false, loop runs 1
isPrime(10007): returns true, loop runs 99
On 100 the loop's body runs once, finds the divisor 2, and returns false. On 10,007 it runs 99 times and returns true. No allowed input runs it zero times. Every n that reaches the loop is at least 100, and 2 times 2 is less than that, so the body runs at least once. The zero case is unreachable, like a statement no allowed input reaches, and the list does not ask for it. With these two inputs the set has loop coverage, branch coverage, and statement coverage, and every test passes. The mistake is still there. It shows only on a square of a prime at least 11, where the loop stops one step short of the one divisor that matters.
What would have found it is a boundary, in Lecture 4's sense, but
read off the code rather than the specification. The condition
k * k < n divides inputs into two kinds at the point where
k * k == n, so a test should sit right on that line: a perfect
square. The list does not ask for this test. A careful tester
writes it anyway.3
Every mistake so far was in code on the page: a line that reads wrong. A mistake can also be code that is missing. When a specification describes three cases and the method handles two, the third has no statement, branch, or loop to run, so the list asks for no test of it. Tests that complete the list pass, and the third case is never tried. Reading the code finds nothing that the code left out. The test for a missing case comes from reading the specification, case by case, as Lecture 4 did. Marick, in the further reading, calls a mistake of this kind a fault of omission.
Some mistakes lie outside what the list names at all. The Therac-25, the radiation therapy machine in Reading 1's extra, gave six patients massive overdoses in the 1980s. One of its mistakes showed only when the operator edited the prescription within the eight seconds the machine took to set its magnets. The task that read the prescription had already finished, and the task that set the magnets never noticed the edit. The beam the patient received was far stronger than any prescription allowed.4 No statement, branch, or loop was wrong on its own. The mistake was in the timing of two tasks that ran at once, which the list never mentions. A test finds it only by typing fast enough, often enough, to land inside those eight seconds.
So coverage tells you when you may stop writing tests. It does not tell you the code is correct. Lecture 1 said why: testing can refute a claim about every run and never confirm it. The one exception is testing every allowed input, which is rarely possible. A set of tests that completes the list and passes is a good reason to believe the code works. It is not knowledge. Lecture 4 named two other ways to know. A tool confirms one claim about every run, the claim it was built to check. Reasoning, which the lectures after Monday's take up, confirms a claim about every run when the argument is right.
The floor
The list says when you may stop. It does not say that you must.
Above the list, how much more to test depends on what a mistake
would cost. A mistake in a program that steers a car costs more
than a mistake in a program that counts words, so the steering
program's tests go further: every path through a method, and
every boundary read off the code, the tests that quadrant and
isPrime needed. How far to go is an engineering judgment, and
you will make it on every program you write.
Below the list there is no judgment to make. The list, together with the cases and boundaries Lecture 4 read off the specification, is the least testing that any code you deliver gets, whatever the code does and whatever the deadline. That least is the floor. The motto says whose job it is. Code tested less than the floor is not code you know works. Delivering it anyway is not a trade-off an engineer may make. It is a failure of due diligence.
Two mistakes from 2014 show what skipping the floor costs. The
code in Apple's iOS and OS X that checks a secure connection's
signature carried the line goto fail; twice in a row, pasted
once too often.5 The second copy sat under no if,
whatever its indentation said, so every run jumped to the exit
with the error code still reporting success, and the signature
check below it never ran, on any input. Statement coverage would
have shown that: no test ran the lines after the stray goto,
and no test could have. The same year, Heartbleed, a mistake in
OpenSSL, a library many web servers use to secure their
connections, let anyone on the internet read up to 64 kilobytes
of a server's memory per request, passwords and private keys
included. The code that answered a heartbeat request trusted the
length the request claimed for itself and never checked it
against the bytes that came: a missing check, the kind of mistake
the last section named.6 Lecture 4's fourth tip would
have shown that: a request whose claimed length is larger than
its payload is a boundary, and one test of it gets back bytes the
request never sent.
Test code is code
Lecture 2 ran every implementation of indexOf on every small
array, and reported the result in
one sentence.
That check was a program: a loop over every array, every range,
and every x, calling indexOf and checking the answer against
each specification. That loop could have been wrong. A loop that
skipped the empty range, or an answer-checker that was itself
mistaken, would have reported success and meant nothing. A test
is code, and code can be wrong.
So keep your tests simple. A test that is one call and one
assertEquals, like the tests of min, quadrant, and pow,
can be read and checked by eye. A test with a loop in it needs
its own tests, and then those need reading. When you find
yourself writing a clever test, write two plain ones instead.
Testable by design
Some methods are hard to unit test. The cure is in how you write the method, not the test. A method is hard to unit test when:
- it is large, or does several jobs at once, so that its inputs must be chosen to steer every job together;
- it reads or writes something outside its arguments and its return value, such as a file, the keyboard, or the screen, so a test has to set that up and inspect it afterward.
Lecture 2's Sum showed the cure. Lecture 1's Sum did
everything in main: read the numbers, added them, and printed
the result.
Lecture 2
split it into readList and sumList. sumList takes a list
and returns a number, so a test of it is one call and one
assertEquals, with no keyboard and no screen in the way.
readList still reads the keyboard, so testing it takes more
setup. But the arithmetic is no longer behind the keyboard: the
split moved it into a method a test can call on its own.
Lecture 2 made that split so that a program can be understood
one method at a time. The same split makes it testable.
What tests tell us
A test confirms a claim about its own run. Coverage says how many such runs a method's tests must include, and which: enough to complete the list in When to stop. Those tests come from reading the code. The expected answers still come from the specification, as Lecture 4 insisted. A method with such tests, all passing, has been tested enough. That is still less than knowing that it is correct. Reading the code further, to the values each variable can take at each line, leads past testing to reasoning about every run at once. Monday's lecture turns to what a program should do when its input is wrong, and the lectures after it to that reasoning.
Exercises
Each answer is folded under its question. Write yours first.
1. Lecture 2's count calls indexOf to count how many times
x occurs in an array. Here it is with a specification. indexOf
lives in the class IndexOf, so count calls it as
IndexOf.indexOf, as Lecture 4's tests did:
/**
* Counts how many times a value occurs in an array.
*
* @param a the array to search; must be non-null
* @param x the value to count
* @return the number of indices i with a[i] == x
*/
static int count(int[] a, int x) {
int c = 0;
int i = IndexOf.indexOf(a, 0, a.length, x);
while (i != -1) {
c++;
i = IndexOf.indexOf(a, i + 1, a.length, x);
}
return c;
}
The indexOf it calls is the one Loop coverage showed,
specification D with Lecture 2's implementation 1:
/**
* Searches for a value in part of an array.
*
* @param a the array to search; must be non-null
* @param lo the index where the range to search begins
* @param hi the index just past where the range to search ends
* @param x the value to search for
* @requires 0 <= lo <= hi <= a.length
* @return some index i with lo <= i < hi and a[i] == x,
* or -1 if there is none
*/
static int indexOf(int[] a, int lo, int hi, int x) {
for (int i = lo; i < hi; i++) {
if (a[i] == x) {
return i;
}
}
return -1;
}
Choose inputs for tests of count that complete the list in
When to stop, and give each test's expected answer.
Answer
-
One set. Each row is one input, the answer the specification promises, and how many times the loop's body runs:
a x expected loop runs {3, 0, 7}42 0 0 {3, 0, 7}7 1 1 {5, 1, 5, 2, 5}5 3 3 {}3 0 0 -
The rest follows. Four tests is at least two. The test that runs the body once runs every statement: the two before the loop, the two in its body, and the
returnafter it. The method has noif. Its only branch is thewhilecondition, true before each run of the body and false when the loop ends. So branch coverage comes with loop coverage. -
Lecture 4's second tip at work. The tests assume
indexOfsatisfies its specification; they do not check it. With implementation 1 underneath, all four pass. -
And a reminder. Lecture 2 showed
countis wrong against specification D, since an implementation of D may return a later match and skip earlier ones. These tests complete the list and pass. Coverage is not correctness.
2. On which allowed inputs does isPrime return the wrong
answer? Why not on 100 or on 101?
Answer
-
Squares of primes from 11 up. On 121, the loop runs k from 2 to 10, since the condition stops k at
k * k < 121. No k in that range divides 121, so the method returns true. The same holds for 169, 289, and every square of a prime at least 11. Below 100 the table answers, and it was built with the right condition. -
100 and 101. 100 is even, so k = 2 divides it on the loop's first run, before the stopping condition matters. 101 is prime, so returning true is right. The mistake bites only when n's smallest divisor is its square root, that is, when n is the square of a prime.
3. Lecture 2's implementation 2 of indexOf is implementation
1 with throw new NoSuchElementException() in place of
return -1. Under specification A, whose precondition promises
that x occurs in the range, which of its statements can tests
reach?
Answer
-
Every statement but the throw. A's precondition promises a match, so every allowed input returns from inside the loop. The
throwruns only on an input that breaks the precondition, which Lecture 4's first tip keeps out of the tests. -
So statement coverage is complete without it. Statement coverage asks for every statement some allowed input reaches, and no allowed input reaches that one. Under B, C, or D, where x may be absent, the same statement is reachable and a test must run it.
Further reading
- The course's testing tips. James Wilcox and Kevin Zatloukal, "Testing" (2025). The second list is the list in When to stop; the heuristics at the end are the practices beyond it.
- Testing systematically. Brian Kernighan and Rob Pike, The Practice of Programming (1999), section 6.2, whose last rule is to measure test coverage, and section 6.1 on boundary conditions, which this lecture read off the code.
- Coverage misused. Brian Marick, "How to Misuse Code Coverage" (1997). A missed line is a clue that the tests near it were designed weakly. Writing the one test that runs it satisfies the tool and leaves the weakness. A suite written to reach a coverage number is weak in the way coverage cannot report: it misses the cases the code never handles. The first seven pages carry the argument (cached: PDF).
- Tests the machine chooses. Koen Claessen and John Hughes,
"QuickCheck: A Lightweight Tool for Random Testing of Haskell
Programs" (ICFP, 2000). State what every answer must satisfy,
as
findsSomeMatchdid, and let the machine choose the inputs, thousands of them. The practice is called property-based testing. - Testing the tests. Richard DeMillo, Richard Lipton, and Fred Sayward, "Hints on Test Data Selection: Help for the Practicing Programmer" (Computer, 1978). Change the program a little, many times over, and ask whether the tests notice each change. A change no test notices marks a weak spot in the tests, as a missed line does for coverage. The practice is called mutation testing.
-
The
isPrimeexample is Kevin Zatloukal's, andminandquadrantare James Wilcox's, from past offerings of this course. ↩ -
James Wilcox and Kevin Zatloukal, "Testing" (2025), the one-page note Lecture 4 cited. Its first three items were Lecture 4's tips; its second list is the list in When to stop. ↩↩↩
-
The "Testing" note lists this and other practices it recommends but does not require: a boundary test on each side of every comparison, two values for every argument, a test for each side of an
||or&&. ↩ -
Nancy Leveson and Clark Turner, "An Investigation of the Therac-25 Accidents" (Computer, 1993), the extra on Reading 1's page, which carries a content note: the paper describes the patients' injuries and deaths. The eight seconds and the two tasks are in its account of the software error found after the Tyler accidents. ↩
-
Adam Langley, "Apple's SSL/TLS bug" (2014), shows the lines from Apple's published source and traces what the second
gotoskips. Apple's note on the fix, iOS 7.0.6, says only that "Secure Transport failed to validate the authenticity of the connection" (CVE-2014-1266). ↩ -
The OpenSSL Security Advisory of April 7, 2014 (CVE-2014-0160): "A missing bounds check in the handling of the TLS heartbeat extension can be used to reveal up to 64k of memory to a connected client or server." The mistake had shipped in OpenSSL 1.0.1 in March 2012. Neel Mehta of Google and a team at Codenomicon found it, and Codenomicon's page, heartbleed.com, named it. ↩