Testing and Debugging: Chapter Goals
Testing and Debugging: Chapter Goals
Testing and Debugging: Chapter Goals
8
CHAPTER
Chapter Goals
◆ To learn how to design test harnesses for testing components of your programs
in isolation
313
314 Chapter 8. Testing and Debugging
A complex program never works right the first time; it will contain errors, commonly
called bugs, and will need to be tested. It is easier to test a program if it has been de-
signed with testing in mind. This is a common engineering practice: On television
circuit boards or in the wiring of an automobile, you will find lights and wire connec-
tors that serve no direct purpose for the TV or car but are put in place for the repair
person in case something goes wrong. In the first part of this chapter you will learn
how to instrument your programs in a similar way. It is a little more work up-front,
but that work is amply repaid by shortened debugging times.
In the second part of this chapter you will learn how to run the debugger to cope
with programs that don’t do the right thing.
The single most important testing tool is the unit test of a method or a set of co-
operating methods. For this test, the methods are compiled outside the program
in which they will be used, together with a test harness that feeds parameters to the
methods.
The test arguments can come from one of three sources: from user input, by run-
ning through a range of values in a loop, and as random values.
In the following sections, we will use a simple example for a method to test, namely
an approximation algorithm to compute square roots that was known to the ancient
Greeks. The algorithm starts by guessing a value x that might be somewhat close to
the desired square root 冪a. The initial value doesn’t have to be very close; x ⳱ a is a
perfectly good choice. Now consider the quantities x and a/x. If x ⬍ 冪a, then a/x ⬎
a/ 冪a ⳱ 冪a. Similarly, if x ⬎ 冪a, then a/x ⬍ a/ 冪a ⳱ 冪a. That is, 冪a lies between
x and a/x. Make the midpoint of that interval our improved guess of the square root, as
shown in Figure 1. Therefore set xnew ⳱ (x Ⳮ a/x)/2 and repeat the procedure—that
is, compute the average of xnew and a/xnew . Stop when two successive approximations
differ from each other by a very small amount.
This method converges very rapidly. To compute 冪400, only 10 steps are re-
quired:
400
200.5
101.24750623441396
52.599110411804922
30.101900881222353
21.695049123587058
20.06621767747577
20.000109257780434
20.000000000298428
20
8.1 Unit Tests 315
Figure 1 a/x a x
Here is a static method sqrt in a class MathAlgs that implements this algorithm.
class MathAlgs
{ public static double sqrt(double a)
{ if (a <= 0) return 0;
double xnew = a;
double xold;
do
{ xold = xnew;
xnew = (xold + a / xold) / 2;
} while (!Numeric.approxEqual(xnew, xold));
return xnew;
}
}
Does this method work correctly? Let us approach this question systematically. First,
let us write a test harness to supply individual values to the method to be tested.
Program SqrtTest1.java
public class SqrtTest1
{ public static void main(String[] args)
{ ConsoleReader console = new ConsoleReader(System.in);
boolean done = false;
while (!done)
{ String inputLine = console.readLine();
if (inputLine == null) done = true;
else
{ double x = Double.parseDouble(inputLine);
double y = MathAlgs.sqrt(x);
For each test case, the harness code calls the MathAlgs.sqrt method and prints the
result. You can then manually check the computations. Once you have confidence
that the method works correctly, you can plug it into your program.
It is also possible to generate test cases automatically. If there are few possible
inputs, it is feasible to run through a representative number of them with a loop:
Program SqrtTest2.java
public class SqrtTest2
{ public static void main(String[] args)
{ for (double x = 0; x <= 10; x = x + 0.5)
{ double y = MathAlgs.sqrt(x);
System.out.println("square root of " + x
+ " = " + y);
}
}
}
Note that we purposefully test boundary cases (zero) and fractional numbers.
Unfortunately, this test is restricted to only a small subset of values. To overcome
that limitation, random generation of test cases can be useful:
Program SqrtTest3.java
import java.util.Random;
No matter how you generate the test cases, the important point is that you test the
method thoroughly before you put it into the program. If you ever put together a
computer or fixed a car, you probably followed a similar process. Rather than simply
throwing all the parts together and hoping for the best, you probably first tested
each part in isolation. It takes a little longer, but it greatly reduces the possibility of
complete failure once the parts are put together.
8.2 Selecting Test Cases 317
scientist Edsger Dijkstra pointed out, testing can show only the presence of bugs—
not their absence. To gain more confidence in the correctness of a program, it is
useful to consider its internal structure. Testing strategies that look inside a program
are called white-box testing. Performing unit tests of each method is a part of white-box
testing.
You want to make sure that each part of your program is exercised at least once
by one of your test cases. This is called test coverage. If some code is never executed
by any of your test cases, you have no way of knowing whether that code would
perform correctly if it ever were executed by user input. That means that you need to
look at every if/else branch to see that each of them is reached by some test case.
Many conditional branches are in the code only to take care of strange and abnormal
inputs, but they still do something. It is a common phenomenon that they end up
doing something incorrect, but that those faults are never discovered during testing,
because nobody supplied the strange and abnormal inputs. Of course, these flaws
become immediately apparent when the program is released and the first user types
in a bad input and is incensed when the program crashes. A test suite should ensure
that each part of the code is covered by some input.
For example, in testing the getTax method of the tax program in Chapter 5, you
want to make sure that every if statement is entered for at least one test case. You
should test both single and married taxpayers, with incomes in each of the three tax
brackets.
It is a good idea to write the first test cases before the program is written completely.
Designing a few test cases can give you insight into what the program should do,
which is valuable for implementing it. You will also have something to throw at the
program when it compiles for the first time. Of course, the initial set of test cases
will be augmented as the debugging process progresses.
Modern programs can be quite challenging to test. In a program with a graphical
user interface, the user can click random buttons with a mouse and supply input in
random order. Programs that receive their data through a network connection need
to be tested by simulating occasional network delays and failures. All this is much
harder, since you cannot simply place keystrokes in a file. You need not worry about
these complexities as you study this book, and there are tools to automate testing in
these scenarios. The basic principles of regression testing (never throwing a test case
away) and complete coverage (executing all code at least once) still hold.
In the last section we worried about how to get test inputs. Now let us consider what
to do with the outputs. How do you know whether the output is correct?
Sometimes you can verify the output by calculating the correct values by hand.
For example, for a payroll program you can compute taxes manually.
Sometimes a computation does a lot of work, and it is not practical to do the com-
putation manually. That is the case with many approximation algorithms, which
8.3 Test Case Evaluation 319
may run through dozens or hundreds of iterations before they arrive at the final an-
swer. The square root method of Section 8.1 is an example of such an approximation.
How can you test that the square root method works correctly? You can supply
test inputs for which you know the answer, such as 4 and 900, and also 25 4 , so that
you don’t just restrict the inputs to integers.
Alternatively, you can write a test harness that verifies that the output values fulfill
certain properties. For the square root program you can compute the square root,
compute the square of the result, and verify that you obtain the original input:
Program SqrtTest4.java
import java.util.Random;
if (Numeric.approxEqual(y * y, x))
System.out.println("Test passed.");
else
System.out.println("Test failed.");
System.out.println("square root of " + x + " = " + y);
}
}
}
Finally, there may be a less efficient way of computing the same value that a method
produces. You can then run a test harness that computes the method to be tested,
together with the slower process, and compares the answers. For example, 冪x ⳱
x 1/2 , so you can use the slower Math.pow method to generate the same value. Such
a slower but reliable method is called an oracle.
Program SqrtTest5.java
import java.util.Random;
◆ You need to change the batch file to make this work. In a batch file, %1 denotes the first string
◆ that you type after the name of the batch file, %2 the second string, and so on:
◆
◆ File test.bat
◆
◆ java %1 < test1.in
◆ java %1 < test2.in
◆ java %1 < test3.in
◆
◆ What if you have more than three test files? DOS batch files have a very primitive for loop:
◆
◆ File test.bat
◆
◆ for %%f in (test*.in) do java %1 < %%f
◆ If you work in a computer lab, you will want a batch file that copies all your files onto a floppy
◆ disk when you are ready to go home. Put the following lines in a file gohome.bat:
◆
◆
◆
File gohome.bat
◆ copy *.java a:
◆ copy *.txt a:
◆ copy *.in a:
◆
◆ There are lots of uses for batch files, and it is well worth it to learn more about them.
◆ Batch files are a feature of the DOS operating system, not of Java. On a UNIX system, shell
◆ scripts are used for the same purpose.
Sometimes you run a program and you are not sure where it spends its time. To get
a printout of the program flow, you can insert trace messages into the beginning and
end of every method:
public static double sqrt(double a)
{ System.out.println("Entering MathAlgs.sqrt. a = " + a);
. . .
System.out.println
("Leaving MathAlgs.sqrt. Return value = " + xnew);
return xnew;
}
To get a proper trace, you must locate each method exit point. Place a trace message
before every return statement and at the end of the method:
public static int factorial(int n)
{ System.out.println("Entering factorial. n = " + n);
if (n < 0) throw new IllegalArgumentException();
if (n == 0)
322 Chapter 8. Testing and Debugging
{ System.out.println
("Exiting factorial. Return value = " + 1);
return 1;
}
else
{ int result = n * factorial(n-1);
System.out.println
("Leaving factorial. Return value = " + result);
return result;
}
}
You aren’t restricted to “enter/exit” messages. You can report on progress inside a
method:
public static double sqrt(double a)
{ . . .
do
{ xold = xnew;
xnew = (xold + a / xold) / 2;
System.out.println("MathAlgs.sqrt. xold = " + xold +
", xnew = " + xnew);
} while (!Numeric.approxEqual(xnew, xold));
. . .
}
Program traces can be useful to analyze the behavior of a program, but they have
some definite disadvantages. It can be quite time-consuming to find out which trace
messages to insert. If you insert too many messages, you produce a flurry of output
that is hard to analyze; if you insert too few, you may not have enough information to
spot the cause of the error. When you are done with the program, you need to remove
all trace messages. If you find another error, however, you need to stick the print
statements back in. If you find that a hassle, you are not alone. Most professional
programmers use a debugger, not trace messages, to locate errors in their code. The
debugger is covered later in this chapter.
An assumption that you believe to be true is called an assertion. Testing for asser-
tions is a powerful quality control mechanism. It is useful to build a special class for
testing assertions. Here is an example of such a class:
public class Assertion
{ public static void check(boolean b)
{ if (!b)
{ System.out.println("Assertion failed.");
// construct a Throwable object to get a stack trace
new Throwable().printStackTrace();
System.exit(1);
}
}
}
This particular implementation prints a stack trace, a printout of all pending methods,
if it finds the assertion has been violated. Here is a typical stack trace:
java.lang.Exception
at Assertion.check(Assertion.java:6)
at DentalRobot.computeIntersection(DentalRobot.java:37)
at RobotTest.testDrill(RobotTest.java:93)
at RobotTest.main(RobotTest.java:10)
The line just below Assertion.check indicates the culprit.
Here is a typical assertion check:
public void computeIntersection()
{ . . .
double y = r * r - (x - a) * (x - a);
Assertion.check(y >= 0);
. . .
root = Math.sqrt(y);
. . .
}
In this program excerpt, the programmer expects that the quantity y can never be
negative. When the assertion is correct, no harm is done, and the program works in
the normal way. If, for some reason, the assertion fails, then the programmer would
rather have the program terminate than go on, compute the square root of a negative
number, and cause greater harm later.
Assertions are different from trace messages in one important respect. You can
leave them in your code when testing is complete.
As you have undoubtedly realized by now, computer programs rarely run perfectly
the first time. At times, it can be quite frustrating to find the bugs. Of course, you
324 Chapter 8. Testing and Debugging
can insert trace messages to show the program flow as well as the values of key
variables, run the program, and try to analyze the printout. If the printout does not
clearly point to the problem, you may need to add and remove print commands and
run the program again. That can be a time-consuming process.
Modern development environments contain special programs, called debuggers,
that help you locate bugs by letting you follow the execution of a program. You
can stop and restart your program and see the contents of variables whenever your
program is temporarily stopped. At each stop, you have the choice of what variables
to inspect and how many program steps to run until the next stop.
Some people feel that debuggers are just a tool to make programmers lazy. Admit-
tedly some people write sloppy programs and then fix them up with the debugger,
but the majority of programmers make an honest effort to write the best program
they can before trying to run it through the debugger. These programmers realize
that the debugger, while more convenient than print statements, is not cost-free. It
does take time to set up and carry out an effective debugging session.
In actual practice, you cannot avoid using the debugger. The larger your programs
get, the harder it is to debug them simply by inserting print statements. You will
find that the time investment to learn about the debugger is amply repaid in your
programming career.
◆ The pioneering computer scientist Maurice Wilkes wrote: “Somehow, at the Moore School
◆ and afterwards, one had always assumed there would be no particular difficulty in getting
◆ programs right. I can remember the exact instant in time at which it dawned on me that a
◆ great part of my future life would be spent finding mistakes in my own programs.”
Figure 3
Figure 4
Inspecting Variables
8.6 The Debugger 327
able to reset the debugger, or you may need to exit the debugging program and start
over. Details depend on the particular debugger.
Program PrimeBug.java
public class PrimeBug // 1
{ /** // 2
Tests whether an integer is prime // 3
@param n any positive integer // 4
@return true iff n is a prime // 5
**/ // 6
// 7
public static boolean isPrime(int n) // 8
{ if (n == 2) return true; // 9
if (n % 2 == 0) return false; // 10
int k = 3; // 11
while (k * k < n) // 12
{ if (n % k == 0) return false; // 13
k = k + 2; // 14
} // 15
return true; // 16
} // 17
// 18
public static void main(String[] args) // 19
{ ConsoleReader console // 20
= new ConsoleReader(System.in); // 21
System.out.println // 22
("Please enter the upper bound:"); // 23
int n = console.readInt(); // 24
328 Chapter 8. Testing and Debugging
// 25
for (int i = 1; i <= n; i = i + 2) // 26
{ if (isPrime(i)) // 27
System.out.println(i); // 28
} // 29
} // 30
} // 31
We numbered the lines so that we can refer to them in this example. Probably your
debugger won’t actually number them.
When you run this program with an input of 10, then the output is
1
3
5
7
9
That is not very promising; it looks as if the program just prints all odd numbers. Let
us find out what it does wrong, by using the debugger. (Actually, for such a simple
program, it is easy to correct mistakes simply by looking at the faulty output and the
program code. However, we want to learn to use the debugger.)
Let us first go to line 27. On the way, the program will stop to read the input into
n. Supply the input value 10.
int n = console.readInt(); // 24
// 25
for (int i = 1; i <= n; i = i + 2) // 26
{ if (isPrime(i)) // 27
System.out.println(i); // 28
} // 29
Now we wonder why the program treats 1 as a prime. Go to line 9.
public static boolean isPrime(int n) // 8
{ if (n == 2) return true; // 9
if (n % 2 == 0) return false; // 10
int k = 3; // 11
while (k * k < n) // 12
{ if (n % k == 0) return false; // 13
k = k + 2; // 14
} // 15
return true; // 16
} // 17
Convince yourself that the parameter n of isPrime is currently 1, by inspecting its
value in the debugger. Then execute the “run to next line” command. You will notice
that the program goes to lines 10 and 11 and then directly to line 16.
public static boolean isPrime(int n) // 8
{ if (n == 2) return true; // 9
if (n % 2 == 0) return false; // 10
8.6 The Debugger 329
int k = 3; // 11
while (k * k < n) // 12
{ if (n % k == 0) return false; // 13
k = k + 2; // 14
} // 15
return true; // 16
} // 17
Inspect the value of k. It is 3, and therefore the while loop was never entered. It
looks as though the isPrime method needs to be rewritten to treat 1 as a special
case.
Next, we would like to know why the program doesn’t print 2 as a prime even
though the isPrime method does recognize that 2 is a prime, whereas all other even
numbers are not. Go again to line 9, the next call of isPrime. Inspect n; you will note
that n is 3. Now it becomes clear: The for loop in main tests only odd numbers. The
main should either test both odd and even numbers or, better, just handle 2 as a
special case.
Finally, we would like to find out why the program believes 9 is a prime. Go again
to line 9 and inspect n; it should be 5. Repeat that step twice until n is 9. (With some
debuggers, you may need to go from line 9 to line 10 before you can go back to
line 9.) Now use the “run to next line” command repeatedly. You will notice that the
program again skips past the while loop; inspect k to find out why. You will find that
k is 3. Look at the condition in the while loop. It tests whether k * k < n. Now k
* k is 9 and n is also 9, so the test fails. Actually, it does make sense to test divisors
only up to 冪n; if n has any divisors except 1 and itself, at least one of them must
be less than 冪n. However, actually that isn’t quite true; if n is a perfect square of
a prime, then its sole nontrivial divisor is equal to 冪n. That is exactly the case for
9 ⳱ 32 .
By running the debugger, we have now discovered three bugs in the program:
Program GoodPrime.java
if (n >= 2) System.out.println(2);
for (int i = 1; i <= n; i = i + 2)
{ if (isPrime(i))
System.out.println(i);
}
}
}
Is our program now free from bugs? That is not a question the debugger can answer.
Remember: Testing can show only the presence of bugs, not their absence.
When you “step over” method calls, you get to the next line:
String name = console.readLine();
System.out.println("Hello " + name + "! I'm glad to meet you.");
8.6 The Debugger 331
However, if you “step into” method calls, you enter the first line of the readLine
method.
public String readLine()
{ String inputLine = "";
. . .
}
You should step into a method to check whether it carries out its job correctly. You
should step over a method if you know it works correctly.
If you single-step past the last line of a method, either with the “step over” or the
“step into” command, you return to the line in which the method was called.
You should not step into system methods like println. It is easy to get lost in
them, and there is no benefit in stepping through system code. If you do get lost,
there are three ways out. You can just choose “step over” until you are finally again
in familiar territory. Many debuggers have a command “run until method return”
that executes to the end of the current method, and then you can select “step over”
to get out of the method. Finally, most debuggers can show you a call stack: a listing
of all currently pending method calls. On the one end of the call stack is main, on the
other the method that is currently executing (see Figure 5). Sometimes, the debug-
ger shows you multiple threads—you should ignore those threads that don’t have
the methods that you are debugging. By selecting another method in the middle
of the call stack, you can jump to the code line containing that method call. Then
move the cursor to the next line and choose “run until this line”. That way, you get
out of any nested morass of method calls.
The techniques you saw so far let you trace through the code in various incre-
ments. All debuggers support a second navigational approach: You can set so-called
breakpoints in the code. Breakpoints are set at specific code lines, with a command
“add breakpoint here”; again, the exact command depends on the debugger. You can
set as many breakpoints as you like. When the program reaches any one of them,
execution stops and the breakpoint that causes the stop is displayed. Breakpoints
stay active until you remove them.
Breakpoints are particularly useful when you know the point at which your pro-
gram starts doing the wrong thing. You can set a breakpoint, have the program run at
full speed to the breakpoint, and then start tracing slowly to observe the program’s
behavior.
Figure 5
Some debuggers let you set conditional breakpoints. A conditional breakpoint stops
the program only when a certain condition is met. You could stop at a particular line
only if a variable n has reached 0, or if that line has been executed for the twentieth
time. Conditional breakpoints are an advanced feature that can be indispensable in
knotty debugging problems.
Now you know about the mechanics of debugging, but all that knowledge may still
leave you helpless when you fire up the debugger to look at a sick program. There
are a number of strategies that you can use to recognize bugs and their causes.
goes in an infinite loop, or it crashes. Find out exactly how to reproduce that behavior.
What numbers did you enter? Where did you click with the mouse?
Run the program again; type in exactly the same answers and click with the mouse
on the same spots (or as close as you can get). Does the program exhibit the same
behavior? If so, then you are ready to fire up the debugger to study this particular
problem. Debuggers are good for analyzing particular failures. They aren’t terribly
useful for studying a program in general.
Chapter Summary
1. Use unit tests to test each key method in isolation. Write a test harness to feed
test data to the method being tested. Select test cases that cover each branch of the
method.
2. Use assertions to document assumptions that you are making in your programs.
336 Chapter 8. Testing and Debugging
3. You can debug a program by inserting trace printouts, but that gets quite tedious
for even moderately complex debugging situations. You should learn to use the de-
bugger.
4. You can make effective use of the debugger by mastering just three commands:
“run until this line”, “step to next line”, and “inspect variable”. The names and
keystrokes or mouse clicks for these commands differ between debuggers.
5. There are three windows in the debugger to which you should pay attention:
the window that shows your source code and, in particular, the currently executed
instruction; the window that shows the values of program variables; and the call
stack, which shows which method calls are currently “stacked up”.
7. The debugger can be used only to analyze the presence of bugs, not to show that
a program is bug-free.
Further Reading
[1] Nancy G. Leveson and Clark S. Turner, “An Investigation of the Therac-25 Accidents,” IEEE
Computer, July 1993, pp. 18–41.
java.lang.Throwable
printStackTrace
Review Exercises
Exercise R8.1. Define the terms unit test and test harness.
Exercise R8.2. If you want to test a program that is made up of four different meth-
ods, one of which is main, how many test harness programs do you need?
Exercise R8.4. Define the terms regression testing and test suite.
Exercise R8.5. What is the debugging phenomenon known as “cycling”? What can
you do to avoid it?
Review Exercises 337
Exercise R8.6. The arc sine function is the inverse of the sine function. That is,
y ⳱ arcsin x if x ⳱ sin y. It is defined only if ⫺1 ⱕ x ⱕ 1. Suppose you need to
write a Java method to compute the arc sine. List five positive test cases with their
expected return values and two negative test cases with their expected outcomes.
Exercise R8.7. What is a program trace? When does it make sense to use a program
trace, and when does it make more sense to use a debugger?
◆ Inspecting a variable
◆ Watching a variable
Exercise R8.11. What is a call stack display in the debugger? Give two debugging
scenarios in which the call stack display is useful.
Exercise R8.13. Explain in detail how to inspect the string stored in a String object
in your debugger.
Exercise R8.14. Explain in detail how to use your debugger to inspect a string stored
in a BankAccount object.
◆ If a program has passed all tests in the test suite, it has no more bugs.
◆ If a program has a bug, that bug always shows up when running the program
through the debugger.
◆ If all methods in a program were proven correct, then the program has no
bugs.
338 Chapter 8. Testing and Debugging
Programming Exercises
Exercise P8.1. The arc sine function is the inverse of the sine function. That is,
y ⳱ arcsin x
if
x ⳱ sin y
For example,
arcsin(0) ⳱ 0
arcsin(0.5) ⳱ /6
arcsin( 冪2/2) ⳱ /4
arcsin( 冪3/2) ⳱ /3
arcsin(1) ⳱ /2
arcsin(⫺1) ⳱ /2
The arc sine is defined only for values between ⫺1 and 1. This function is also often
called sin⫺1 x. Note, however, that it is not at all the same as 1/ sin x. There is a
Java standard library method to compute the arc sine, but you should not use it for
this exercise. Write a Java method that computes the arc sine from its Taylor series
expansion
You should compute the sum until a new term is ⬍ 10⫺6 . This method will be used
in subsequent exercises.
Exercise P8.2. Write a simple test harness for the arcsin method that reads floating-
point numbers from standard input and computes their arc sines, until the end of the
input is reached. Then run that program and verify its outputs against the arc sine
function of a scientific calculator.
Exercise P8.3. Write a test harness that automatically generates test cases for the
arcsin method, namely numbers between ⫺1 and 1 in a step size of 0.1.
Exercise P8.4. Write a test harness that generates 10 random floating-point numbers
between ⫺1 and 1 and feeds them to arcsin.
Exercise P8.5. Write a test harness that automatically tests the validity of the
arcsin method by verifying that Math.sin(arcsin(x)) is approximately equal to
x. Test with 100 random inputs.
Programming Exercises 339
Exercise P8.6. The arc sine function can be computed from the arc tangent function,
according to the formula
冢
arcsin x ⳱ arctan x/冪1 ⫺ x 2 冣
Use that expression as an oracle to test that your arc sine method works correctly.
Test your method with 100 random inputs and verify against the oracle.
Exercise P8.7. The domain of the arc sine function is ⫺1 ⱕ x ⱕ 1. Supply an ex-
ception to your arcsin method that ensures that the input is valid. Test your method
by computing arcsin(1.1). What happens?
Exercise P8.8. Place trace messages into the loop of the arc sine method that com-
putes the power series. Print the value of n, the value of the current term, and the
current approximation to the result. What trace output do you get when you com-
pute arcsin(0.5)?
Exercise P8.9. Add trace messages to the beginning and end of the isPrime method
in the buggy prime program. Also put a trace message as the first statement of the
while loop in the isPrime method. Print relevant values such as method parameters,
return values, and loop counters. What trace do you get when you compute all primes
up to 20? Are the messages informative enough to spot the bug?
Exercise P8.10. Run a test harness of the arcsin method through the debugger.
Step inside the computation of arcsin(0.5). Step through the computation until
the x 7 term has been computed and added to the sum. What is the value of the
current term and of the sum at this point?
Exercise P8.11. Run a test harness of the arcsin method through the debugger.
Step inside the computation of arcsin(0.5). Step through the computation until
the x n term has become smaller than 10⫺6 . Then inspect n. How large is it?
Create a series of test cases to flush out the bug. Then run a debugging session to
find it. Run the debugger to the line in which the bug manifests itself. What are the
values of all local variables at that point?
Exercise P8.13. Write a program that tests the recursive factorial method from
Chapter 7. Compute factorial(6). Step inside recursive calls until you arrive at
factorial(3). Then display the call stack. Which calls are currently pending?
Exercise P8.14. Find all bugs in the following version of a factorial method. De-
scribe how you found the bugs.
class Numeric
{ public int factorial(int n)
{ int p = 1;
int i = n;
return i;
}
}