Chapter 10

Testing and Debugging


Chapter Goals

Unit Tests

Example: Setting Up Test Harnesses

File RootApproximator.java

File Numeric.java

File RootApproximatorTester.java

Testing the Program

Self Check

  1. What is the advantage of unit testing?
  2. Why should a test harness be repeatable?

Answers

  1. It is easier to test methods and classes in isolation than it is to understand failures in a complex program.
  2. It should be easy and painless to repeat a test after fixing a bug.

Providing Test Input

File RootApproximatorHarness1.java


Output
   square root of 100.0 = 10.0
square root of 4.0 = 2.0
square root of 2.0 = 1.414213562373095
square root of 1.0 = 1.0
square root of 0.25 = 0.5
square root of 0.01 = 0.1

Providing Test Input

File RootApproximatorHarness2.java


Output
   square root of 1.0 = 1.0
square root of 1.5 = 1.224744871391589
square root of 2.0 = 1.414213562373095
. . .
square root of 9.0 = 3.0
square root of 9.5 = 3.0822070014844885
square root of 10.0 = 3.162277660168379

Providing Test Input

File RootApproximatorHarness3.java


Output
   square root of 810.4079626570873 = 28.467665212607223
square root of 480.50291114306344 = 21.9203766195534
square root of 643.5463246844379 = 25.36821485017103
square root of 506.5708496713842 = 22.507128863348704
square root of 539.6401504334708 = 23.230156057019308
square root of 795.0220214851004 = 28.196134867834285
. . .

Providing Test Input

Reading Test Inputs from a File

File RootApproximatorHarness4.java

Reading Test Inputs from a File

Self Check

  1. How can you repeat a unit test without having to retype input values?
  2. Why is it important to test boundary cases?

Answers

  1. By putting the values in a file, or by generating them programmatically.
  2. Programmers commonly make mistakes when dealing with boundary conditions.

Test Case Evaluation

File RootApproximatorHarness5.java

Output

Test passed: x = 913.6505141736327, root squared = 913.6505141736328
Test passed: x = 810.4959723987972, root squared = 810.4959723987972
Test passed: x = 503.84630929985883, root squared = 503.8463092998589
Test passed: x = 115.4885096006315, root squared = 115.48850960063153
Test passed: x = 384.973238438713, root squared = 384.973238438713
. . .
Pass: 100
Fail: 0

File RootApproximatorHarness6.java

Output

Test passed: square root = 718.3849112194539, oracle = 718.3849112194538
Test passed: square root = 641.2739466673618, oracle = 641.2739466673619
Test passed: square root = 896.3559528159169, oracle = 896.3559528159169
Test passed: square root = 591.4264541724909, oracle = 591.4264541724909
Test passed: square root = 721.029957736384, oracle = 721.029957736384
. . .
Pass: 100
Fail: 0

Self Check

  1. Your task is to test a class that computes sales taxes for an Internet shopping site. Can you use an oracle?
  2. Your task is to test a method that computes the area of an arbitrary polygon. Which polygons with known areas can you use as test inputs?

Answers

  1. Probably not–there is no easily accessible but slow mechanism to compute sales taxes. You will probably need to verify the calculations by hand.
  2. There are well-known formulas for the areas of triangles, rectangles, and regular n-gons.

Regression Testing

Test Coverage

Self Check

  1. Suppose you modified the code for a method. Why do you want to repeat tests that already passed with the previous version of the code?
  2. Suppose a customer of your program finds an error. What action should you take beyond fixing the error?

Answers

  1. It is possible to introduce errors when modifying code.
  2. Add a test case to the test suite that verifies that the error is fixed.

Unit Testing with JUnit

Program Trace

Logging

Logging

Self Check

  1. Should logging be activated during testing or when a program is used by its customers?
  2. Why is it better to send trace messages to Logger.global than to System.out?

Answers

  1. Logging messages report on the internal workings of your program–your customers would not want to see them. They are intended for testing only.
  2. It is easy to deactivate Logger.global when you no longer want to see the trace messages, and to reactivate it when you need to see them again.

Using a Debugger

The Debugger Stopping at a Breakpoint

Inspecting Variables

Debugging

Single-step Example

Self Check

  1. In the debugger, you are reaching a call to System.out.println. Should you step into the method or step over it?
  2. In the debugger, you are reaching the beginning of a long method with a couple of loops inside. You want to find out the return value that is computed at the end of the method. Should you set a breakpoint, or should you step through the method?

Answers

  1. You should step over it because you are not interested in debugging the internals of the println method.
  2. You should set a breakpoint. Stepping through loops can be tedious.

Sample Debugging Session

File Word.java

File WordTester.java

Debug the Program

More Problems Found

Debugging the Word Constructor

Debugging the Word Constructor

Another Error

Debugging countSyllables (again)

Fixing the Bug

Self Check

  1. What caused the first error that was found in this debugging session?
  2. What caused the second error? How was it detected?

Answers

  1. The programmer misunderstood the second parameter of the substring method–it is the index of the first character not to be included in the substring.
  2. The second error was caused by failing to reset insideVowelGroup to false at the end of a vowel group. It was detected by tracing through the loop and noticing that the loop didn't enter the conditional statement that increments the vowel count.

The First Bug


Therac-25 Facility