Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Introduction to Unit Testing

DZone's Guide to

Introduction to Unit Testing

Zone Leader John Vester introduces the concepts behind Unit Testing, and shows a few examples in practice.

· DevOps Zone
Free Resource

Download the blueprint that can take a company of any maturity level all the way up to enterprise-scale continuous delivery using a combination of Automic Release Automation, Automic’s 20+ years of business automation experience, and the proven tools and practices the company is already leveraging.

Image title

I've been a fan of writing unit tests for quite some time.  One of my favorite examples was when I worked with a team of developers re-writing an accounting process.  The team lead broke up the work for us so that we could target finishing around the same time.  The problem was, each of us required work from the other developers...work that was nowhere near complete.

After mapping out our expectations, we ended up using unit tests, mock objects, and stub objects as a way to mimic the incoming and outgoing data for our processes. It worked out quite well.

In this short series of articles, I plan to provide the following:

  • Simple introduction to unit tests (this article)

  • Introduction to mock and stub objects

  • Differentiating between unit tests and integration tests

  • Looking ahead

Since I have a strong background in Java, I am planning to focus on the Java programming language and the JUnit testing framework.  However, the concepts provided should translate well to other object-oriented languages.

Simple Example — the Candy Class

For simplicity's sake, consider the following Candy class:

public class Candy {
    private static Double OUNCE_CONVERSION = new Double("0.035274");
    private Integer id;
    private String name;
    private Integer size;

    public Candy() {
    }

    public Candy(Integer id, String name, Integer size) {
        this.id = id;
        this.name = name;
        this.size = size;
    }

    // getters and setters go here

    public Double getSizeInOunces() {
        if (this.size != null) {
            return new Double(this.size * OUNCE_CONVERSION);
        } else {
            return new Double("0");
        }
    }
}

The Candy object includes an id, a name and a size (which is stored in grams).  You'll notice there is a helper method in this class.  While this isn't a common practice, I added the getSizeInOunces() helper method to convert the size object from grams to ounces.  I also added a static Double variable used to house the conversion multiplier.  Again, this is merely for simplicity.

Simple Example — the CandyTest Class

In order to be a disciplined developer, a unit test needs to be created.  The test should validate the object can instantiated and we can validate the values stored in the object.  Since we have the helper method, tests should be written to validate the results.

Using JUnit, I established some static variables to house the data used for testing the object:

    public static final Integer ID_ONE = new Integer("1");
    public static final String NAME_GOOD_AND_PLENTY = new String("Good & Plenty");
    public static final Integer SIZE_GRAMS_51 = new Integer("51");
    public static final Double SIZE_GRAMS_51_IN_OUNCES = new Double("1.798974");
    public static final Double SIZE_GRAMS_NULL_IN_OUNCES = new Double("0");
    public static final Double SIZE_GRAMS_51_IN_OUNCES_INCORRECT = new Double("1.967527");

These variables will be used in the tests, to avoid duplicating them in the tests.

The first item I like to test is the "happy path" test, where everything works as expected:

    @Test
    public void standardTest() {
        Candy candy = new Candy(ID_ONE, NAME_GOOD_AND_PLENTY, SIZE_GRAMS_51);

        assertTrue(candy.getId().equals(ID_ONE));
        assertTrue(candy.getName().equals(NAME_GOOD_AND_PLENTY));
        assertTrue(candy.getSize().equals(SIZE_GRAMS_51));
        assertTrue(candy.getSizeInOunces().equals(SIZE_GRAMS_51_IN_OUNCES));
    }

The test establishes a new Candy object using the custom constructor, then validates the values match.  Finally, the getSizeInOunces() method is tested for accuracy.  

Next, two more tests are created.  The first, will set the candy size to null and the second makes sure the test asserts false when an incorrect gram to ounce conversion is suggested:

    @Test
    public void nullSizeTest() {
        Candy candy = new Candy();
        candy.setId(ID_ONE);
        candy.setName(NAME_GOOD_AND_PLENTY);
        candy.setSize(null);

        assertTrue(candy.getId().equals(ID_ONE));
        assertTrue(candy.getName().equals(NAME_GOOD_AND_PLENTY));
        assertTrue(candy.getSizeInOunces().equals(SIZE_GRAMS_NULL_IN_OUNCES));
    }

    @Test
    public void incorrectConversionTest() {
        Candy candy = new Candy(ID_ONE, NAME_GOOD_AND_PLENTY, SIZE_GRAMS_51);

        assertTrue(candy.getId().equals(ID_ONE));
        assertTrue(candy.getName().equals(NAME_GOOD_AND_PLENTY));
        assertTrue(candy.getSize().equals(SIZE_GRAMS_51));
        assertFalse(candy.getSizeInOunces().equals(SIZE_GRAMS_51_IN_OUNCES_INCORRECT));
    }

You'll notice the nullSizeTest() method uses the default constructor and setter methods.  This was chosen in order to achieve 100% code coverage.

The full test class is listed below:

public class CandyTest {
    public static final Integer ID_ONE = new Integer("1");
    public static final String NAME_GOOD_AND_PLENTY = new String("Good & Plenty");
    public static final Integer SIZE_GRAMS_51 = new Integer("51");
    public static final Double SIZE_GRAMS_51_IN_OUNCES = new Double("1.798974");
    public static final Double SIZE_GRAMS_NULL_IN_OUNCES = new Double("0");
    public static final Double SIZE_GRAMS_51_IN_OUNCES_INCORRECT = new Double("1.967527");

    @Test
    public void standardTest() {
        Candy candy = new Candy(ID_ONE, NAME_GOOD_AND_PLENTY, SIZE_GRAMS_51);

        assertTrue(candy.getId().equals(ID_ONE));
        assertTrue(candy.getName().equals(NAME_GOOD_AND_PLENTY));
        assertTrue(candy.getSize().equals(SIZE_GRAMS_51));
        assertTrue(candy.getSizeInOunces().equals(SIZE_GRAMS_51_IN_OUNCES));
    }

    @Test
    public void nullSizeTest() {
        Candy candy = new Candy();
        candy.setId(ID_ONE);
        candy.setName(NAME_GOOD_AND_PLENTY);
        candy.setSize(null);

        assertTrue(candy.getId().equals(ID_ONE));
        assertTrue(candy.getName().equals(NAME_GOOD_AND_PLENTY));
        assertTrue(candy.getSizeInOunces().equals(SIZE_GRAMS_NULL_IN_OUNCES));
    }

    @Test
    public void incorrectConversionTest() {
        Candy candy = new Candy(ID_ONE, NAME_GOOD_AND_PLENTY, SIZE_GRAMS_51);

        assertTrue(candy.getId().equals(ID_ONE));
        assertTrue(candy.getName().equals(NAME_GOOD_AND_PLENTY));
        assertTrue(candy.getSize().equals(SIZE_GRAMS_51));
        assertFalse(candy.getSizeInOunces().equals(SIZE_GRAMS_51_IN_OUNCES_INCORRECT));
    }
}

Simple Example — CandyTest Results

Running the tests with Clover Coverage in IntelliJ yields the following results:

Image title

All three tests passed with a green OK icon.  Reviewing the original Candy class shows 100% coverage as well:

Image title

Conclusion

In this simple example, a new class was created which included a simple calculation.  Using JUnit, a test class was created to validate the Candy class was functioning as expected.  

In the next article, I will introduce a Candy service class, which will require use of mock objects to validate the service logic without having to rely on any external objects or connectivity.

Have a really great day!

Download the ‘Practical Blueprint to Continuous Delivery’ to learn how Automic Release Automation can help you begin or continue your company’s digital transformation.

Topics:
java ,unit testing ,integration testing ,junit

Published at DZone with permission of John Vester, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}