Hands on Testing Java: Lab #5

Java Control Structures

Introduction

Today's lab explores some Java statements that you can use to write more sophisticated methods. You'll work on a problem whose solution requires more complicated behaviors than we have seen thus far, and you'll see the built-in statements of Java that provide these behaviors.

First, a word about naming convensions in this manual: often I will refer to a method by its name using parentheses, but ignoring its parameters. For example, I'll often refer to the method main() even though this method does have a String[] parameter named args. If there's an ambiguity or if I need to highlight the parameter, then you may find a name or data type in the method name; otherwise, the parenthese are there just to make it clear that I'm talking about a method.

The Tax Computation Problem

Our problem in this lab exercise is to write an interactive tax computation program that the owner of small online business might use to compute the cost of items being sold to buyers in states with different sales tax. To make the problem more interesting, we will assume that an additional national 1% luxury tax has been enacted for any purchase over one hundred dollars.

Recall that object-centered design involves several steps, including instructions on how and when to create new methods:

  1. Identify the behavior we wish the program to exhibit in order to solve the problem.
  2. Identify the objects in that behavior.
  3. Identify the operations in that behavior.
    1. If there is no predefined way to perform an operation, then build a method to perform it; store the method in the class responsible for providing the behavior.
  4. Organize the operations and objects into an algorithm.

Each method (including the main program) gets its own OCD.

Preparing Your Workspace

Do this...

Analysis

As always, spending a bit of time planning how to attack our problem will result in a better solution. Let's begin by visualizing and writing down how the driver program might behave. Here's one option:

This program computes the cost of individual items given a price and a
sales tax.

Enter the number of items: 3

Enter the item name, cost and tax rate for item 1: kleenex 25 5.25
The cost of kleenex is $26.31 with tax.

Enter the item name, cost and tax rate for item 2: eggs 1.50 6.15
The cost of eggs is $1.59 with tax.

Enter the item name, cost and tax rate for item 3: tires 350 6.15
The cost of tires is $374.03 with tax.

Consider the first item: "kleenex" costs $25 (must be a really big box), and it's sold with a 5.25% sales tax. Its total cost will be its original cost (25) plus the sales tax (25*0.0525), which comes to a total of 26.3125 (i.e., 25 + 25*0.0525).

The cost of the "eggs" is computed the same way.

Since the "tires" cost more than $100, a 1% luxury tax on the amount above $100 is added in addition to the normal sales tax.

Design of the Driver

Behavior

Put into words, the main program (i.e., the driver) should do the following:

Behavior of driver

The program should display on the screen a greeting, and then prompt for and read in the number of items to process. Then our program should read in and process each item.

The responsibility of this driver is to handle the top-most details: discover how many items, and process each one. The details of the processing will be left to another method.

Objects

If we identify the nouns in this behavioral description, we get the following list:

Objects
Description Type Kind Name
the screen Screen varying theScreen
A greeting String literal --
A prompt for input String literal --
The number of items int varying numberOfItems
The number of the current item int varying (loop index) itemNum
The keyboard Keyboard varying theKeyboard

From this list, we can build a precise specification of how our program is to behave:

Specification:

input (keyboard): The number of items.
output (screen): The result of processing each item.

For a computation method, the specification revolves around the parameters and return value. For a driver (which is what you have here), the specification centers on the input and output of the program.

Do this...
Write a main() driver method in the TaxDriver class, and write the Javadoc for main() using the information in this design.

Operations

When you identify the operations in our behavioral description, you get this list:

Operations
Description Predefined? Name Library
Display a String (greeting, prompts, labels) yes print ann.easyio
Read an int from the keyboard yes readInt ann.easyio
Process an item no processItem(num) same class
repeat processing for each item yes for loop built-in

The I/O operations are provided for us by the ann.easyio package; readInt() is fairly straightforward. You should note that really bad things happen when your program tries to read in an int (or any particular data type) but finds a word (or otherwise mismatching data type) instead. For now, you don't have to worry about errors in the user's input.

There is no predefined Java built-in or method to process an item.

Do this...
Look at the operations entry for the "process an item" operation.

Presently it's undefined, but even while writing this driver method, you can choose a name and a library. Since this operation is so closely tied with this driver, you might as well put it in the same class (i.e., TaxDriver). And you can give it a name, Since the message printed when processing an item contains the number of the item (e.g., "item 1"), the processItem() method will need the number of the current item; so we even know that it takes one argument.

The last operation is a new Java statement called the for statement which repeats a group of statements a specified number of times. We'll deal with this statement when we write the code; for now, think of it as counting on your fingers.

Algorithm

Even without knowing the details of how you will process an item, an algorithm can be formed from the operations.

Algorithm
  1. Display a greeting on theScreen plus a prompt for the number of items.
  2. From theKeyboard, read an integer, storing it in numberOfItems.
  3. For each value itemNum in the range 1 to numberOfItems:
    1. Process item #itemNum.
    End loop.

Testing the Driver

Testing a driver with JUnit is tricky at best. Let's settle on testing the computation method with a JUnit test case, but the driver you'll have to test by hand. It's not a tricky driver, so this should be okay.

Coding the Driver

You should have this code already in your editor (and some Javadoc comments):

public class TaxDriver {
  public static void main(String[] args) {
  }
}

Do this...
To make sure that this much is correct before you add to it, take a moment to compile your code.

Taking a quick look at the list of objects, you will notice that we need theKeyboard and theScreen. We can define them when we need them (e.g., theKeyboard isn't needed until Step #2, but it's often helpful to declare I/O variables as early as possible. Generally we will not include declarations for our I/O variables in our algorithms, but they're very necessary in the code.

Do this...
Declare and initialize theKeyboard and theScreen at the beginning of main(). This will mean importing the proper libraries at the top of the file.

Let's got through the algorithm for the driver step by step. Have the algorithm handy.

Step #1. This step can be performed using a Java output statement, which we have seen before:

theScreen.print( Value1 + Value2 + ... + ValueN );
theScreen.println( Value1 + Value2 + ... + ValueN );

Each ValueI is some Java object (mostly String literals and some variables). You can use either print() or println(); the latter adds a newline automatically to the output.

Do this...
Add an output statement to main() that displays the following message:

This program computes the cost of individual items given a price and a
sales tax.

Enter the number of items:

Then compile your program, and run it. You should see this output, and the driver ends.

Step 2. The second step is an input expression:

theKeyboard.readInt()

This is an expression that reads in an integer from the keyboard. Remember that expressions are incomplete thoughts (the missing semi-colon is deliberate, not a mistake!). Step #2 of the algorithm says to read in numberOfItems from the keyboard. So the complete statement is a declaration for numberOfItems which uses the input expression above for initialization.

Do this...
Write the resulting declaration statement. Again, compile and run your program to make sure it's okay so far.

As long as it executes without errors, you're fine; the following statement will actually do something with the value.

Step 3. The primary purpose of Step 3 is to count from 1 to numberOfItems, and repeat another statement (i.e., process an item) each time.

For situations like this that require repetitive behavior based on counting, Java supplies the for statement. The for statement can use its own local variable, called a loop-control variable, to do the counting.

A simplified general form of a counting for loop that counts from firstValue to lastValue looks like this:

counting-for-loop pattern
for (int loopVar = firstValue; loopVar <= lastValue; loopVar++) {
  bodyStatements
}
  • loopVar is the loop-control variable.
  • firstValue and lastValue are the beginning and ending values for loopVar, respectively and inclusively.
  • bodyStatements is the list of statements that are executed repeatedly.
  • Execution: bodyStatements is repeatedly executed with loopVar set to different values between firstValue and lastValue, inclusive and in increasing order.
  • Warning: there's no semi-colon before the opening curly brace!

The bodyStatements are usually just called the body of the loop.

The behavior of this statement is as follows:

Algorithm
  1. loopVar is declared and initialized tofirstValue.
  2. If loopVar is less than or equal to lastValue.
    1. Statements get executed.
    2. loopVar++ is executed.
    3. Restart this step.
    Otherwise
    1. The loop stops, and control goes to the statement after the loop.

So how does the pattern match up with our algorithm? numberOfItems indicates how many items we have, assuming we start counting from 1. So, firstValue is 1, and lastValue is numberOfItems. The bodyStatements will take care of themselves in just a bit. So the only choice now is loopVar, but even that is covered in the object list by itemNum.

Do this...
Using the pattern above, write the counting for loop for Step #3. Compile and run the program; you won't see any visible evidence yet that the loop is running, but you shouldn't get any errors.

If the user enters a negative value for numberOfItems, the body of the loop will not be executed. itemNum starts at 1, which is already greater than any negative number. So the test will be false immediately, making any explicit checking for a negative number unnecessary (unless you wanted to print an error message).

If your program never quits, you're in an infinite loop---a loop that runs forever and ever. Generally this is due to a syntax error or because the initialization or test are done wrong. You'll have to forcibly stop the program.

As for the body of the loop, it's just a single statement that invokes processItem(). The method does require one arugment: the number of the current item; the object list tells you where this number is stored. This method is the next method that you work on, so as long as everything has compiled and run fine up to this point, adding in a call to this method is fine. The compiler will complain, and fixing that is your next step.

Do this...
Add a call to processItem() as the body of the loop.

Incidentally, processItem(), it was decided above, will be declared in the same class as the driver, TaxDriver. Be sure to pass in itemNum as an argument.

Design of the Item Processing Method

That finishes up your work on the main() method. Now it's time to turn to the processItem() method (which is the one compilation error you should have right now).

Behavior

To process an item involves this behavior:

Behavior of TaxDriver#processItem()

The method first receives the number of the current item. For this item, the method displays a prompt for the item name, the item cost, and the tax rate. Then, these three pieces of information are read in from the keyboard. The total cost of the item is computed, and a friendly message is printed reporting the total cost.

Objects

Objects of TaxDriver#processItem()
Description Type Kind Name
the number of the current item int paramater itemNum
the screen Screen varying theScreen
A prompt for input String literal --
The keyboard Keyboard varying theKeyboard
An item's name String varying name
An item's cost double varying cost
An item's tax rate double varying taxRate
An item's cost double varying totalCost

From these objects, you get this specification:

Specification of TaxDriver#processItem():

receive: itemNum, the number of the current item.
input (keyboard): The item's name, cost, and tax rate.
output (screen): The item's name and total cost.

Technically, this method is an extension of the driver (as opposed to being a computation method). So the specification is mostly in terms of input and output. The method does receive one value which the driver itself sets up.

Do this...
Write a processItem() stub in the TaxDriver class, and write the Javadoc for the method. You'll need to remember how to specifiy a "no return type".

Operations

If we identify the operations in our behavioral description, we get this list:

Operations of TaxDriver#processItem()
Description Predefined? Name Library
Display anything yes print ann.easyio
read a name (i.e., a "word") from the keyboard yes readWord ann.easyio
read a double from the keyboard yes readDouble ann.easyio
compute an item's totalCost no computeTotalCost() Tax

Do this...
Make careful note how you're going to read in an item's name.

The following distinctions matter only during input. No matter how you read in the name, it will always be a String.

No matter how you try to solve this problem, it will be disappointing. Input is very difficult, especially from the keyboard on a console. It is easiest to just require your users to enter one single word for the name of an item, and so consider the name of an item to be a "word".

There is no predefined Java capability to compute totalCost, so this will lead us to another OCD and another method. Since the responsibility of this other method will be computation (not input or output), you'll put it in another class (the current class's responsibility is input and output).

Algorithm

Algorithm of TaxDriver#processItem()
  1. Receive itemNum.
  2. Display a prompt for the name, cost and tax rate of item #itemNum.
  3. From theKeyboard, read in name, cost, and taxRate.
  4. Compute totalCost, using cost and taxRate.
  5. Display name and totalCost.

Coding the Processing-Item Method

Do this...
Make sure you have a stub method for processItem(), enough to make the compiler happy.

You will notice that you need theKeyboard and theScreen again. Because of a variable's scope, the previous declarations in main() do not work in this new method. As soon as the compiler reaches the end of main(), those declarations are inactive. So these variables must be redeclared in this new method; using the same names is perfectly fine since you have a brand new scope.

Do this...
Declare and initialize theKeyboard and theScreen at the beginning of processItem().

And now for coding up the algorithm of processItem():

Step #1. As with any method, receiving a value is already done when you declare your parameters. This method receives itemNum; you should have a parameter itemNum.

Step #2. This is a normal output statement, like those we have seen before. One trick to the output is that we want to include the number of the item, like so:

Enter the name, cost and tax rate for item 22:

This is the output you should get when itemNum is 22.

Do this...
Add an output statement that prints out the appropriate statement. Compile and run your program; you should see some visible evidence that the loop is executing. Run your driver several times; even try a negative number.

Step #3. Once the output statement works, you need to read in some values as this next step. Since we need to read in three values, this step will actually take three statements.

The statements themselves are similar to the input statement in main(), except for the method you invoke on theKeyboard. It'll go easiest for you if you heeded the hint just after the operations list.

Do this...
Add three statements that declare and initialize the appropriate variables with values read in from the keyboard. Compile and run your program.

Anytime you code up an input operation, you should consider the possibility of human error: what could the user do to foul up your program? There are several possibilities here:

  1. The user could enter a non-numeric value for cost.
  2. The user could enter a negative value for cost.
  3. The user could enter a non-numeric value for taxRate.
  4. The user could enter a negative value for taxRate.

These are tricky problems. The non-numeric problems are handled somewhat by the readWhatever() methods of the Keyboard object; eventually we will look at how you can process the complaints from these methods. For now, do not worry about non-numeric input for numeric variables.

This leaves the problem of negative values. You could test for negative values, and there are good reasons to test for them here. However, this is a responsibility we'll save for the computation method.

Step #4. As noted before, the total-cost operation is not predefined, and so we will design and build a method to perform it. At this point we can at least note that we could call the method computeTotalCost(). It'll need the cost of item and it's tax rate, so you'll have to pass them in as arguments; assume that the cost is listed before the rate. Since this method will live in the Tax class, you have all you need to write the method invocation.

Do this...
Write a declaration statement for totalCost that initializes it to be the result of calling computeTotalCost() of the Tax class. You'll have to pass the cost and the tax as arguments to this method. Wait on the resulting compiler error.

Step #5. The last step is easily implemented using an output statement.

Do this...
Add an output statement that would print this given the proper inputs:

The cost of milk is $1.784332 with tax.

This completes the coding of the driver method. You now have to write the code for the computation method.

Design of the Total-Cost Method

To compute totalCost for an arbitrary item, our method needs the cost and taxRate. Since these values will differ from item to item, our method should receive these values from its caller. Remember to keep the responsibilities straight: the methods in TaxDriver do the input and output. You're now completely done with input and output! Everything else is done through paramaters and returned values.

Behavior

Behavior of computeTotalCost()

Our method should receive the cost and tax rate from its caller.

  • If the cost or tax rate is negative, the method should generate an error.
  • If the cost is less than or equal to the minimum cost for the luxury tax:
    • Then let the total cost be the cost plus the cost times the tax rate divided by 100.
    • Otherwise,
      • Let the regular cost be the cost plus the cost times the tax rate divided by 100.
      • Let the luxury base be the cost minus the luxury-tax minimum.
      • Let the luxury surcharge be luxury-tax rate times the luxury base divided by 100.
      • Let the total cost be the sum of the regular cost and the luxury surcharge.

It assumed that tax rates are given in percentage form (e.g., 5 for 5%); hence, all computations with tax rates must be divided by 100.

That's a lot of English prose to describe mathematical formulas; when writing your own behavior paragraphs, feel free to write mathematical formulas.

Objects

We can identify the following objects in this description:

Objects
Description Type Kind Movement Name
item cost double varying received cost
tax rate (percent) double varying received taxRate
negative parameter error IllegalArgumentException -- thrown --
minimum luxury cost double constant local MIN_LUXURY_COST = 100.00
total cost double varying returned totalCost
regular cost double varying local regularCost
luxury surcharge double varying local luxurySurcharge
luxury base double varying local cost - MIN_LUXURY_COST
luxury tax rate double constant local LUXURY_TAX_RATE = 1.0

We can thus specify the behavior of our method as follows:

Specification:

receive: cost and taxRate.
precondition: cost >= 0 and taxRate >= 0.
return: the total cost.

This specification introduces a precondition specification. A precondition is a condition that must be true before the method executes. Sometimes we'll write code that generates an error for the precondition; other times we'll just let the method return a junk value. In either case, we must warn any programmer who might call this method, so it's important that the precondition be noted in the documentation of the method. In our behavior paragraph and object list, we've added the error that will be generated. We'll talk about how we do this and what a "thrown" movement means when we code up the method.

Do this...
Using the specification for Tax#computeTotalCost(), write its stub as well as its Javadoc comment. Use Double.NaN as the dummy value that you return. Compile and run the driver.

Each object that is neither received nor returned must be defined as a local object, within the body of our method. For example, MIN_LUXURY_COST will be a local double constant, defined to have the value 100.0; while regularCost will be a local double variable.

Operations

From our behavioral description, we have these operations:

Operations
Description Predefined? Name Library
receive cost and taxRate from caller yes parameters built-in
test if cost or taxRate is negative yes < 0 and || built-in
complain with an error yes throw built-in
compare cost and MIN_LUXURY_COST in less-than-or-equal relationship yes <= built-in
compute totalCost without luxury tax yes + and * built-in
compute regularCost yes * built-in
compute luxuryBase yes - built-in
compute luxurySurcharge yes * and / builtin
compute totalCost yes + built-in
if no luxury tax applies, compute normally, otherwise apply the tax yes if built-in
return totalCost yes return built-in

We have seen most of these operations before except for making decisions and complaining about errors. The table already suggests solutions for these operations.

Algorithm

We can organize our objects and operations as follows:

Algorithm
  1. Receive cost and taxRate from the caller.
  2. If cost or taxRate is negative,
    1. Generate an error.
  3. If cost <= MIN_LUXURY_COST
    1. Compute totalCost = cost + cost * taxRate/100.
    Otherwise
    1. Compute regularCost = cost + cost * taxRate/100.
    2. Compute luxuryBase = cost - MIN_LUXURY_COST.
    3. Compute luxurySurcharge = luxuryBase * LUXURY_TAX_RATE/100.
    4. Compute totalCost = regularCost + luxurySurcharge.
  4. Return totalCost.

Now that we have an algorithm, we can turn to testing this computation method.

Testing the Method

Do this...
Add a test method to the TaxTest class named testComputeTotalCost(). Add a Javadoc comment before the method to indicate what method you're testing.

Coming up with data for testing this method requires plug-and-chug work that we'll skip over. It's a matter of coming up with some numbers and pushing them through the algorithm above. So it takes some time, not a whole lot of thought.

A good place to start when testing is to test zero values. What if cost is 0? What if taxRate is 0? What if they're both 0? There's three tests already!

Do this...
Then add three calls to assertEquals() to test the three zero cases described above. Pick your own values for the non-zero values in these three cases. Compile and run the test-case class for a red bar. (The computation method should still be returning Double.NaN, so there's no way to get a green bar yet.)

In all of the assertEquals() tests, since we really only have to be accurate to the nearest cent, use 1e-2 (i.e., .01) as the fudge factor.

Zero tests only get you so far because zero actually quite a strange number; let's look at two other interesting computations.

First, what if the cost is less than the luxury base? Our test method will assume that the luxury base is 100. We don't want our numbers to be too simple, so let's try a cost of $59 and a tax of 4.35%. The result then should be 59 + 59*4.35/100 or 61.566 (as I discover from my handy calculator). There's our fourth test.

Second, what if the cost is greater than the luxury base? Again, we assume that the luxury base is 100. Let's try a cost of $112, a tax of 4.35%, and a luxury tax of 1.0%. Keep in mind that the normal sales tax applies to the whole cost while the luxury tax applies only to the cost above the luxury base. The computation then is

112 + 112*4.35/100 + (112-100)*1.0/100 = 116.99

There's a fifth test.

Do this...
Code up the fourth and fifth tests. Compile and run the test-case class for a red bar.

You want to make sure you're getting that red bar because it reassures you that the tests are being executed now. Later when you get a green bar, you'll know that the tests are being executed with good code.

You should also test the preconditions. This is a bit tricky due to the way that you're going to handle a failed precondition. First you have to try to violate a precondition:

Tax.computeTotalCost(-3, 12.3);

The 12.3 doesn't matter here since the -3 cost is enough to trigger a failed precondition. However, since this method call results in a failed precondition, the test this is in will always fail unless we add some more code

test-failure pattern
try {
  method(badArguments);
  fail(message);
} catch (exception e) { }
  • method is the method you're trying to test.
  • badArguments is an argument that you believe should trigger a problem in method.
  • message is a String describing the test.
  • exception is the type of exception that you expect method to throw back.
  • Warning: only one method call is allowed before the fail() statement.

The idea with this code is that you try to execute some code that you expect will fail. If the method call does not fail, then the fail() method is invoked. The method fail() comes to us from the JUnit framework (just like assertEquals()), and if it's executed, the test fails.

But if, as we hope, the method call to method() does fail, then an exception is thrown. An exception is a special object in Java indicating that something is wrong---very, very wrong. The catch clause catches the exception, and allows the code to keep executing normally. So the exception is never seen by the JUnit code, and so the test doesn't fail. It's basic "two wrongs make a right"!

You also need to identify what type of exception has been thrown. For now, you have really just one choice: IllegalArgumentException.

We'll explore the try-catch statement in more detail in a latter lab. For now, it's enough just to use the pattern above.

Some programmers prefer separating out their tests-for-failure from their tests-for-successful-computation.

Do this...
Write a new test method in TaxTest named testComputeTotalCostFailures().

Then you can add failure tests to it.

Do this...
Using the precondition-violating method call and the failure-test pattern, add two failure tests to testComputeTotalCostFailure(). One should test a negative cost, the other should test a negative tax rate. The messages in the call to fail() should indicate which argument is bad.

This means you'll have two try-catch statements.

Coding the Method

Constants should be declared first in the method.

Do this...
Declare any constants in the object list at the beginning of computeTotalCost().

To write the code for the computeTotalCost() method, you must use the if statement. Here's the general pattern:

two-branch-if-statement pattern
if (condition) {
  thenStatements
} else {
  elseStatements
}
  • condition is a Boolean expression that determines whether thenStatements or elseStatements are executed.
  • thenStatements and elseStatements are one or more Java statements.

As the pattern mentions, the condition of the if statement determines the control flow. If condition is true, then the thenStatements (a.k.a. then clause) are executed; otherwise (i.e., condition was false), the elseStatements (a.k.a. else clause) are executed.

The choice here is exclusively either-or: for one execution of a simple if statement, either the then clause is executed, or the else clause is executed. But never both.

You need the if statement for two things: first, for handling the error case (when the cost or tax rate is negative); second, for doing the basic computation.

Java uses exceptions to handle error situations. In the test method you tried some code and then caught the exception that was expected. Any good programming language carries through with its metaphors, and Java is a good programming language. Just as in the rest of life, we catch things that are thrown at us. Here's a pattern for checking parameters which throws an exception:

parameter-check pattern
if (errorCondition)
  throw new exceptionClass(messageString);
  • errorCondition is a Boolean condition that's true when there's a problem with the parameters.
  • messageString is a String expression that describes the problem.
  • exceptionClass is datatype that can be thrown.

It's important to note that throw works a lot like the return statement---the current method stops immediately. Unlike the return statement, a throw statement cascades through all of the methods that have been called. For now, it cascades all the way to the Java Virtual Machine, and you're program will crash! The test methods use a try-catch to handle the throwing more gracefully, but we'll wait until later to explore this in more details. For now, let the program crash.

The parameter-check pattern uses a single-branch if statement. The else clause is officially optional, and in some situations, it's not necessary. The throw statement gets the program out of the method if there's something wrong; and if there's nothing wrong, execution just goes to the statement after the parameter check.

To fit the parameter-check pattern: the errorCondition is whenever either parameter is negative; messageString should be something that says that the parameters should be positive.

As for the exceptionClass, Java provides us with an IllegalArgumentException class. That's exactly the situation we're in: if cost or taxRate is negative, then we have at least one illegal argument. You already catch this exception in a test method because you could anticipate that you would throw it from computeTotalCost() when you wrote the real computation for it.

Do this...
Add a parameter check to the beginning of the computeTotalCost() method. Compile your code, and run the test case class; the computation tests should still fail, but the failure test should not fail anymore.

The second if statement should compare cost against MIN_LUXURY_COST.

Do this...
After the parameter check, add a two-branch if statement that tests to see if cost is less than or equal to MIN_LUXURY_COST. Leave the then- and else-clauses empty; leave the original return statement at the end of the method. Compile your code to make sure it's good.

MIN_LUXURY_COST should have been declared earlier as a constant.

Both sections of the if computes totalCost which will be returned after the whole if statement. Since this variable is used in both sections, it must be declared before the if statement. Declare the variable once before the if statement. You don't have to initialize it to anything there; just be sure to assign the appropriate value to it in both sections of the if statement.

Do this...
Coding the statements for the then and else clauses of this if statement is straightforward, so go ahead and add them. Be sure to assign a value to totalCost in each clause; do not redeclare totalCost!

Do this...
Finish the method by changing the return statement so that it returns the value of totalCost. Compile and run the test-case class for a green bar---finally!

Do this...
When the test-case class runs for a green bar, run the driver again.

When you test bad data with a driver, the program will quit with a exception report. This is normal for a driver that doesn't catch its exceptions. Also be sure to test the driver for a negative number of items.

Maintenance

Program maintenance is the final stage of program development, in which modifications and improvements are added during its lifetime. Some studies have shown that the cost to write a program is only 20% of its total cost---the other 80% accumulates during maintenance! One of the goals of object-oriented programming is to reduce this maintenance cost by writing code that is reusable.

You can improve the driver by modifying it so that it displays totalCost in monetary format (i.e., with 2 decimal digits). The number of decimal digits in a real number is called the precision of that number. To show only two decimal digits, we must alter the default precision. This can be done with a method in the ann.easyio package for Screen objects:

theScreen.printFormatted(doubleExpression, integerExpression)

Do this...
Modify your source program so that totalCost is displayed with precision 2. Then test the correctness of your modification.

Very important note: you are changing the output of the driver. This does not in any way, shape, or form change the tests in the test method. The computation is the same as it always was, the driver alone has changed the way it displays the information. So your test-case class should still run for a green bar!

Submit

Turn in your code (three files) plus a sample execution of your test case and six executions of your driver. Be sure to run the driver on some bad data (negative number of items, negative cost, and negative tax rate); it's also useful to duplicate the tests in the test method since you already know what output you should get.

Also make sure that all of your code is well documented with Javadoc comments.

Terminology

body of a loop, catch an exception, condition, counting for loop, else clause, exception, exception report, for statement, for statement, human error, infinite loop, loop-control variable, precision, precondition, program maintenance, repetitive behavior, scope, single-branch if statement, then clause, throw an exception