The Power of Unit Testing

The purpose of Unit Testing is to verify for the developer that a software unit does what it is supposed to and is fit for use. The confidence that the developer gets, gives the developer courage to do other useful practices like Refactoring.

Unit testing is often used to test complex units with one or more collaborators, usually with the help of mock objects. But the greatest value with unit testing, in my opinion, lies in testing the lowest level utility methods; the building blocks that simply have to be bullet-proof in order for the rest of the system to have a stable foundation.

Let’s say that we have an Article domain object, storing article numbers from the database in the form “01234567”:

public class Article {

    /**
     * 8 character numeric article number, eg "01234567".
     */
    private String articleNumber = "";

    public String getArticleNumber() {
        return articleNumber;
    }

    public void setArticleNumber(String articleNumber) {
        this.articleNumber = articleNumber;
    }
}

Business now demands that the article number should be formatted with dots here and there. They have added the story “User should see article numbers formatted as 123.456.78” to the Sprint Backlog.

We decide to write a test for this:

import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class ArticleTest {

    @Test
    public void testGetFormattedArticleNumberNoZeros() {
        Article tested = new Article();
        tested.setArticleNumber("98765432");
        assertEquals("987.654.32", tested.getFormattedArticleNumber());
    }
}

We search around for a while, and find several nice tools for parsing formatted strings, but nothing useful for writing formatted strings that will help us in our scenario. Lacking ideas, we desperately try DecimalFormat. It’s actually not that bad. If we divide by 100, we’ll automatically get the right-most dot, don’t we? We decide to give it a try:

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;

public class Article {

    /**
     * 8 character numeric article number, eg "01234567".
     */
    private String articleNumber = "";

    public String getArticleNumber() {
        return articleNumber;
    }

    public void setArticleNumber(String articleNumber) {
        this.articleNumber = articleNumber;
    }

    /**
     * Retrieves the formatted article number.
     *
     * TODO I didn't find a nice generic formatting method, so I wrote this terrible hack.
     *
     * @return the formatted article number, eg 123.456.78
     */
    public String getFormattedArticleNumber() {
        NumberFormat nf = NumberFormat.getNumberInstance();
        DecimalFormat df = (DecimalFormat) nf;
        BigDecimal number = new BigDecimal(articleNumber);
        number = number.divide(new BigDecimal(100));
        String str = df.format(number);
        return str;
    }
}

We get a failure:

expected:<987[.654.]32> but was:<987[ 654,]32>

The decimal point is a comma (,) in the default locale, and the grouping separator is a blank. We can tweak that by calling the getDecimalFormatSymbols method, giving us a DecimalFormatSymbols that we can change:

DecimalFormatSymbols symbols = df.getDecimalFormatSymbols();
symbols.setDecimalSeparator('.');
symbols.setGroupingSeparator('.');
df.setDecimalFormatSymbols(symbols);

We also make sure the grouping size is always three by calling df.setGroupingSize(3). Here is the code so far:

public String getFormattedArticleNumber() {
    NumberFormat nf = NumberFormat.getNumberInstance();
    DecimalFormat df = (DecimalFormat) nf;
    df.setGroupingSize(3);
    DecimalFormatSymbols symbols = df.getDecimalFormatSymbols();
    symbols.setDecimalSeparator('.');
    symbols.setGroupingSeparator('.');
    df.setDecimalFormatSymbols(symbols);
    BigDecimal number = new BigDecimal(articleNumber);
    number = number.divide(new BigDecimal(100));
    String str = df.format(number);
    return str;
}

Success! Excellent. Now, we suspect that there might be problems with leading zeros, so we add another test:

    @Test
    public void testGetFormattedArticleNumberLeadingZero() {
        Article tested = new Article();
        tested.setArticleNumber("01234567");
        assertEquals("012.345.67", tested.getFormattedArticleNumber());
    }

As we suspected. We get a failure:

expected:<[0]12.345.67> but was:<[]12.345.67>

This can be fixed by setting the minimum integer size to 6:

df.setMinimumIntegerDigits(6);

We run the test suite again. Success! Great. We suspect that trailing zeros can also be troublesome. We add a few tests for that:

    @Test
    public void testGetFormattedArticleNumberSingleTrailingZero() {
        Article tested = new Article();
        tested.setArticleNumber("98765430");
        assertEquals("987.654.30", tested.getFormattedArticleNumber());
    }

    @Test
    public void testGetFormattedArticleNumberTrailingZeros() {
        Article tested = new Article();
        tested.setArticleNumber("98765400");
        assertEquals("987.654.00", tested.getFormattedArticleNumber());
    }

    @Test
    public void testGetFormattedArticleNumberAllZeros() {
        Article tested = new Article();
        tested.setArticleNumber("00000000");
        assertEquals("000.000.00", tested.getFormattedArticleNumber());
    }

We run the tests. The last three tests all fail, indicating that trailing zeros are lost. We fix that by setting the minimum fraction size to 2:

df.setMinimumFractionDigits(2);

Success! The complete method now looks like this:

/**
 * Retrieves the formatted article number.
 *
 * TODO I didn't find a nice generic formatting method, so I wrote this terrible hack.
 *
 * @return the formatted article number, eg 123.456.78
 */
public String getFormattedArticleNumber() {
    NumberFormat nf = NumberFormat.getNumberInstance();
    DecimalFormat df = (DecimalFormat) nf;
    df.setGroupingSize(3);
    df.setMinimumIntegerDigits(6);
    df.setMinimumFractionDigits(2);
    DecimalFormatSymbols symbols = df.getDecimalFormatSymbols();
    symbols.setDecimalSeparator('.');
    symbols.setGroupingSeparator('.');
    df.setDecimalFormatSymbols(symbols);
    BigDecimal number = new BigDecimal(articleNumber);
    number = number.divide(new BigDecimal(100));
    String str = df.format(number);
    return str;
}

It’s not pretty, but it works. We check in the code, relax, and think for a while. Is there a simpler way? Of course there is:

/**
 * Retrieves the formatted article number.
 *
 * @return the formatted article number, eg 123.456.78
 */
public String getFormattedArticleNumber() {
    StringBuffer number = new StringBuffer(articleNumber);
    number.insert(3, '.');
    number.insert(7, '.');
    return number.toString();
}

Here, the power of unit testing shows. We don’t even think twice about replacing the existing code. The tests still run, so we hook this code into the user interface, run the functional tests, check in, wait for a successful build, and finally ask the Product Owner to run the acceptance tests for the story.

This Post Has 9 Comments

  1. Hi, nice posts there :-) thank’s for the interesting information

  2. Good.. Nice Article..

    -jigs4all

  3. Nice! This is why unit testing is so addictive!

    You probably also want to add validation to the setArticleNumber that the number actually have the correct format, ie 8 digits. And maybe even refactor out the ArticleNumber into its own class, DDD style?

  4. Nice article… Is there a way to test a public method that contains more than one private methods in it??

  5. If you are OK with the private methods being run as well, then simply call the public method in your test. However, if you want to stub out some or all of the private methods, you have a few options. You could change the access modifier of the private methods that you want to stub out to package private, and then override them in a subclass, which you then test instead of the original class. Or, if you run Java5 or higher, you could use PowerMock to mock any private methods you want.

  6. Yes, i have been using java5 with powermock and junit3. However when i am creating a mock object and using expectPrivate() to test the private method, why is that the actual method gets invoked? As far as i know, creation of a mock will simulate the method to be tested and will not invoke the actual method. Please help me with your feedback.

  7. I think you should report an issue here and discuss this further with the PowerMock guys.

  8. Seema, you’re probably not preparing the correct classes for test using the PrepareForTest annotation. You must prepare the class containing the private method that you want to mock. Have a look at the examples in the documentation at http://www.powermock.org.

  9. wow..great help….as i was having the same problem like that of seema…thanks to both of you

Leave a Reply

Close Menu