Chapter 6
|
Chapter 5 expanded the notion of collections to include maps. Important topics discussed were information hiding and documentation. Static variables and constants were also examined. Chapter 6 does not introduce new Java syntax but important ideas about programming: testing small units, automating test, regression testing, manual testing and debugging.
The chapter is concerned with testing and debugging software. Up to this point, we have have approached testing in an ad hoc fashion, now we we attempt to develop a more formal approach.
testing - Determining whether a segment of code contains errors.
debugging - Correcting errors exposed through testing.
| Unit testing - Testing individual parts of an application ranging from a class to individual methods. |
| Application testing - Testing the complete application. |
Exercise 1 - diary-prototype project In BlueJ
- Open project chapter06\diary-prototype
- Select Day class.
- Click Tools.
- Project Documentation.
- What are the stated values of the dayNumber parameter to the Day constructor?
- Would you expect it to work for those values?
- What about other values?
- Construct a Day object.
- Are you testing a unit or the complete diary application?
- Construct another Day object using dayNumber 367.
- 367 exceeds the stated Day constructor boundary condition. Did the constructor fail?
- Do you think something will eventually fail later?
- Is it better to fail if the stated operating conditions are violated or to give a possibly bogus response?
- What is another boundary condition?
Exercise 1.5 - Inspectors In BlueJ
- Construct a Day object.
- Inspect the Day object to determine if enough space is available for storing hourly appointments that start at 9am and run until 6pm?
- Construct three Appointment objects of 1 hour duration.
- Make appointments for 9am, 1pm and 5pm.
- Inspect the Day object to verify that the appointments occupy the correct positions.
- What is the Day class makeAppointment method boundary condition? Test it.
- Test whether two appointments can be scheduled at the same time.
- Have you just performed unit or application testing?
| positive testing - Testing cases that are expected to succeed. |
| negative testing - Testing cases that are expected to fail. |
Exercise 2 - Positive and negative testing In BlueJ
- Construct one Appointment object of 10 hour duration and construct a Day object.
- Make an appointment starting at 9am.
- What did your test indicate?
- Did the makeAppointment method work as specified?
- What were the makeAppointment method specifications?
- Have you just performed positive or negative testing?
- Can you think of another, similar test for the makeAppointment method?
| test automation - Test using a program (testing harness) rather than manually. Favored because repeatable, easier and more reliable than manual testing or inspection. |
| regression testing - Because corrections or other changes can introduce errors, earlier tests must be performed again. |
The following class automates tests for Exercise 3.
public class Exercise3Tests
{
public Exercise3Tests()
{ }
public void test()
{
Day day4 = new Day( 4 );day4.makeAppointment( 9, new Appointment("Sleep all day", 10) );
}
}
Exercise 3 - Test automation In BlueJay
- Create a new class Exercise3Tests in the diary-prototype project.
- Copy and paste the above class.
- The class diagram should appear as at right.
- Create an Exercise3Tests object.
- Call method test().
- Did the automated test produce the same results as the manual tests?
- Is this a good test to automate?
- Change the test() method to:
- Construct a Day object.
- Construct three Appointment objects of 1 hour duration.
- Make appointments for 9am, 1pm and 5pm.
- Test that appointments are placed at the correct times using the Day class showAppointments() method.
- Test whether two appointments can be scheduled at the same time.
Exercise 4 - Automation of tests In BlueJ
- Change the test() method of Exercise 3 to return a boolean type.
- The result returned should be the conjunction (&&) of the appointments for 9am, 1pm and 5pm.
- Note that makeAppointment returns false if the appointment is not made.
- Combine the results of the three makeAppointment calls using &&.
- Call your test() method.
- Change one of the appointments to 6pm and call your test() method again.
- Is this a positive or negative test?
- In this case all positive tests return true to correctly indicate success. Results can be combined using and (&&). All negative tests return false to correctly indicate failure. How would we combine test results to indicate the failure of a set of negative tests?
- Can all method tests easily be performed automatically?
- If not, give an example of a method signature that cannot be easily tested directly.
| interface - The point of connection between two modules, generally through methods. |
A calculator can be divided into two parts:
- user interface for the buttons and display
- calculation engine for the computation
The interface to a simplified calculation engine consists of methods with the signatures of:
- public void plus();
- public void minus();
- public void equals();
- public void clear();
- public int getDisplayValue();
- public void numberPressed(int number);
Because the calculation engine is separate from the the user interface, it can be implemented and fully tested independently of the user interface.
Exercise 5 - Modularization and interfaces In BlueJ
- Open project chapter06\calculator-engine
- Create a CalcEngineTester object.
- Call the testAll() method.
- Do you trust the printed results?
- Call the testAll() method again.
- Do you now trust the printed results?
- Create a CalcEngine object.
Call methods simulating: 3 + 4 =
- numberPressed(3)
- plus()
- numberPressed(4)
- equals()
- getDisplayValue();
- Create a CalcEngineTester object.
testplus() tests the plus() method for: 3 + 4 =
- Call the testPlus() method at least twice.
- Do you trust the results?
- The method at right tests the plus() method.
- Change to test 32 + 4 =
- Create a CalcEngineTester object.
- Call the testPlus() method at least twice.
- Do you trust the results?
public int testPlus()
{
// Make sure the engine is in a valid starting state.
engine.clear();
// Simulate the presses: 3 + 4 =
engine.numberPressed(3);
engine.plus();
engine.numberPressed(4);
engine.equals();
// Return the result, which should be 7.
return engine.getDisplayValue();
}
Exercise 6 - Modularization and interfaces In BlueJ
- The method at right tests the minus() method.
- Change to test 32 - 4 =
- Create a CalcEngineTester object.
- Call the testMinus() method at least twice.
- Do you trust the results of minus()?
public int testMinus()
{
// Make sure the engine is in a valid starting state.
engine.clear();
// Simulate the presses: 9 - 4 =
engine.numberPressed(9);
engine.minus();
engine.numberPressed(4);
engine.equals();
// Return the result, which should be 5.
return engine.getDisplayValue();
}
The TicTacToe class of Homework 6 uses your TTTEngine to manage the game. Well-behaved classes with well-defined method interfaces such as your TTTEngine allow the class can be used without changes. As an example, the text-based interface class TicTacToe can be replaced by a GUI interface without changes to your TTTEngine.
Exercise 6.1 - Well-behaved classes In BlueJ
- Open your HW6 project.
- Create a TicTacToe object and execute game() method.
- Remove the TicTacToe object.
- Create a GUITicTacToe object and execute the start() method.
Because your TTTEngine had well-defined method interfaces, either TicTacToe or GUITicTacToe can use the class.
Exercise 7 - Commenting and style In BlueJ
- Open CalcEngine class.
- Do the comments seem sufficient to explain the class and method use?
- Is the source code layout reasonably consistent to the eye?
- Indentation.
- Use of { and }.
- Commenting consistent.
- Click the Implementation button and change to Interface to display the class documentation from the comments.
- Is the documentation sufficient to use the CalcEngine class without reading the source code?
Public manual walkthroughs share program analysis with others. Though time consuming, it is an important tool for analyzing code that fails for undetermined reasons or for code that cannot be live tested (e.g. controlling an emergency in a nuclear reactor).
Essentially the code in question is discussed and hand executed. You may have already done this with others on homework assignments then you know that it helps.
Exercise 8 - Manual walkthroughs to find errors In BlueJ
- Create a CalcEngineTester object.
- Call the testMinus() method at least twice. Note that the results differ between the first and second calls.
- Knowing that the first call works correctly with input of:
- 9 - 4 =
Let's assume that we've determined that after the first call the CalcEngine object has the following state:
- previousOperator = '-'
leftOperand = 0
displayValue = 5The second call fails with input of:
- 9 - 4 =
When minus() is called the second time the input so far to cause minus() to be called has been: 9 -.
The state is now:
- previousOperator = '-'
leftOperand = 0
displayValue = 9All appears well. Now to start our manual walkthrough of the CalcEngine class:
- Line 3 calls applyPreviousOperator() and previousOperator = '-'.
- Because previousOperator = '-' line 20 is executed and performs:
- leftOperand -= displayValue
which is:
- leftOperand = leftOperand - displayValue or 0 - 9 = -9
- When the number 4 and '=' is entered, the equals() method at line 7 is called. The object state is:
- previousOperator = '-'
leftOperand = -9
displayValue = 4
and the statement executed is line 12:
displayValue = leftOperand - displayValue or -9 - 4 = -13
- We found the problem.
- leftOperand = -9
when it should be:
- leftOperand = 9
Retracing our steps (something a debugger normally doesn't do) we may notice applyPreviousOperator() with previousOperator = '-' is the trouble point, in line 20. In fact line 22 should have been executed instead:
- leftOperand = displayValue = 9
- We may realize that the real problem is that after the '=' was entered in 9 - 4 =, previousOperator = ' ' rather than previousOperator = '-'.
Adding the following after line 13 solves the problem:
previousOperator = ' '
- public void minus()
- {
- applyPreviousOperator();
- previousOperator = '-';
- displayValue = 0;
- }
- public void equals()
- {
- if(previousOperator == '+')
- displayValue = leftOperand + displayValue;
- else
- displayValue = leftOperand - displayValue;
- leftOperand = 0;
- }
- private void applyPreviousOperator()
- {
- if(previousOperator == '+')
- leftOperand += displayValue;
- else if(previousOperator == '-')
- leftOperand -= displayValue;
- else
- leftOperand = displayValue;
- }
Printing the state of an object (fields), parameters and local variables as a program executes has several advantages over debugging and walkthroughs, primarily that reporting of test results can be integrated as a part of the class definition and turned on for testing and turned off when testing is complete.
The following includes printing of all fields on entry and exit of the minus() method. The results of calling testMinus() twice is listed below.
- public class CalcEngine
- {
- private int displayValue;
- private char previousOperator;
- private int leftOperand;
- public void minus()
- {
- printState( "minus() entry");
- applyPreviousOperator();
- previousOperator = '-';
- displayValue = 0;
- printState( "minus() exit" );
- }
- private void printState( String where )
- {
- System.out.println(where+": previousOperator="+previousOperator+
- " displayValue="+displayValue+" leftOperand="+leftOperand);
- }
- }
minus() entry: previousOperator= displayValue=9 leftOperand=0
minus() exit: previousOperator=- displayValue=0 leftOperand=9
minus() entry: previousOperator=- displayValue=9 leftOperand=0
minus() exit: previousOperator=- displayValue=0 leftOperand=-9
Software development ideally is a cycle of:
- Analysis to determine an appropriate solution
- Designing a solution
- Coding the solution
- Testing to verify correctness
- Use of production system
- Maintenance by modifying, correcting, improving.
Testing and maintenance both benefit from printing debugging information while the program is running. In the production system, such debugging information should be turned off. Because most successful systems move often between testing, production and maintenance, debugging should be easily turned on and off.
- The following uses the value of the field debugging=true to turn printing On.
- Change debugging=false to turn printing Off.
- public class CalcEngine
- {
- private int displayValue;
- private char previousOperator;
- private int leftOperand;
- private boolean debugging = true;
- public void minus()
- {
- printState( "minus() entry");
- applyPreviousOperator();
- previousOperator = '-';
- displayValue = 0;
- printState( "minus() exit" );
- }
- private void printState( String where )
- {
- if ( debugging )
- System.out.println(where+": previousOperator="+previousOperator+
- " displayValue="+displayValue+" leftOperand="+leftOperand);
- }
- }