JavaData & JavaSoftware Testing for Programmers, Part 2

Software Testing for Programmers, Part 2

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Whenever you are tempted to type something into a print statement or a debugger expression, write it as a test instead.
—Martin Fowler

In my previous article on software testing—Software Testing for Programmers, Part 1—I presented the basics of software testing for programmers. In this article, I illustrate how to do unit testing of Java applications with JUnit, an open source framework for testing Java programs.

JUnit

The core of JUnit is a set of classes and interfaces that provides an easy-to-use framework for building automated tests. It is comprised of the following:

TestCase Defines tests and fixtures
Test Defines the interface for running a test
TestSuite Collects and runs a set of tests
Assert Provides a set of assertion methods
TestRunner Runs Tests and TestSuites

Tests

Tests are defined in test cases. To create a test case:

  1. Create a class that extends junit.framework.TestCase.
  2. Write at least one method that starts with the word “test.”

For example, suppose that you are developing an application to help you make homebrew (that’s right, home-brewed beer), and in the application you have a class that models a batch of brew and calculates alcohol by volume (ABV). Here is the code for the ABV calculation:

import java.util.*;

public final class Brew {
   // ...

   /**
    * Calculate alcohol by volume.
    * abv = (og - fg) * 131.25
    */
   public double abv() {
      if (og < 1.0) {
         throw new ArithmeticException("OG < 1.0");
      }

      if (fg < 1.0) {
         throw new ArithmeticException("FG < 1.0");
      }

      return (og - fg) * 131.25;
   }
}

Here is a test case that tests Brew.abv():

import junit.framework.*;

public class AbvTest extends TestCase {
   /**
    * Construct an ABV test.
    */
   public AbvTest(String name) {
      super(name);
   }

   /**
    * Test abv().
    */
   public void testAbv() {
      Brew brew = new Brew();
      brew.setOrigGrav(1.042);
      brew.setFinalGrav(1.010);
      assertEquals(4.200000000000004, brew.abv(), 0.0);
   }
}

Assertions

Assertions are used to control the result of a test by verifying that expected conditions are met. If an assertion fails, the test fails. Otherwise, the test passes.

Assertion methods are defined in junit.framework.Assert and are inherited by TestCase. To implement an assertion, call the assertion method that is applicable to the expected condition. A message may be specified to provide details about the assertion if it fails.

Here are some examples of assertions:

// Assert that a given value equals an expected value:
assertEquals(1, x);

// Assert that a condition is true:
assertTrue("ABV exceeds 1.0", abv > 1.0);

// Assert that a condition is false:
assertFalse(abv > 1.0);

// Assert that an object is null:
assertNull("someObject is null.", someObject);

// Assert that an object is not null:
assertNotNull(someObject);

// Assert that two variables refer to the same object:
assertSame(obj1, obj2);

// Assert that two variables do not refer to the
// same object:
assertNotSame(obj1, obj2);

Exceptions

Testing for expected exceptions is done by catching the exception and forcing failure, by calling fail, if the exception is not thrown. As an example, here is the AbvTest test case from above with some exception tests added:

import junit.framework.*;

public class AbvExceptionTest extends TestCase {
   // ...

   /**
    * Test abv() with invalid OG.
    */
   public void testInvalidOrigGrav() {
      try
      {
         Brew brew = new Brew();
         brew.setOrigGrav(1.0);
         double abv = brew.abv();
         // Force failure.
         fail("ArithmeticException not thrown.");
      } catch(ArithmeticException e) {
         // success
      }
   }

   /**
    * Test abv() with invalid FG.
    */
   public void testInvalidFinalGrav() {
      try
      {
         Brew brew = new Brew();
         brew.setFinalGrav(1.0);
         double abv = brew.abv();
         // Force failure.
         fail("ArithmeticException not thrown.");
      } catch(ArithmeticException e) {
         // success
      }
   }
}

Fixtures

Fixtures are implemented in test cases and are used to set up and tear down objects that are common to the tests in the test case. To implement a fixture:

  1. Define private variables for the items in the fixture.
  2. Override setUp() to initialize the fixture items.
  3. Override tearDown to close any resources, such as files or database connections, that are opened in setUp().

Before a test is run, setUp() is called. After the test completes, whether it passed or failed, tearDown() is called. Each test gets a new fixure, so that one test does not cause side effects in another test.

Continuing with the homebrew example, suppose you add a method to Brew that calculates the bitterness of your brew. Here is the code:

public final class Brew {
   // ...

   /**
    * Calculate bitterness in Homebrew Bitterness Units
    * (HBU).
    * hbu = sum [1, n]: ounces * alpha acid
    */
   public double hbu() {
      double total = 0.0;

      for (Iterator i = hopsList.iterator();
                        i.hasNext(); ) {
         Hops hops = (Hops) i.next();
         total += hops.hbu();
      }

      return total;
   }
}

Here is a test case that implements a fixture and a couple of tests for Brew.hbu():

import junit.framework.*;

public class HbuTest extends TestCase {
   // Declare items in fixture.
   private Brew brew;
   private Hops hops1;
   private Hops hops2;

   /**
    * Construct an HBU test.
    */
   public HbuTest(String name) {
      super(name);
   }

   /**
    * Set up fixture.
    */
   protected void setUp() {
      brew  = new Brew();
      hops1 = new Hops(1.0, 1.0);
      hops2 = new Hops(2.0, 2.0);
   }

   /**
    * Test hbu() of zero hops.
    */
   public void testZeroHops() {
      assertEquals(0.0, brew.hbu(), 0.0);
   }

   /**
    * Test hbu() of one hops.
    */
   public void testOneHops() {
      brew.addHops(hops1);
      assertEquals(1.0, brew.hbu(), 0.0);
   }

   /**
    * Test hbu() of multiple hops.
    */
   public void testMultipleHops() {
      brew.addHops(hops1);
      brew.addHops(hops2);
      assertEquals(5.0, brew.hbu(), 0.0);
   }
}

Test Suites

Test suites are simply collections of tests. To create a test suite:

  1. Declare a public static method called “suite” that returns a Test.
  2. In the suite method, create a TestSuite, and then add tests or other suites to it.
  3. Return the test suite.

Here is an example of creating a test suite that will run all the test cases for Brew:

import junit.framework.*;

/**
 * AllTests is used to create a suite that contains
 * all tests.
 */
public class AllTests {
   public static Test suite() {
      TestSuite suite = new TestSuite();
      suite.addTest(new TestSuite(AbvTest.class));
      suite.addTest(new TestSuite(HbuTest.class));

      return suite;
   }
}

Runners

JUnit comes with two test runners: one that has a Swing interface and one that runs tests from the command line.

To run tests using the Swing runner, start junit.textui.SwingRunner.

To run tests from the command line:

java junit.textui.TestRunner [class]

Resources

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories