Hands on Testing Java: Lab #4

Java Methods and Libraries

Introduction

As you likely found out the last time you purchased your last textbook, books can be quite expensive. One way that this expense can be reduced is through sharing: if you are unable to afford the full price for a book, and can find another person with the same problem, then you might purchase the book together, and share it. By having one person use the book and then another person reuse the book, the individual cost of using the book is cut in half.

A library is based on this idea of sharing. If a community of people pool their resources, then they can buy and share a centralized collection of books. By cooperating this way, each person has access to a greater set of books than he or she could afford individually.

Java implements shared libraries through its package system. You've already used libraries in packages. For example, in the previous lab exercise, the test-case class had this at the beginning:

import junit.framework.TestCase;

The effect of this directive is to tell the compiler that you want to use the class TestCase contained in the package junit.framework. Whenever you need a test-case class, you can import this same class, reusing everything it provides. Trust me: you do not want to have to write the code in that class even once, let alone every time you need it.

As another example, you've used these imports as well:

import ann.easyio.Keyboard;
import ann.easyio.Screen;

These go to the ann.easyio package, and you get access to the Keyboard and Screen classes. Here again, you definitely do not want to have to type in all of that code yourself for your programs. By using these classes from the package, you're free to concentrate on your own problem.

A Java package is thus similar to a library of books: it contains a set of classes ("books") that can be shared and reused by any program that needs them. It prevents programmers from reinventing the wheel.

There are two levels of libraries in Java:

Review

We have seen that Java provides a variety of classes, including Math that provides various mathematical methods, and Character that provides character-processing methods.

Importing a Class

To use a class, a program must use the import directive to include the class:

import-statement pattern
import packageName.className;
  • packageName is the name of the library's package.
  • className is the name of the class that implements a particular library.

For example:

import java.lang.Math;

This would let your program use the Math class. However, you may recall that you never imported the Math class or even the java.lang package. This is because the java.lang package and all of its classes are automatically imported for us in every program. This same package also provides us with the String and wrapper classes.

All other classes in other packages must be imported by your programs.

Invoking a Method

To call a method (invoke a method), you must name the class the method is in, name the method itself, and list out any arguments the method needs to do its job.

class-method-invocation pattern
className.methodName(arguments)
  • className is the name of the class that implements the method.
  • methodName is the name of the method.
  • arguments is a comma-separated list of expressions.

For example:

Math.pow(x, y)

The class is Math, the method is pow, and x and y are the arguments.

When control reaches such a method call, the arguments x and y are evaluated and their values are passed to the pow() method of the Math class. The method performs its task and (if appropriate) returns a value back to its caller (in this case x^y).

A Problem Solved by Writing Methods

The problem for this lab exercise will be to write some simple methods for computing English-units to metric-units, e.g., feet to meters. These conversions are obviously useful in a variety of places, so putting them into a library seems quite useful.

Getting Started

Do this...

  1. Start up Eclipse.
  2. Create a package
    edu.institution.username.hotj.lab04
    
  3. Create a computation class for this package named Metric.
  4. Create a test-case class for this package named MetricTest.
  5. Create lab04.txt in your Exercise Questions folder.

Planning a Module

The first thing to decide is what measurement conversions should be defined. For example, the following are just a few of the useful conversions:

English Unit Metric Unit Conversion Formula
Inches Centimeters 1 inch = 2.54 cm
Feet Centimeters 1 foot = 30.48 cm
Feet Meters 1 foot = 0.3048 m
Yards Meters 1 yard = 0.9144 m
Miles Kilometers 1 mile = 1.609344 km
Ounces Grams 1 ounce = 28.349523 g
Pounds Kilograms 1 pound = 0.3732417 kg
Tons Kilograms 1 ton = 907.18474 kg
Pints Liters 1 pint = 0.473163 l
Quarts Liters 1 quart = 0.946326 l
Gallons Liters 1 gallon = 3.785306 l

This lab exercise has you write a method to convert feet into meters. By storing this method in class, it can be shared by any and all programs that need to convert feet into meters, allowing them to avoid reinventing the wheel.

Method Design

Software that is carefully designed is better than software that is poorly designed. This applies to any method, not just the program itself. Fortunately, the same OCD we use for a program works just as well for any method.

Behavior

Behavior of Metric.feetToMeters()

The method should receive from its caller the number of feet to be converted. It should convert that quantity to meters by multiplying it by 0.3048, and return the resulting value to the caller.

Where a program typically inputs values from the keyboard, a method typically receives values from whatever code called the method. Similarly, where a program typically outputs values to the screen, a method typically returns a value to whatever code called the method.

This somewhat subtle difference is often ignored by beginning programmers, but it'll make your life easier if you drill this into your head now:

Helpful hint: Most methods do not interact with the screen or keyboard. Receiving data is done with parameters (not the keyboard); returning data is done with the return statement (not the screen).

When you come up with the behavior of a method, you should also anticipate what could go wrong. In this case, there's really nothing that could go wrong. It is reasonable to talk about a negative number of feet: perhaps a positive number of feet refers to feet above sea level, zero feet means at sea level, and a negative number of feet refers to feet below sea level.

Objects

The objects of a method are similar to those of a program except that in addition to describing the type and kind of each object, we also want to describe its movement: does it move into the method from outside, does it move from the method out to the outside, or is it purely local to the method?

Objects of Metric.feetToMeters()
Description Type Kind Movement Name
the number of feet double varying in feet
the conversion factor double literal local 0.3048
the corresponding number of meters double varying out meters

This information provides us with what we need to specify the problem your method solves:

Specification:

receive: feet, the number of feet to be converted.
return: meters, the equivalent number of meters.

Any data going in to the method is received; any data going out of the method is returned. This specification can be included in the documentation for the method.

This specification also provides us with what we need to write the method's signature, the first step in writing a method. A signature looks like this:

class-method-signature pattern
public static returnType methodName(parameters)
  • The public allows all other classes access to the method; static attaches the method to the class itself.
  • returnType is the data type of the value that the method returns.
  • methodName is the name of the method.
  • parameters is a list of declarations of parameters.

A precise method specification (derived from the object list) tell you how to build the signature.

Observe closely that the returned value is specified in the signature only by its data type. The parameter is specified by both its data type and a name. This is always the case in Java.

Do this...
Add the signature for feetToMeters() to the Metric class now. Remember that everything (except package and import statements) goes in the class.

You will get errors about the signature because it's not compilable code yet. To make it compilable, turn it into a method stub:

class-method-stub pattern
public static returnType methodName(parameters) {
  return dummyValue;
}
  • The public allows all other classes access to the method; static attaches the method to the class itself.
  • returnType is the data type of the value that the method returns. If the return type is void, no return statement is necessary.
  • methodName is the name of the method.
  • parameters is a list of declarations of parameters.
  • dummyValue is a dummy value to satisfy the compiler; pick the worst value you can think of.

Do this...
Compare this pattern to the pattern for a class method signature, and turn the signature into a stub. Use Double.NaN as a dummy value.

The constant Double.NaN stands for "not a number". It is guaranteed that Double.NaN is never equal to any other double, including itself! This means that you're guaranteed to get a failure from your unit testing when you use this stub. You'll know the method is being tested, and so when you eventually get a green bar again, you'll know that your method is working properly.

Operations

Continuing with the design, the list of needed operations is quite short:

Operations of Metric.feetToMeters()
Description Predefined? Name Library
get a value from the caller yes parameter mechanism built-in
multiply two real values yes * built-in
return a value to the caller yes return built-in

The multiplication should be familiar from the previous lab. The other operations we'll explain as we code up the algorithm since they're particular to implementing a method.

Algorithm

We can then organize these operations and objects into the following algorithm:

Algorithm of Metric.feetToMeters()
  1. Receive feet from the caller.
  2. Compute meters = feet * 0.3048.
  3. Return meters.

Now that the design is finished, we're ready to write our tests.

Testing

Wait a second. Testing? You haven't actually written the method yet. You have a signature, but it returns a dummy value. So why in the world would I have you write your tests next?

Well, as a matter of fact, some might argue that we've waited too long to write the tests; you should have written them before the design. This way you test the problem rather than the code.

Test Method

So what's involved in the tests? First, you need a test method in the test-case class.

Do this...
MetricTest is the test-case class, so open up this class. Add this test method to the class:

public void testFeetToMeters() {
}

This method declaration should make more sense; compare this definition to the pattern for a method stub given above. The only significant difference is the lack of the keyword static; this is due to the way that the JUnit library works. It's actually very rare for a method to be static, but it makes some other things easier to learn about methods.

Test Data

Next we figure out what data we'd like to test. The problem is pretty simple, so there aren't really any really goofy cases to worry about. First figure out some interesting data:

The first three data inputs are simple inputs that should give us predictable results. The fourth input is a relatively random number I picked; I used a calculator to compute the equivalent number of meters. The last test tests negative inputs.

Here's the code for the first and third assertions:

assertEquals("0 feet is equivalent to 0 meters", 0, Metric.feetToMeters(0), 1e-3);
// missing second...
assertEquals("1/0.3048 feets is equivalent to 1 meter", 1, Metric.feetToMeters(1/0.3048), 1e-3);
// missing fourth...
// missing fifth...

Do this...
Add these tests to the test method.

I've given you the exact code for the first and third; use these examples to create the code for the other tests.

There's nothing that says I can't do some extra computation in these tests, so note the argument passed to feetToMeters() in the third test. I could punch that division into my calculator and use that result as the argument, but computing an argument for the computation is okay.

However, computing the expected value probably wouldn't be a good idea; it allows too many things to go wrong.

The order in which you pass in arguments to assertEquals() is very important for debugging. When a test fails, you'll be told that one value was expected but another one was computed. The expected value---the value you hope to get---should be first; the computed value---the value you get from doing your computation---should be second.

As we saw in the previous lab, testing doubles requires an extra argument. This argument is a fudge factor (or more formally, a tolerance); arithmetic with doubles is inherently inaccurate. The fudge factor allows the test to pass if difference between the expected and computed values is less than this fudge factor. We're using 1e-3, which is 1x10-3 = .001 which should give us enough accuracy for these computations.

To summarize, here's the pattern for a assertion of a double computation:

double-assert-equals-statement pattern
assertEquals(expectedValue, computedValue, fudgeFactor);
  • expectedValue is the result you expect to get.
  • computedValue is the result you compute.
  • fudgeFactor is the tolerance you can tolerate between the expected and computed values.

Do this...
Write the assert statements for the three other tests. Compile and run the test-case class; it will red bar, but that's okay.

Documenting the Test Method

Do this...
Add a comment before the test method that explains what the method is going to test.

Here's one possibility:

/**
 * This method tests the {@link Metric#feetToMeters(double)} method
 * with five different inputs.
 */

I've used a Javadoc format here. You're welcome to try your hand at running the javadoc tool, but it takes a bit of effort to get it working nicely. The important thing is that you have clearly stated what the test method actually tests. You don't have to go into specifics, but you should at least give anyone reading the comments a very good idea what the method does (not how it does it).

Now we're ready to code the method itself.

Coding

Do this...
Switch back to the Metric#feetToMeters(double) method.

Defining A Method

The general pattern for a method definition looks like this:

class-method-declaration pattern
public static returnType methodName(parameters) {
  bodyStatements
}
  • The public allows all other classes access to the method; static attaches the method to the class itself.
  • returnType is the data type of the value that the method returns. If the return type is void, no return statement is necessary.
  • methodName is the name of the method.
  • parameters is a list of declarations of parameters.
  • bodyStatements is the body of the method, consisting of program statements.

A method definition (or declaration) is just like the stub, except the returned dummy value is replaced with useful computation statements.

Let's take the statements of the algorithm step by step:

1. Receive feet from the caller.

This step is already taken care of for us by the Java method-call mechanism. In the test-case class, we called feetToMeters() several times. Consider this call from testFeetToMeters():

Metric.feetToMeters (1/0.3048)

The division will be computed first, and then a copy of that value will automatically be stored in the parameter feet in our method, already declared in the parameter list of the prototype.

Generally, the first step of our computation methods will be to receive values, and this is always done with parameters, not in the body of the method.

2. Compute meters = feet * 0.3048.

Do this...
Using a declaration statement and an arithmetic expressions (as you saw in the previous lab), write the code for this step. (Make sure the return statement stays at the end of the method.)

3. Return meters.

The third statement can be performed using the Java return statement:

return-statement pattern
return expression;
  • expression evaluates to the value that is returned by the method.
  • returnType is the data type of the expression.

Do this...
At the end of feetToMeters(), write a better return statement that implements this last step of the algorithm.

That's the end of the algorithm for the method.

Do this...
Now compile all of your code, and run the test-case class for a green bar. If you get errors compiling or running tests, fix the problems until you see that green bar.

The tricky thing with writing tests is that initially errors could be in your computation method (i.e., feetToMeters()) or in the test method (i.e., testFeetToMeters()). You'll have to check to make sure that the tests are correct and that the computation is correct. Once you get that green bar, though, the tests should be fine, and further problems should be only in the computation method.

Documenting the Method

Do this...
Add a comment before the method explaining what the method does, mostly in terms of its parameters and return value.

You should also include any special cases that might surprise someone using the method (which we don't have this time around).

Here's one possibility:

/**
 * This method converts a number of feet into an equivalent number of
 * meters.
 * @param feet the number of feet.
 * @return the equivalent number of meters.
 */

The @param and @return are special Javadoc directives for specifying parameters and return values. Each parameter should be specified with its own @param, followed by the name of the parameter. There's only ever one value returned from a method, so there's only ever one @return.

Writing a Driver

Many people like to watch their method in action. The benefit of a test-case class is that it tests a lot of data automatically. But if you want interactive testing, then you want a driver.

A driver is a program that drives input, output, and computation. Usually, it refers to a simple program that drives just one or two methods to test them out interactively.

Designing The Driver Program

Most driver programs use the same algorithm:

Algorithm of any driver program
  1. Display a prompt for whatever values the method requires as arguments.
  2. Read those values from the keyboard.
  3. Do the computation that you're testing using the values from the keyboard.
  4. Display the result of the computation with appropriate labels.

Since our driver program uses this same basic algorithm, we can skip the entire design stage and proceed straight to coding.

Coding the Driver Program

Everything is implemented in a class, including drivers:

Do this...
First, create a driver class named Driver in the hotj.lab04 package.

You need access to the screen and keyboard:

Do this...
Add two import statements at the beginning of the file to import the Screen and Keyboard classes of the ann.easyio package.

Now we can write code in the main() method:

Do this...
Then add these statements to the beginning of the main() method:

Screen theScreen = new Screen();
Keyboard theKeyboard = new Keyboard();

Step 1 of the driver algorithm should be done after these declarations. It's just a matter of using the println() method of the theScreen object:

theScreen.println(expression);

The expression can be any expression at all that you want displayed on the screen. For now, it should just be a String literal like "Enter the number of feet:".

Do this...
Add code for this first step of the driver.

Step 2 involves the keyboard and a declaration. We want to read in a number of feet, a double; and that should be saved away for us in a variable. Every variable must be declared first, so let's start there:

double feet = expression;

Yes, we do declare feet a second time here, because this is a separate class and a separate method---a different scope; each variable and parameter is only valid within its own method.

The expression here should be an expression that reads in a double from the keyboard. Well, as good object-oriented programmers eager to call methods, let's ask the keyboard itself to do the reading:

theKeyboard.readDouble()

Do this...
Put this input expression into the declaration statement above to complete Step 2 of the driver algorithm.

Next, you need to do the computation and save the result. This will look a lot like the statement for feet:

double meters = expression;

The only difference here is that the value for meters does not come from the keyboard, but from invoking Metric.feetToMeters().

Do this...
Type in the statement for this third step. See the method invocations earlier in this lab exercise or in MetricTest for examples of this method invocation.

Finally, print the result with a useful message. We'll actually use two methods from theScreen object. First we need the print() we (almost) had before:

theScreen.print(expression)

Just like println(), the method print() will print the result of the expression. However, println() finishes it off by also printing a newline character; print() does not.

As for the result of the conversion, notice that there's no semi-colon at the end of that last statement. That's because we're not quite finished with our statement. Screen objects, like theScreen allow us to chain along our method calls, so we'll add two more:

theScreen.print (expression1)
  .printFormatted (realNumberExpression, digits)
  .println (expression2);

Do this...
Use the full version of this statement, supplying the missing code so that your output looks like this (assuming a certain input):

That many feet is equal to 0.000 meters.

Compile all of your code, and run the Driver class.

Re-use the sample data from the test-case class when running the driver so that you know what answers you're supposed to get. Instead of testing the result automatically, though, the driver will print the result, and you'll have to check it.

Documenting the Driver

Do this...
Add class documentation. You should also add a comment for a driver before main() that explains what main() does.

Submit

Turn in all three code files, a sample execution of MetricTest and of Driver (run Driver at least three times with different inputs each time).

Terminology

argument, call a method, computed value, driver, expected value, fudge factor, input, invoke a method, library, method, method call, method declaration, method definition, method stub, movement, output, package, parameter, receive, return, return, return type, scope, signature, tolerance