Test Driven Development With Junit 4 0

I am facilitating as well learning this course at diycomputerscience.com

This is a 3 week course which explains how to use JUnit 4.0 It assumes Java programming skills and some background on unit testing.

Introduction

In this section we understand what Test Driven Development is, and why it is such an important practice in software development. After that we have to do 2 activities in which we create some test cases.

What is test driven development and why is it such an important practice in software development.

Quoting from the Wikipedia entry for TDD.

Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: first the developer writes a failing automated test case that defines a desired improvement or new function, then produces code to pass that test and finally refactors the new code to acceptable standards. Kent Beck, who is credited with having developed or 'rediscovered' the technique, stated in 2003 that TDD encourages simple designs and inspires confidence.

I would like to stress on 2 things mentioned in the above passage (in reverse order).

TDD encourages simple designs and inspires confidence

Why does TDD inspire confidence? Let's look at a very simple contrived code sample.

public class Calculator {
    public Object multiply(long n1, long n2) {
        return n1 + n2;
    }
}

Code: Listing 1

The code above is supposed to contain logic to multiply 2 values and return the product. The developer made a silly mistake and used the addition operator instead of the multiplication operator. What would be the result of such a mistake on the code base which contains this method? Things can go horrendously wrong. If this code base were part of a banking software, that bank would end up having many unhappy and perhaps broke customers.

Trust me such mistakes can happen, but what I am trying to convey is that in a large code base, there is a lot of potential for writing code that is logically incorrect, or code which causes side effects which cause things to go wrong.

The question is how do we prevent such mistakes from going into deployed systems? Well, we need to catch such mistakes early, before they get deployed in production.

Traditionally most software development teams have adopted a practice where developers wrote code, and testers tested the product and made sure that everything works fine. There are many things wrong with this scenario. When a tester tests a system they are typically testing a deployed system or sub-system. They test it by actually using it in various ways which would expose any bugs. However, this strategy is not fool-proof. However, hard a tester tries, and how many things they try, it is virtually impossible to exercise all paths of execution of a code base. This means that there will always be some code which will remain untested, and could possibly cause bugs in some corner case.

The solution is to test every piece of code that is written with another piece of code which exercises the code and verifies it's output. We consider a unit of code to be a method in a class. This unit testing is the practice of testing all the methods which we write.

first the developer writes a failing automated test case that defines a desired improvement or new function, then produces code to pass that test

When I started writing unit tests, I would typically write some code, and then write tests for that code. However, this order is incorrect. The correct order is to first write the test, then write just enough code to get the tests to compile (but fail), and then implement the code to get the tests to pass This method has two advantages. First, because we are writing tests first, we will never skip the tests and end up in a situation where we have code which is not tested. Another advantage is, when we invoke the code we are testing, in some way we are writing code which may be similar to the code which will use our actual code. This practice will help us discover flaws in code and API design at a very early stage.

First test case

Shown below is a very simple unit test written in a naive and primitive manner.

public class CalculatorTest {
    public void testMultiply() {
        Calculator calc = new Calculator();
        long ans = calc.multiply((long)2,(long)5);
        if(ans != 10) {
            throw new RuntimeException("The result of invoking the multiply method is incorrect. We expected 10, but got " + ans);
        }
    }

    /**
      * We will get an Exception in the console if any test passes
      */
    public static void main() {
        CalculatorTest calcTest = new CalculatorTest();
        calcTest.testMultiply();
    }
}

Code: Listing 2

The above code is a nice but primitive test case. I am sure many of you are aghast at the amount of hard coding we have in the test. There is so much room for improving the test code. That is exactly what JUnit does. It provides us a framework for writing test cases.

Now let us understand how JUnit can help us writing test cases. But first let us set up the development environment. In the video below, I am setting up development environment in Eclipse.

VIDEO - SETTING UP ECLIPSE

In this example we are going to focus on a simple class called Calculator which has two methods - add() and multiply(). We will focus on the production code (Calculator class) and the test cases to test that code.

The next video shown below gives an example of a correct test case, but an incorrect process. It is incorrect because I first write the actual code, then the test code. This is not test driven development.

VIDEO - THIS IS NOT TDD

The next video shows the creation of the same test case, but using a proper Test Driven Development process.

VIDEO - TEST CASE USING TEST FIRST DEVELOPMENT

Here is the code we have written till now. I am listing it below so you can look at it for a while before proceeding ahead.

public class CalculatorTest {

    @Test
    public void testAdd() {
            Calculator calculator = new Calculator();
        long n1 = 10;
        long n2 = 20;
        Assert.assertEquals((long)30, calculator.add(n1, n2));
    }

    @Test
    public void testMultiply() {
            Calculator calculator = new Calculator();    
        long n1 = 10;
        long n2 = 20;
        Assert.assertEquals((long)200, calculator.multiply(n1, n2));
    }
}

Code: Listing 3
public class Calculator {

    public Object add(long n1, long n2) {
        return n1 + n2;
    }

    public Object multiply(long n1, long n2) {
        return n1 * n2;
    }

}

Code: Listing 4

Fixtures

If you look carefully at code listing 3. You will notice that the code

Calculator calculator = new Calculator();

is repeated in both the test methods. This code is setting up an object (Calculator in this case) which is being used in the test case. In real test cases we may have one or more such objects that need to be setup for every test case in a test class. Typically these objects may be database connections, repository type objects, dependencies for the code which will be invoked. Setting up these objects not only required us to create the object, but also to bring up in a well known state. An example is populating a database with specific data before the test is run, and cleaning it up after the test completes.

This is known as Fixtures. JUnit allows us to annotate methods in the test case with @Before and @After. Methods which are annotated with @Before will be run before every method of the test class, and methods which are annotated with @After will be executed after every method of the test class is executed.

In the video below we use a method called setUp to create the Calculator object and make it available to the test case.

VIDEO - SETUP AND TEARDOWN

The code listing below shows our test case with setUp and tearDown methods.

public class CalculatorTest {

    private Calculator calculator;

    @Before
    public void setUp() {
        this.calculator = new Calculator();
    }

    @After
    public void tearDown() {
        this.calculator = null;
    }

    @Test
    public void testAdd() {
        long n1 = 10;
        long n2 = 20;
        Assert.assertEquals((long)30, this.calculator.add(n1, n2));
    }

    @Test
    public void testMultiply() {        
        long n1 = 10;
        long n2 = 20;
        Assert.assertEquals((long)200, this.calculator.multiply(n1, n2));
    }
}

Code: Listing 4

Testing with multiple data points

A good test plan required that we test a method with multiple data sets. If the multiply() method works with two positive integers, it may fail when one of the parameters is a zero, or maybe when one of the parameters is a negative number. For a complete test suite we need to test certain methods with multiple data sets.

Let's refactor the testMultiply() method to use multiple data sets.

@Test
public void testMultiply() {        
    Assert.assertEquals((long)200, this.calculator.multiply(10, 20));
    Assert.assertEquals((long)0, this.calculator.multiply(0, 20));
    Assert.assertEquals((long)-200, this.calculator.multiply(-10, 20));
    Assert.assertEquals((long)200, this.calculator.multiply(-10, -20));
}

This is a much better test case, because it tests the multiply method with different data (It is important to select the data wisely. For example having 10 such tests with only positive integers would hardly serve any purpose).

However, there has to be a better way of doing this. Why are we mixing the data with the tests? Good separation of concerns asks us to separate unrelated things. We should define the data in a different place. Well, that is exactly what the following annotation gives us:

@RunWith(Parameterized.class)

To create a test case which will be run with multiple data sets, we need to do the following. Quoting @aditi_mohan

  1. Annotate the class with @RunWith and specify Parameterized.class as the runner.
  2. Add local variables for the dataset, that can be accesses by the test.
  3. Add constructor which would take the dataset as parameters and populate local variable.
  4. Add a static method to return the dataset. Dataset would be a collection of arrays, where array maps on the data required by constructor. Annotate this with @Parameters .
  5. Create the test case to execute with local variables.

Running JUnit tests from an ANT script

The first activity for week 3 of the JUnit 4.0 course, is to run unit tests using an ANT script. Here is my script for running unit tests.

Reflections

I read this and this article, and thought about the process of Test Driven Development and using JUnit 4.0. These are some random reflections:

  • Whenever you are tempted to type something into a print statement or a debugger expression, write it as a test instead - Martin Fowler
  • There is a cost benefit ratio to writing test cases. Some test cases have a clear benefit in that they help us find bugs early, and also assist in regression testing. Some other test cases probably fall into the 'nice to have category'. They are good, but they do not add as much value. A silly but simple example for test cases which do not add much value are test cases that test getter and setter methods. If these methods are simple, then there is little value for such test cases. Remember, we only have a finite amount of time to write test cases, so select your test cases wisely.
  • Just writing test cases is not good enough. We also need to ensure that the tests are running all the time. A red bar (failing tests) should signify something that needs immediate attention.
  • Do not test for multiple conditions in one test case. Test for only one thing in one test case.
  • Test for expected Exceptions (JUnit 4.0 makes this much easier)
  • I really like JUnit 4.0 's parameterized tests, because they make it easy to run the same test case with a huge data set. It would be really nice to specify the data set in a text file, so we do not have to write confusing arrays in the getData() method.