JUnit Primer

Summary

This article demonstrates a quick and easy way to write and run JUnit test cases and test suites. We'll start by reviewing the key benefits of using JUnit and then write some example tests to demonstrate its simplicity and effectiveness.

The two-day, on-site Test-Driven Development with JUnit Workshop is an excellent way to learn JUnit and test-driven development through lecture and a series of hands-on exercises guided by Mike Clark.

Table of Contents

This article contains the following sections:

Why Use JUnit?

Before we begin, it's worth asking why we should use JUnit at all. The subject of unit testing always conjures up visions of long nights slaving over a hot keyboard trying to meet the project's test case quota. However, unlike the Draconian style of conventional unit testing, using JUnit actually helps you write code faster while increasing code quality. Once you start using JUnit you'll begin to notice a powerful synergy emerging between coding and testing, ultimately leading to a development style of only writing new code when a test is failing.

Here are just a few reasons to use JUnit:

Design of JUnit

JUnit is designed around two key design patterns: the Command pattern and the Composite pattern.

A TestCase is a command object. Any class that contains test methods should subclass the TestCase class. A TestCase can define any number of public testXXX() methods. When you want to check the expected and actual test results, you invoke a variation of the assert() method.

TestCase subclasses that contain multiple testXXX() methods can use the setUp() and tearDown() methods to initialize and release any common objects under test, referred to as the test fixture. Each test runs in the context of its own fixture, calling setUp() before and tearDown() after each test method to ensure there can be no side effects among test runs.

TestCase instances can be composed into TestSuite hierarchies that automatically invoke all the testXXX() methods defined in each TestCase instance. A TestSuite is a composite of other tests, either TestCase instances or other TestSuite instances. The composite behavior exhibited by the TestSuite allows you to assemble test suites of test suites of tests, to an arbitrary depth, and run all the tests automatically and uniformly to yield a single pass or fail status.

Step 1: Install JUnit
  1. First, download the latest version of JUnit, referred to below as junit.zip.

  2. Then install JUnit on your platform of choice:

    Windows

    To install JUnit on Windows, follow these steps:

    1. Unzip the junit.zip distribution file to a directory referred to as %JUNIT_HOME%.

    2. Add JUnit to the classpath:

      set CLASSPATH=%JUNIT_HOME%\junit.jar

    Unix (bash)

    To install JUnit on Unix, follow these steps:

    1. Unzip the junit.zip distribution file to a directory referred to as $JUNIT_HOME.

    2. Add JUnit to the classpath:

      export CLASSPATH=$JUNIT_HOME/junit.jar

  3. Test the installation by using either the textual or graphical test runner to run the sample tests distributed with JUnit.

    Note: The sample tests are not contained in the junit.jar, but in the installation directory directly. Therefore, make sure that the JUnit installation directory is in the CLASSPATH.

    To use the textual test runner, type:

    java junit.textui.TestRunner junit.samples.AllTests

    To use the graphical test runner, type:

    java junit.swingui.TestRunner junit.samples.AllTests

    All the tests should pass with an "OK" (textual runner) or a green bar (graphical runner). If the tests don't pass, verify that junit.jar is in the CLASSPATH.

Step 2: Write a Test Case

First, we'll write a test case to exercise a single software component. We'll focus on writing tests that exercise the component behavior that has the highest potential for breakage, thereby maximizing our return on testing investment.

To write a test case, follow these steps:

  1. Define a subclass of TestCase.

  2. Override the setUp() method to initialize object(s) under test.

  3. Optionally override the tearDown() method to release object(s) under test.

  4. Define one or more public testXXX() methods that exercise the object(s) under test and assert expected results.

The following is an example test case:

Example JUnit Test Case
import junit.framework.TestCase;

public class ShoppingCartTest extends TestCase {

    private ShoppingCart cart;
    private Product book1;

    /**
     * Sets up the test fixture.
     *
     * Called before every test case method.
     */
    protected void setUp() {

        cart = new ShoppingCart();

        book1 = new Product("Pragmatic Unit Testing", 29.95);

        cart.addItem(book1);
    }

    /**
     * Tears down the test fixture.
     *
     * Called after every test case method.
     */
    protected void tearDown() {
        // release objects under test here, if necessary
    }

    /**
     * Tests emptying the cart.
     */
    public void testEmpty() {

        cart.empty();
    
        assertEquals(0, cart.getItemCount());
    }

    /**
     * Tests adding an item to the cart.
     */
    public void testAddItem() {

        Product book2 = new Product("Pragmatic Project Automation", 29.95);
        cart.addItem(book2);

        double expectedBalance = book1.getPrice() + book2.getPrice();
 
        assertEquals(expectedBalance, cart.getBalance(), 0.0);

        assertEquals(2, cart.getItemCount());
    }

    /**
     * Tests removing an item from the cart.
     *
     * @throws ProductNotFoundException If the product was not in the cart.
     */
    public void testRemoveItem() throws ProductNotFoundException {

        cart.removeItem(book1);

        assertEquals(0, cart.getItemCount());
    }

    /**
     * Tests removing an unknown item from the cart.
     *
     * This test is successful if the 
     * ProductNotFoundException is raised.
     */
    public void testRemoveItemNotInCart() {

        try {

            Product book3 = new Product("Pragmatic Version Control", 29.95);
            cart.removeItem(book3);

            fail("Should raise a ProductNotFoundException");

        } catch(ProductNotFoundException expected) {
            // successful test
        }
    }
}
(The complete source code for this example is available in the Resources section).
Step 3: Write a Test Suite

Next, we'll write a test suite that includes several test cases. The test suite will allow us to run all of its test cases in one fell swoop.

To write a test suite, follow these steps:

  1. Write a Java class that defines a static suite() factory method that creates a TestSuite containing all the tests.

  2. Optionally define a main() method that runs the TestSuite in batch mode.

The following is an example test suite:

Example JUnit Test Suite
import junit.framework.Test;
import junit.framework.TestSuite;

public class EcommerceTestSuite {
	
    public static Test suite() {

        TestSuite suite = new TestSuite();
	
        //
        // The ShoppingCartTest we created above.
        //
        suite.addTestSuite(ShoppingCartTest.class);

        //
        // Another example test suite of tests.
        // 
        suite.addTest(CreditCardTestSuite.suite());

        //
        // Add more tests here
        //

        return suite;
    }

    /**
     * Runs the test suite using the textual runner.
     */
    public static void main(String[] args) {
        junit.textui.TestRunner.run(suite());
    }
}
Step 4: Run the Tests

Now that we've written a test suite containing a collection of test cases and other test suites, we can run either the test suite or any of its test cases individually. Running a TestSuite will automatically run all of its subordinate TestCase instances and TestSuite instances. Running a TestCase will automatically invoke all of its public testXXX() methods.

JUnit provides both a textual and a graphical user interface. Both user interfaces indicate how many tests were run, any errors or failures, and a simple completion status. The simplicity of the user interfaces is the key to running tests quickly. You should be able to run your tests and know the test status with a glance, much like you do with a compiler.

To run our test case using the textual user interface, use:

java junit.textui.TestRunner ShoppingCartTest

The textual user interface displays "OK" if all the tests passed and failure messages if any of the tests failed.

To run the test case using the graphical user interface, use:

java junit.swingui.TestRunner ShoppingCartTest

The graphical user interface displays a Swing window with a green progress bar if all the tests passed or a red progress bar if any of the tests failed.

The EcommerceTestSuite can be run similarly:

java junit.swingui.TestRunner EcommerceTestSuite
Step 5: Organize the Tests

The last step is to decide where the tests will live within our development environment.

Here's the recommended way to organize tests:

  1. Create test cases in the same package as the code under test. For example, the com.mydotcom.ecommerce package would contain all the application-level classes as well as the test cases for those components.

  2. To avoid combining application and testing code in your source directories, create a mirrored directory structure aligned with the package structure that contains the test code.

  3. For each Java package in your application, define a TestSuite class that contains all the tests for validating the code in the package.

  4. Define similar TestSuite classes that create higher-level and lower-level test suites in the other packages (and sub-packages) of the application.

  5. Make sure your build process includes the compilation of all tests. This helps to ensure that your tests are always up-to-date with the latest code and keeps the tests fresh.

By creating a TestSuite in each Java package, at various levels of packaging, you can run a TestSuite at any level of abstraction. For example, you can define a com.mydotcom.AllTests that runs all the tests in the system and a com.mydotcom.ecommerce.EcommerceTestSuite that runs only those tests validating the e-commerce components.

The testing hierarchy can extend to an arbitrary depth. Depending on the level of abstraction you're developing at in the system, you can run an appropriate test. Just pick a layer in the system and test it!

Here's an example test hierarchy:

Example JUnit Test Hierarchy
AllTests (Top-level Test Suite)
    SmokeTestSuite (Structural Integrity Tests)
        EcommerceTestSuite
            ShoppingCartTestCase
            CreditCardTestSuite
                AuthorizationTestCase
                CaptureTestCase
                VoidTestCase
            UtilityTestSuite 
                MoneyTestCase
        DatabaseTestSuite
            ConnectionTestCase
            TransactionTestCase
    LoadTestSuite (Performance and Scalability Tests)
        DatabaseTestSuite
            ConnectionPoolTestCase
        ThreadPoolTestCase
Testing Idioms

Keep the following things in mind when writing JUnit tests:

Training and Mentoring

Reduce defects and improve design and code quality with a two-day, on-site Test-Driven Development with JUnit Workshop that quickly spreads the testing bug throughout your team.

I also offer JUnit mentoring to help your keep the testing momentum.

Contact me for more information.

Resources
About the Author

Mike Clark is a consultant, author, speaker, and most important, he's an experienced programmer. He is the developer of JUnitPerf, an open source collection of JUnit extensions for continuous performance testing. He also wrote the JUnit FAQ and serves as its editor for the JUnit community. Mike frequently writes and speaks on test-driven development using JUnit. He helps teams build better software faster through his company, Clarkware Consulting, Inc.