Unit Testing in Java

Let’s write some tests in Java.

JUnit

JUnit is the framework practically every Java programmer uses.

JUnit, developed by Kent Beck and Erich Gamma, is almost indisputably the single most important third-party Java library ever developed. As Martin Fowler has said, “Never in the field of software development was so much owed by so many to so few lines of code.” JUnit kick-started and then fueled the testing explosion. Thanks to JUnit, Java code tends to be far more robust, reliable, and bug free than code has ever been before. — Eliotte Harold

Here is a test for the MoneyBag class:

MoneyBagTest.java
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import java.math.BigDecimal;

import org.junit.Before;
import org.junit.Test;

/**
 * JUnit test for the money bag class.
 */
public class MoneyBagTest {

    private MoneyBag bag;

    /**
     * All tests should begin with a fresh bag.
     */
    @Before
    public void initializeBag() {
        bag = new MoneyBag();
    }

    /**
     * Tests that a bag is empty upon creation, and stays empty if you add a zero amount of
     * some currency, and that retrieving currencies that don't exist actually give a zero
     * amount instead of throwing an exception or otherwise crashing.
     */
    @Test
    public void testEmptyBag() {
        assertThat(bag.numberOfCurrencies(), is(0));
        assertThat(bag.toString(), is("[ ]"));
        bag.add(BigDecimal.ZERO, "USD");
        assertThat(bag.numberOfCurrencies(), is(0));
        assertThat(bag.getAmount("CHF"), is(BigDecimal.ZERO));
        assertThat(bag.getAmount("CAD"), is(BigDecimal.ZERO));
        assertThat(bag.getAmount("EEK"), is(BigDecimal.ZERO));
    }

    /**
     * Tests various methods on non-empty bags.
     */
    @Test
    public void testNonEmptyBag() {
        bag.add(BigDecimal.ONE, "USD");
        assertThat(bag.numberOfCurrencies(), is(1));
        assertThat(bag.getAmount("USD"), is(BigDecimal.ONE));
        bag.add(new BigDecimal("1.50"), "USD");
        assertThat(bag.numberOfCurrencies(), is(1));
        assertThat(bag.getAmount("USD"), is(new BigDecimal("2.50")));
        bag.add(new BigDecimal("200.0"), "COP");
        assertThat(bag.numberOfCurrencies(), is(2));
        assertThat(bag.toString(), is("[ COP:200.0 USD:2.50 ]"));
        assertThat(bag.getAmount("EEK"), is(BigDecimal.ZERO));
    }

    /**
     * Tests that making a currency value go to 0 removes the currency from the bag.
     */
    @Test
    public void testCancellation() {
        bag.add(BigDecimal.ONE, "USD");
        bag.add(new BigDecimal("1.50"), "USD");
        bag.add(new BigDecimal("200.0"), "COP");
        assertThat(bag.numberOfCurrencies(), is(2));
        bag.add(new BigDecimal("-2.50"), "USD");
        assertThat(bag.numberOfCurrencies(), is(1));
        assertThat(bag.toString(), is("[ COP:200.0 ]"));
    }

    /**
     * Tests that uses of illegal currency names throw the right exception.
     */
    @Test(expected = IllegalArgumentException.class)
    public void testTheeCharactersButNotAllUpperCase() {
        bag.getAmount("aBC");
    }

    @Test(expected = IllegalArgumentException.class)
    public void testNullCurrencyCode() {
        bag.getAmount(null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testLessThanThreeCharacters() {
        bag.getAmount("AB");
    }

    @Test(expected = IllegalArgumentException.class)
    public void testMoreThanThreeCharacters() {
        bag.getAmount("CCDE");
    }
}

Running JUnit from an IDE

If you are developing with an IDE like Eclipse, you can select “Run as JUnit test case” from a menu, and voila! If you don’t see the JUnit option, you’ll need to go to Project → Properties → Java Build Path → Libraries → Add Library and select JUnit. If Eclipse gives you a choice between JUnit 3 and JUnit 4 take 4. Please.

juniteclipse.png

Running JUnit from the command line

You could also run the test case directly from the command line (your jar name may be different from the one below, depending on your version):

$ javac -cp .:junit-4.12.jar MoneyBagTest.java

$ java -cp .:junit-4.12.jar org.junit.runner.JUnitCore MoneyBagTest
JUnit version 4.12
.......
Time: 0.125

OK (7 tests)

Running JUnit from Ant

In a large application you might have hundreds or thousands of test files, and you might launch all of them from an ant script. Something like this:

<project name="...">
    ...
    <target name="test" depends="compile-tests"
        description="Runs all unit tests">
        <junit printSummary="true"
            haltOnFailure="true"
            fork="true">
            <batchtest todir="${test.output.todir}">
                <fileset dir="${test.src.dir}">
                    <include name="**/*Test.java" />
                </fileset>
            </batchtest>
        </junit>
    </target>
    ...
</project>

Running JUnit from Maven

Any classes under src/test/java will be run automatically as part of the Maven test goal. You don’t have to do anything except write the application and the tests in the right places. (In fact, tests are run by default under Maven; you have to physically suppress them if you don’t want them to run, but don’t do that, ever.)

Make sure that JUnit is referenced from your pom, though. Place the following in the <dependencies> section:

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
  <scope>test</scope>
</dependency>

TestNG

This framework is popular, too. Check it out.