Google
 

 

up previous next contents
Up: 4. Day 4: Databases, Previous: 4.1 Databases and JDBC Next: 4.3 Best practices   Contents

Subsections

4.2 JUnit

4.2.1 Is Testing Important?

Do you think testing your software code is important? Consider the following examples:

4.2.2 Mars Orbiter

A failure to recognize and correct an error in a transfer of information between the Mars Climate Orbiter spacecraft team in Colorado and the mission navigation team in California led to the loss of the spacecraft last week, preliminary findings by NASA's Jet Propulsion Laboratory internal peer review indicate... The peer review preliminary findings indicate that one team used English units (e.g., inches, feet and pounds) while the other used metric units for a key spacecraft operation. This information was critical to the maneuvers required to place the spacecraft in the proper Mars orbit.

4.2.3 USS Yorktown

An article in the November 1998 Scientific American describes an incident aboard the USS Yorktown, a guided missile cruiser. A crew member mistakenly entered a zero for a data value, which resulted in a division by zero, an error that cascaded and eventually shut down the ship's propulsion system. The Yorktown was dead in the water for a couple of hours because a program didn't check for valid input.

4.2.4 Types of tests

We can't tell the story of working backwards (using JUnit) until we see how and why we should use tools like JUnit. So first, a little background on testing.

  • Unit tests - An automated exercise of code where the developer may create an artificial environment to automatically exercise a unit of code.
  • Regression tests - Running the same set of tests regularly or after known events.
  • Black Box tests - Testing based only upon the documentation of the unit without any knowledge of the implementation.
  • White Box tests - Tests based on knowledge of the internals of the unit.
  • Integration tests - Taking separate units of software that have already been tested and testing them together.
  • Bounds tests - Testing the outer ranges of values. Nulls, zeroes, blanks, etc.
  • Stress tests - Testing a system's ability to handle a desired system load, typically more than is expected.
  • System/Acceptance testing - Making sure the software as a whole works as expected.

4.2.5 Unit Testing 101

4.2.5.1 Definitions of unit testing

A few definitions of unit tests from various sources ...

  • A unit test is an automated exercise of code.
  • A unit test is written for each module (class), in isolation, to verify its behavior.
  • Unit tests are tests that you, the developer, create to exercise your code and prove that it works, i.e., prove that each of your methods meet the criteria of its contract.
  • Unit testing is a form of ``white box'' testing.
  • In combination, unit tests can be used to create large regression tests.

C3 (the famous Chrysler C3 project) has over 1300 unit tests, performing over 13,000 individual checks. They run in about 10 minutes.

4.2.6 Goals of unit testing?

  • Test each part (unit) of the code in isolation.
  • Create tests that retain their value over time.
  • Create much larger regression tests that help demonstrate that the entire system still works (or not) after changes.
  • In the XP view, if a program feature lacks an automated test it is assumed that it doesn't work.
  • In a team environment unit tests ensure that we don't break one another's code.

4.2.7 Unit Testing with JUnit


4.2.7.1 How to create unit tests with JUnit

  1. Build a subclass of the class TestCase.
  2. Create as many test methods in this test class as necessary. 
  3. With JUnit, if each method begins with the name "test", like testThis(), testThat(), etc., the test methods will be discovered automatically.
  4. The testing framework collects up all the tests and executes them one after another.
  5. You can create a method named setUp() that sets stuff up before each test is run.
  6. You can create a method named tearDown() that helps you get rid of stuff after each test is run.


4.2.8 A sample JUnit session

Let's go back to the need for a Pizza class. Here are the things we want to be able to do with our Pizza class.

  1. Create a means of adding toppings to a pizza.
  2. Create a Pizza class and a test class for it.
  3. Create methods to get the list of toppings from a pizza.
  4. Create a way to remove a topping from a pizza.
  5. Create a way to determine the price of a pizza.

For this class your first task is to make sure you can add and remove toppings. How do you start?

  1. First, make sure you have JUnit set up properly so you can easily use it.
  2. Next, create the desired Pizza class, but implement nothing.
  3. Next, create a test class named _Pizza. This class will extend TestCase, and will hold all of your unit tests for the Pizza class.
  4. In the _Pizza class, start to implement the first test method. The first thing I want to be able to do is to get a list of toppings that a pizza has, so I write a little code like this:
    public void testToppingsOnNewPizza()
    {
      Pizza pizza = new Pizza();
      List toppings = pizza.getToppings();
      assert( (toppings.size()==0) );
    }
    

  5. Run this JUnit test. Does it work?

  6. No, it doesn't work, because the getToppings() method does not exist. So what do you do next? Make it work. Implement this behavior in the Pizza class.

  7. Go to the Pizza class. Create a method named getToppings(). From what we know so far it should return a List, and apparently for a new Pizza(), the best thing to do is to return an empty List (not a null, but a List with nothing in it).

    public List getToppings()
    {
      return this.toppings;
    }
    

  8. Will this work by itself? No, you also have to have a List named toppings in the Pizza class. Here's what the Pizza class should look like:

    public class Pizza
    {
      private List toppings = new LinkedList();
    
      public List getToppings()
      {
        return this.toppings;
      }
    }
    

  9. Now run JUnit again ...does the test work?

4.2.9 Recap

``Whoa ...that went fast ...how about a recap?'' No problem. Here's what we did:

  1. Write one test.
  2. Compile the test. It should fail (because you haven't implemented anything yet).
  3. Implement just enough to compile. (Refactor first if necessary.)
  4. Run the test and see it fail.
  5. Implement just enough to make the test pass.
  6. Run the test and see it pass.
  7. Refactor for clarity and "once and only once".
  8. Repeat from the top.

One of the great things to come out of the recent Extreme Programming movement has been to bring unit testing to the forefront of every developers thought process.