Lab 4: Arrays, Objects, and Testing

A. Intro

Download the code for Lab 4 and create a new Eclipse project out of it.

Learning Goals for Today

Work with a partner that you haven't already worked with.

Today you're going to learn about a simple data structure: the array. Arrays are an ordered list of elements, all the same type. It enables access to an element by number—its position in the array—rather than name as in objects. Arrays act like objects with some extra notation. Exercises in the first part of today's lab give you practice declaring and using an array.

In the second part of the lab, you're introduced to JUnit. JUnit is a provides a way to write repeatable tests, which substantially reduces the tedium of testing. Many of your homework submissions the rest of the course will include a JUnit testing file.

We provide some guidelines for designing test cases, and give you some practice applying them. Properties of good organization that you've learned in earlier program activities apply to JUnit as well.

JUnit also makes easy an approach to programming called test-driven development (TDD), in which you design test cases before the code they are testing. TDD is popular in industry. We will encourage it in the remainder of CS 61BL, starting by leading you through the steps of the construction of a class representing measurements (feet, yards, inches) for homework.

B. Arrays

Array Definition and Use

An array is an indexed sequence of elements, all the same type. Real-life examples of arrays include the following:

Index values go from 0 to the length of the array, minus 1.

We declare an array variable by giving the type of its elements, a pair of square brackets, and the variable name, for example:

int[] values; 

Note that we don't specify the length of the array in its declaration.

Arrays are basically objects with some special syntax. To initialize an array, we use the new operator as we do with objects; the argument to new is the type of the array, which includes the length. For example, the statement

values = new int[7]; 

stores a reference to a 7-element integer array in the variable values. This initializes the array variable itself, For an int array, all of the elements are initially 0. The elements can be changed with assignment statements. For example, to make the second element be 4 we write,

values[1] = 4;

To access an array element, we first give the name of the array, and then supply an index expression for the element we want in square brackets. For example, if we want to access the kth element of values, we can write,

values[k-1] 

If the value of the index expression is negative or greater than or equal to the length of the array, an exception results.

An array has an instance variable named length that stores the number of elements the array can hold. For the values array just defined, values.length is 7. The length variable can't be changed; once we create an array of a given length, we can't shrink or expand that array.

Exercise: Version 3 of the Line Class

The file Line3.java contains a version of the Line class that represents a line segment as an array of four integers: the x and y values of one of the endpoints (in elements 0 and 1), and the x and y values of the other endpoint (in elements 2 and 3). As before, complete and test the framework, with one of you at the keyboard and the other monitoring.

Why are we doing so many different versions of the Line class?

Exercise: Version 4 of the Line Class

The file Line4.java contains the final version of the Line class. It represents a line segment as an array of two Point objects. Switch places with your partner. Then complete and test the framework as in previous steps.

After completing the code, draw box-and-arrow diagrams to represent all of the Line initializations.

Self-test: Practice with Arrays of Primitives

In the code below, we use the method call System.out.print( ), which is different than System.out.println( ) in that it prints its arguments without a terminating newline character.

What gets printed by the code below?

public class Test { 
  public static void main (String [ ] args) { 
     int [ ] vals1, vals2, vals3; 
     vals1 = new int[2]; 
     vals1[0] = 1; 
     vals1[1] = 3; 
     vals2 = new int[2]; 
     vals2[0] = vals1[1]; 
     vals2[1] = vals1[0]; 
     vals1[0] = 5; 
     System.out.print (vals2[0] + " " + vals2[1] + " and ");
     vals3 = vals1; 
     vals3[1] = 27; 
     System.out.print (vals1[0] + " " + vals1[1] + " and ");
     vals1[0] = 25; 
     System.out.print (vals3[0] + " " + vals3[1]);
  }
}
Toggle Solution

Answer: 3 1 and 5 27 and 25 27

Self-test: Practice with Arrays of Objects

What is printed by the program below?

import java.awt.*; 

public class Test { 
   public static void main (String [ ] args) { 
     Point [ ] line1, line2; 
     line1 = new Point [2]; 
     line2 = new Point [2]; 
     line1[0] = new Point ( ); 
     line1[1] = new Point ( ); 
     line1[0].x = 1; 
     line1[0].y = 3; 
     line1[1].x = 7; 
     line1[1].y = 9; 
     line2[0] = line1[1]; 
     line2[1] = line1[0]; 
     line1[0].x = 11; 
     line1[1] = line1[0]; 
     System.out.println (line2[0].x + " " + line2[0].y 
         + " " + line2[1].x + " " + line2[1].y); 
   } 
 }
Toggle Solution

Answer: 7 9 11 3

Self-test: Recognizing Purpose from Code

Provide a good name for the following method. Assume that values contains at least one element.

private static int _______ (int [ ] values) {
  int rtn = values[0];
  int k = 1;
  while (k < values.length) {
    if (rtn < values[k]) {
      rtn = values[k];
    }
    k++;
  }
  return rtn;
}
Toggle Solution

Answer: max

Provide a good name for the following method.

private static void _______ (int [ ] values) {
  int k = 0;
  while (k < values.length/2) {
    int temp = values[k];
    values[k] = values[values.length-1-k];
    values[values.length-1-k] = temp;
    k = k+1;
  }
}
Toggle Solution

Answer: reverse

Provide a good name for the following method. Assume that values contains at least one element.

private static boolean _______ (int [ ] values) {
    int k = 0;
    while (k < values.length-1) {
        if (values[k] > values[k+1]) {
            return false;
        }
        k = k+1;
    }
    return true;
}

(Hint: When we have methods that return a boolean we sometimes start the method name with "is".)

Toggle Solution

Answer: isIncreasing

Provide a good name for the following method. Assume that values contains at least one element.

private static int _______ (int [ ] values, int a) { 
    int k = 0; 
    int n = 0; 
    while (k < values.length) { 
        if (values[k] == a) { 
            n++; 
        } 
        k++; 
    } 
    return n;
} 
Toggle Solution

Answer: count

C. Argument Passing

Argument Passing

When you pass an argument into a method in Java, we say the argument is passed by value. This means that each argument is copied into the corresponding parameter in the method being called.

Here's an example with primitives. Suppose I have the following method,

public void makeThree(int x){
    x = 3;
}

Then suppose I run the following code,

int y = 0;
makeThree(y);
System.out.println(y);

This will print out 0, not 3, because makeThree changes the value of x inside the method, which is a copy of y, and not the original y.

Don't get confused when using objects. When calling methods with objects, the argument to the method is actually the reference to the object. What this means is that you copy the reference, but not the object. For example, suppose I have the following method,

public void setFirstToThree (int [] values){
    values[0] = 3;
}

Then suppose I run the following code,

int [] originalValues = new int[3];
setFirstToThree(originalValues);
System.out.println(originalValues[0]);

It will actually print out 3! Why? If we draw a picture including both values and originalValues, it looks like this:

ArrayPassByValue

Notice the reference has been copied (values points to the same array as originalValues) but the object has not (there is only one array that we are modifying).

D. Useful Methods All Objects Share

The toString method

The printing methods System.out.print and System.out.println both take an argument of type String (one of Java's builtin classes). (We have seen the String concatenation operator + used to build a String argument to be printed.) The String class knows how to do "the right thing" with values of primitive types; for example, if you try to print out an int, it converts the integer to the corresponding sequence of digit characters. However, what happens if you try to print out an object for a new class you just wrote? If the class is very complicated, is there any one answer to what the object should look like when printed out?

By default, if you try to print an object of a class you wrote, it will just print the memory address of the object. For example, say you we try to print out a Line4 object.

Line4 test1 = new Line4();
test1.points = new Point[2];
test1.points[0] = new Point(1, 2);
test1.points[1] = new Point(3, 4);
System.out.println(test1);

produces the output

Line4@47b480

This isn't very useful, because it doesn't really show us anything about the Line4 object we made. We can fix this behavior by supplying our own conversion method named toString. If we define a toString method exactly as follows inside the Line4 class:

public String toString() {
    ... 
}

System.out.print and System.out.println will call it whenever they receive a reference to a Line4 object.

For example, we could add this method to the Line4 class:

public String toString() {
    return "(" + points[0].getX() + ", " + points[0].getY() + ")" + ", " 
        + "(" + points[1].getX() + ", " + points[1].getY() + ")";
}

Now our code from above will print:

(1.0, 2.0), (3.0, 4.0)

Which is significantly more useful.

Essentially, for any class we write, if we want to print it out we have to define our own custom toString method for that class.

Note: (This is an instance of using inheritance to override behavior in a parent class. In Java, all classes inherit from the Object class, whose toString produces the cryptic result mentioned above. We'll investigate the details of inheritance in a couple of weeks.)

The equals Method

Another useful method is equals, conventionally the method to determine whether two objects contain the same information. Like with toString, we have to define our own custom equals method for any class we write, because the default behavior of equals isn't very useful. (The default behavior of equals is to be the same as the == operator, which determines whether its operands reference the same object. You want to change equals so it does something different than ==.) Just as System.out.print and System.out.println call an object's toString method to determine its printable representation, there are a variety of situations where an object's equals method is called as a result of some other processing.

Again because of inheritance, the header to the equals method has to be defined exactly as follows to override the Object class's equals method:

public boolean equals (Object obj) {
    ...
}

Note that, no matter what class you're defining equals in, the argument must have type Object.

Generally the way to write an equals method would be to go through each of the objects' instance variables and check they have the same values.

Callbacks

A callback is a situation where we call a method of a library class and another of our methods gets called as a result.

It is common in Java for a method we provide to be called not by our own code but by someone else's. toString is a common example of this. toString is rarely called directly. More likely, it is called by code that needs to produce a people-readable representation of an object, for instance, the System.out.println method.

Another frequent participant in a callback is the equals method. For example, the ArrayList contains method calls equals to determine if the argument to contains is an element of the array.

We will experiment with the equals method in the context of the Measurement class.

Self-test: Experiment with equals and toString in the MeasurementX Class

Look at the class MeasurementX.java from the files you downloaded earlier.

Perform tests to answer the questions. Through these tests think about how Java is designed. Would you have made the same design decisions?

Remove the toString() method from MeasurementX.java. Does main still work as it should? (indicating that 2'7" and 10'9" are in the ArrayList?) Choose one answer.

Yes
Incorrect. contains uses equals which uses toString
No
Correct! contains uses equals which uses toString
Check Solution

Remove the equals(Measurement m) method from MeasurementX.java. Does main still work as it should? (indicating that 2'7" and 10'9" are in the ArrayList?) Choose one answer.

Yes
Yes! contains calls equals(Object) , which is overriden from the Object class
No
No. contains calls equals(Object) , which is overriden from the Object class
Check Solution

Remove the equals(Object obj) method from MeasurementX.java. Does main still work as it should? (indicating that 2'7" and 10'9" are in the ArrayList?)

Yes
No. contains calls equals(Object) to work
No
Yes! contains calls equals(Object) to work
Check Solution

E. Testing with JUnit

JUnit is a testing framework that's integrated into the Eclipse programming environment. It provides a convenient way to structure and evaluate your program tests. You'll see a JUnit test in the next step.

To set up a testing file for the class you're currently working on in Eclipse, select New JUnit Test Case from the File menu. When you do this, Eclipse will ask you about adding the JUnit library to your build path; answer yes to this. It will then set up a test class whose name is the name of your class with "Test" appended.

Eclipse then presents you with a stub for your test class. You should now start defining void methods whose names start with "test" and describe what is being tested. They will call a number of assertion methods provided in JUnit.

Here are the most useful methods provided by JUnit:

void assertTrue (boolean condition); If assertTrue's condition evalues to false, then the test fails.
void assertTrue (String errmsg, boolean condition); Like assertTrue, but if it fails, it prints an error message.
void assertNull (Object obj); If assertNull's argument isn't null, the test fails. An error message may also be supplied.
void assertNotNull(Object obj); Fails for a null argument. An error message may be supplied.
void assertEquals (Object expected, Object actual); assertEquals suceeds when expect.equals(actual). For primitives, it checks expected == actual. An error message may also be supplied.
void fail(); If this code is run, the test fails. An error message may be supplied.

Perhaps you have been accustomed to testing your code using print statements. You don't do that here; the various test... methods you write will communicate their results to you by the success or failure of the assertions.

Once you type in your test methods, run your test class. JUnit will give you a list of the assertions that fail, or a green bar if they all pass.

JUnit Example

Suppose we have written a toString method for the Line3 class and want to test that it works correctly. We expect a Line3 object to print out in the form (x1, y1), (x2, y2). Here's an example method using JUnit that could test this functionality,

void testToString(){
    Line3 l = new Line3();
    l.coords = new int[4];
    l.coords[0] = 8;
    l.coords[1] = 6;
    l.coords[2] = 4;
    l.coords[3] = 2;    

    assertEquals("(8, 6), (4, 2)", l.toString());
}

Setting up JUnit Tutorial

The goal here is to get you and your partner experience running JUnit. You might want to skim over the tutorial to see the big picture before you get started.

  1. Navigate to the Counter.java file
  2. Make a new JUnit Test Case

    • Right-click on your file Counter.java and select "New" -> "Junit Test Case"

    NewJUnit

    • Name the JUnit Test Case "CounterTest". For now, select "New JUnit 3 test" instead of "New JUnit 4 test"

    MakeCounterTest

    • Copy and paste the following CounterTest.java code into your CounterTest.java.

    CounterTest.java

    // JUnit test case
    import junit.framework.TestCase;
    
    public class CounterTest extends TestCase {
        public void testConstructor() {
            Counter c = new Counter();
            assertTrue (c.value() == 0);
        }
    
        public void testIncrement() {
            Counter c = new Counter();
            c.increment();
            assertTrue (c.value()  == 1);
            c.increment();
            assertTrue (c.value() == 2);
        }
    
        public void testReset() {
            Counter c = new Counter();
            c.increment();
            c.reset();
            assertTrue (c.value() == 0);
        }
    
    }
  3. Run your JUnit Test Case

    • Click the green button to run CounterTest.java (just like you run other programs)

    Run

    • You will see a green bar appear to show that you have no errors.

    RunSuccessful

  4. Break something in your JUnit Test Case

    • Introduce an error into one of the CounterTest methods, asserting for example that the value of a freshly-built counter object is 7. Run the JUnit test again. Observe the red bar.

JUnit 3 vs. JUnit 4

You'll notice that whenever you create a JUnit test in Eclipse, you'll have a choice between making a JUnit 3 or a JUnit 4 test case. There are a number of differences between them, but here are the most critical ones:

In this class, it won't make a big difference whether you use JUnit 3 or 4. The examples in this lab will use JUnit 3, but later in the class we'll use JUnit 4.

F. Testing Principles

Mod N Counters Defined

Now that you've learned how to use JUnit, let's learn some testing principles. We're going to use the example of class that implements a Mod N counter to demonstrate good testing principles. But first, let's explain what a Mod N counter is.

A Mod N counter is a counter that counts up to a specified amount (the N), and then cycles back to zero.

For example, if we had a Mod 4 counter, it would count like this: 0, 1, 2, 3, 0, 1, 2, 3, 0, ...

The ModNCounter class is similar to the Counter class, but notice that in order to keep track of the value of N it will need an an extra instance variable—a good name for it is myN. myN is initialized with a one-argument constructor whose argument is the intended myN value. Thus the code,

ModNCounter c = new ModNCounter (2);
System.out.println (c.value ( ));
c.increment ( );
System.out.println (c.value ( ));
c.increment ( );
System.out.println (c.value ( ));
c.increment ( );

should print 0, then 1, then 0.

Exercise: Renaming a Class in Eclipse

To write the ModNCounter class, we're going to modify the Counter class from earlier. Here's how to do this quickly using Eclipse. First go to Counter.java, the program you just tested. Double-click the word "Counter" in the class header; it should now be selected. Right-click "Counter" and select "Refactor"—>"Rename" and type "ModNCounter" in the little box that appears. The effect of this change is to change any reference to Counter to ModNCounter, not only in Counter.java but also in CounterTest.java.

You should notice that the name of the file Counter.java is now ModNCounter.java However the file CounterTest.java has not changed names, but all of the times it said Counter in CounterTest.java it now says ModNCounter. You'll have to change the name of CounterTest.java manually.

Remember this operation! It's pretty common to want to rename an identifier (a variable or a class name) at some point. Eclipse makes this easy by renaming not only the definition of the identifier but also everywhere it's used.

Don't make any other changes to ModNCounter.java just yet.

Test-driven Development

Test-driven development is a development process that involves designing test cases for program features before designing the code that implements those features. The work flow is:

  1. Write test cases that demonstrate everything you want your program to be able to do
  2. Write as little code as possible so that all the tests are passed
  3. Clean up the code as necessary. Recheck that all tests still pass.

Repeat these three steps in a loop, once for each new feature you add to a program.

Exercise: Test-driven Development for ModNCounter

Here's a walkthrough of how to do test-driven development for creating ModNCounter:

  1. Decide what you're going to change in your program: In ModNCounterTest.java, supply an argument for each of the constructor calls, because you know you will have to initialize a ModNCounter with a value of N.
  2. Write code in JUnit to test the change: Add code in testIncrement that checks (using assertions) that wraparound of the cycling counter works correctly.
  3. Eliminate compilation errors: In ModNCounter.java, add an argument to the constructor. Both files should now be free of compile-time errors. Don't make any other changes in ModNCounter.java. We don't want it to work just yet. Run CounterTest as a JUnit test case. All tests should pass except the wraparound check.
  4. Write code to make the tests pass: Now go fix the increment method and run the JUnit test again. If all goes well, your updated code should pass all the tests.

So you've done some test-driven development. First, you supplied test cases in the CounterTest class. Then you changed as little code as possible in ModNCounter to remove compile-time errors. Then you provided the code to pass the tests.

Exercise: More Testing

From the standpoint of test-driven development, this activity is backward. It starts with an already implemented class, and then designs the testing code. The purpose of this exercise is just to give you more practice with JUnit.

Copy over the Account class from last lab into your current lab project. Then create a new JUnit test case for it with the following methods:

Work on the test cases until they all succeed.

Discussion: System.out.println() vs. JUnit

Link to the discussion

What is one reason or situation where using JUnit for debugging and testing is better than using System.out.println() ?

Testing Principle 1

One testing principle you can imagine is that test values should exercise every statement in the program, since any statement that's not tested may contain a bug. Our tests of the Account class basically did that, covering the three cases in withdraw and the two cases in deposit. As in the testing of Account, exercising all statements may require more than one run of a program.

Recall the leap year program from an earlier exercise:

if (year % 400 == 0) {
    System.out.println ("leap year");
} else if (year % 100 == 0) {
    System.out.println ("not leap year");
} else if (year % 4 == 0) {
    System.out.println ("leap year");
} else {
    System.out.println ("not leap year");
}

The code contains four cases, exactly one of which is executed for any particular value of year. Thus we must test this code with at least one year value per case, so at least four values of year are needed for testing:

Principle 1 by itself is insufficient, however. Here's an example;

isLeapYear = false;
if (year % 4 == 0) {
    isLeapYear = true;
}
if (year % 100 == 0) {
    isLeapYear = false;
}
if (year % 400 == 0) {
    isLeapYear = true;
}

A year value of 2000 causes all the statements in this program segment to be executed. However, there is a bug in this code it will not catch (see next slide).

Self-test: When Testing Principle 1 is not Enough

Here's the code again with line numbers.

00: isLeapYear = false; 
01: if (year % 4 == 0) { 
02:     isLeapYear = true; 
03: } 
04: if (year % 100 == 0) { 
05:     isLeapYear = false; 
06: } 
07: if (year % 400 == 0) { 
08:     isLeapYear = true; 
09: } 

A single test value that exercises every statement in this code is insufficient to reveal all bugs in the code.

Below are proposed replacement lines for the program. Which changes would make this program wrong for some values, but not for the test value 2000? Check all that apply.

00: isLeapYear = true; // was false
Correct!
02: isLeapYear = false; // was true
Correct!
05: isLeapYear = true; // was false
Correct!
08: isLeapYear = false; //was true
Incorrect.
Check Solution

How many test cases do you need in general if you have three if statements as in the code above?

1
Incorrect. You have to test different cases!
3
Incorrect. It's not enough to test each if individually: the combination of all three of them can lead to different effects
4
Incorrect.
8
Correct! This is all possible combinations of outcomes that can occur
10
Incorrect. There aren't 10 different cases that can happen.
Check Solution

Testing Princple 2

To augment principle 1, we'll say we need to test various paths through the program. For example, suppose our program had two consecutive if statements:

if ( ... ) {
    ...
}
if ( ... ) {
    ...
}

There are two possibilities for each test, success or failure. Thus there are four paths through the two statements, corresponding to the four possibilities

This explains why one test was insufficient to exercise all the code in

isLeapYear = false;
if (year % 4 == 0) {
    isLeapYear = true;
}
if (year % 100 == 0) {
    isLeapYear = false;
}
if (year % 400 == 0) {
    isLeapYear = true;
}

From the previous discussion, it looks like we need eight tests, corresponding to the eight paths through the three tests. They are listed below.

year % 4 == 0, year % 100 == 0, and year % 400 == 0 (which just means that year % 400 == 0)
year % 4 == 0, year % 100 == 0, and year % 400 != 0
year % 4 == 0, year % 100 != 0, and year % 400 == 0 (not possible)
year % 4 == 0, year % 100 != 0, and year % 400 != 0
year % 4 != 0, year % 100 == 0, and year % 400 == 0 (not possible)
year % 4 != 0, year % 100 == 0, and year % 400 != 0 (not possible)
year % 4 != 0, year % 100 != 0, and year % 400 == 0 (not possible)
year % 4 != 0, year % 100 != 0, and year % 400 != 0 (equivalently, year % 4 != 0)

Notice that some of the tests are logically impossible, and so we don't need to use them. This leaves the four tests we needed to write.

Self-test: Are we Missing a Case?

Below is code from an earlier quiz. Do you remember what it does? good should become true if exactly two of the variables are zero.

if (a == 0) {
    if (b == 0) {
        if (c == 0) {
            good = false;
        } else {
            good = true;
        }
    } else if (c == 0) {
        good = true;
    } else {
        good = false;
    }
} else if (b != 0) {
    good = false;
} else if (c == 0) {
    good = true;
} else {
    good = false;
}

There are only seven assignment statements. Shouldn't we expect eight, because there are eight possible settings of three variables? Was a case missed?

Yes
Incorrect. Is one of the assignment statements handling multiple cases?
No
Correct! The case else if (b != 0) handles the case where a=1, b=1, c=0 AND a=1, b=1, c=1 .
Check Solution

A Principle for Testing Loops

A loop can vastly increase the number of logical paths through the code, making it impractical to test all paths. Here are some guidelines for testing loops, drawn from Program Development in Java by Barbara Liskov and John Guttag, a book used in previous CS 61B offerings.

Liskov and Guttag go on to say: This approximation to path-complete testing is, of course, far from fail-safe. Like engineers' induction "One, two, three—that's good enough for me," it frequently uncovers errors but offers no guarantees.

Black-box Testing

All the test principles discussed so far focused on testing features of the code. Since they assume that we can see into the program, these techniques are collectively referred to as glass-box testing, as if our code is transparent.

Another testing approach is called black-box testing. It involves generating test cases based only on the problem specification, not on the code itself. There are several big advantages of this approach:

Principles for Black-box Testing

In black-box testing as in glass-box testing, we try to test all possibilities of the specification. These include typical cases as well as boundary cases, which represent situations that are extreme in some way, e.g. where a value is as large or as small as possible.

There are often a variety of features whose "boundaries" should be considered. For example, in the DateConverter program, boundary cases would include not only dates in the first and last months of the year, but also the first and last dates of each month.

Discussion: Boundary Cases

Link to the discussion

Consider the problem of printing the days of a month in calendar format, given the number of days in the month and an integer that says what day the month starts on. For instance, given 30 as the number of days in the month and 3 (Wednesday) as the starting day, the output should be

          1  2  3  4

 5  6  7  8  9 10 11

12 13 14 15 16 17 18

19 20 21 22 23 24 25

26 27 28 29 30

Devise as many boundary cases as you can.

Parting Advice on Testing

We'll encounter many more testing techniques later in the course. Here are some last bits of advice for now.

Examples of careless selection of test data may have come up in your testing of the date converter program. Here's how we would have tested that program.

  1. We start with dates in January: 1 and 31. (These values give us as much or more information as values between 2 and 30.)
  2. We continue with the corresponding dates in February, computing the expected answers from January's dates. February 1 is just January 31 + 1 = 32; February 29 is 32 + 28 more days = 60.
  3. We design March's test values from February's, April's from March's, and so on through December. December 31 should be day number 366, so that provides a check on our arithmetic.

Exercise: Testing a Measurement Class

Now, you're going to be writing the code and a JUnit Test Case (a whole file of tests) for the Measurement class. We have provided "stubs" for each of the methods in Measurement.java. Stubs show the header of the method. You will both be writing the code for these methods and, we hope, doing test-driven development (write the tests first).

Your work will be graded on two criteria:

As mentioned in lab and lecture, all your instance variables should be private. You are not to change the public interface of the Measurement class; e.g. don't add any additional public accessor methods. You are allowed to add anything that's private.

FAQ:

Q: Do we need to handle negative numbers?

A: No. The Measurement class doesn't need to do sensible things when passed negative numbers as arguments.

Q: Do we need to worry about integer overflow?

A: No. (Although you could imagine someone's Measurement class handling this correctly.)

Q: Do we need to worry about null arguments?

A: Not this time. You may assume that minus and plus are only passed non-null arguments. (They are the only two methods that take in objects.)

Q: Should the methods modify the current Measurement object or create a new Measurement object to be returned?

A: minus, plus, and multiple should all create a new Measurement object.

G. Conclusion

Summary

Arrays, the first of today's topics, came to Java from C and C++. It turns out than rather using arryays themselves, most needs for an array in Java are satisfied with using a class called ArrayList (a member of Java's class library), which is an expansion on the array we learned today. We will be studying ArrayList as the basis for implementation of the various "collection classes" that we'll soon encounter.

Properties of good organization that you've learned in earlier program activity apply to JUnit as well. Here are some examples:

We think test-driven development is a big win, and we will be encouraging you to use it when building your programs. At the very least, you have to like the frequent green bars!

If you're not feeling rock solid on the stuff we've done so far, check out http://codingbat.com/java to get some extra practice writing java programs.

Readings

For next lab, check out the reading assignment:

Submission

For this lab, you are to turn in the following:

Submit the Java files in the directory lab04.

In addition, you must fill out this self-reflection form before this lab is due, as part of your homework assignment. Self-reflection forms are to be completed individually, not in partnership. You'll have one of these to do at the end of each week. They're here to help you be conscious of how your partnerships are working out.

This lab (and all Friday labs) will be due before your upcoming Monday lab.