2. The Example Problem

Every programmer knows they should write tests for their code. Few do. The universal response to "Why not?" is "I'm in too much of a hurry." This quickly becomes a vicious cycle:

The program we will write solves the problem of representing arithmetic with multiple currencies. You cannot just convert one currency into another for doing arithmetic since there is no single conversion rate - you may need to compare the value of a portfolio at yesterday's rate and today's rate.

Let's start simple and define a class Money to represent a value in a single currency.

class Money {
    private int fAmount;
    private String fCurrency;
    public Money(int amount, String currency) {
        fAmount= amount;
        fCurrency= currency;
    }

    public int amount() {
        return fAmount;
    }

    public String currency() {
        return fCurrency;
    }
}

When you add two Moneys of the same currency, the resulting Money has as its amount the sum of the other two amounts:

public Money add(Money m) {
    return new Money(amount()+m.amount(), currency());
}

Now, instead of just coding on, we want to get immediate feedback and practice "code a little, test a little, code a little, test a little".

JUnit defines how to structure your test cases and provides the tools to run them:

  1. You implement a test in a subclass of TestCase. To test our Money implementation we therefore define MoneyTest as a subclass of TestCase.

  2. Classes are contained in packages and we have to decide where to put MoneyTest. Our current practice is to put MoneyTest in the same package as the classes under test. In this way a test case has access to the package private methods.

  3. We add a test method testSimpleAdd, that will exercise the simple version of Money.add() above. A JUnit test method is an ordinary method without any parameters:

    public class MoneyTest extends TestCase {
        //…
        public void testSimpleAdd() {
            Money m12CHF= new Money(12, "CHF");  // (1)
            Money m14CHF= new Money(14, "CHF");        
            Money expected= new Money(26, "CHF");
            Money result= m12CHF.add(m14CHF);    // (2)
            Assert.assertTrue(expected.equals(result));     // (3)
        }
    }

The testSimpleAdd() test case consists of:

  1. Code which creates the objects we will interact with during the test. This testing context is commonly referred to as a test's fixture. All we need for the testSimpleAdd test are some Money objects.

  2. Code which exercises the objects in the fixture.

  3. Code which verifies the result.

Before we can verify the result we need a way to test that two Money objects are equal. We override the method equals. Before we implement equals let's a write a test for equals in MoneyTest.

public void testEquals() {
    Money m12CHF= new Money(12, "CHF");
    Money m14CHF= new Money(14, "CHF");

    Assert.assertTrue(!m12CHF.equals(null));
    Assert.assertEquals(m12CHF, m12CHF);
    Assert.assertEquals(m12CHF, new Money(12, "CHF")); // (1)
    Assert.assertTrue(!m12CHF.equals(m14CHF));
}

The equals method in Object returns true when both objects are the same: Two Monies are considered equal if they have the same currency and value. To test this property we have added a test (1) to verify that Monies are equal when they have the same value but are not the same object.

Next let's write the equals method in Money:

public boolean equals(Object anObject) {
    if (anObject instanceof Money) {
        Money aMoney= (Money)anObject;
        return aMoney.currency().equals(currency())
            && amount() == aMoney.amount();
    }
    return false;
}

As an aside, it is a recommended practice to also override the method hashCode whenever you override method equals.

assertTrue: With an equals method in hand we can verify the outcome of testSimpleAdd. In JUnit you do so by a calling Assert.assertTrue, which triggers a failure that is recorded by JUnit when the argument isn't true.

assertEquals: Since assertions for equality are very common, there is also an Assert.assertEquals convenience method. In addition to testing for equality with equals, it reports the printed value of the two objects in the case they differ.

Now that we have implemented two test cases we notice some code duplication for setting-up the tests. With JUnit you can initialize test variables by overriding the setUp method.

The symmetric operation to setUp is tearDown which you can override to clean up the test at the end.

public class MoneyTest extends TestCase {
    private Money f12CHF;
    private Money f14CHF;   

    protected void setUp() {
        f12CHF= new Money(12, "CHF");
        f14CHF= new Money(14, "CHF");
    }

    public void testEquals() {
        Assert.assertTrue(!f12CHF.equals(null));
        Assert.assertEquals(f12CHF, f12CHF);
        Assert.assertEquals(f12CHF, new Money(12, "CHF"));
        Assert.assertTrue(!f12CHF.equals(f14CHF));
    }

    public void testSimpleAdd() {
        Money expected= new Money(26, "CHF");
        Money result= f12CHF.add(f14CHF);
        Assert.assertTrue(expected.equals(result));
    }
}