Unit Testing Woes

While unit testing is expected to be an integral part of every development effort, it is often not given its due importance during planning stages.

Let’s start with the question “what is unit testing”?

It is the effort of selecting a unit of code and writing an independent set of code that exercises one or more features of that unit.

You can define the unit as either a class or a set of classes that work together to deliver a certain feature. I would not waste time writing unit test for every java class you create. Select a logical unit of work that suites your needs (and project schedule) and write tests against that. Make sure the test exercises your code and not integration with other developers’ units. It is a good idea to write integration test cases but start with your own unit first. If you have dependencies with other components you can choose the strategy of mocking those components. This way you are free to test your code and do not get bogged down by other components. Let’s say you are testing a backend component that accepts credit card information from users, does some validations on the user data, stores it in the database and then submits the transaction to a credit card authorization provider. Obviously you are not interested in testing the authorization provider’s code and you may not even have access to that provider during development. You can write a few classes and mock out the provider or write a simple emulator to emulate the actions of the provider. Later on, as part of your developer integration testing, you can swap in the real providers test environment for more thorough testing.

Often the importance of unit testing is not understood. During a recent project status meeting, for a release that I was not actively involved in, the build manager asked “should we run the automated unit tests that used to run every night during the previous release?” Everyone stared at each other with a silence and then there were some quiet shaking of the heads to say “no”. I was shocked to see this reaction. The team for the current release was not writing a single new test case and on top of that had the boldness to choose not to run any of the tests that already existed. The unit test execution was completely automated. You add your unit test case class name to an XML file and that’s it. It would be included in the nightly build and test cycle. Come next morning and you have a neat HTML page with the complete test results. Finally it was decided that once development finishes everyone can spend a week and write test cases. I disagree with this approach too, since it comes a little too late. It was disappointing to say the least.

When experienced developers make this choice, how do we convince everyone else about the importance of unit testing? Let’s start with some questions you can ask yourself. How do you know if the “thing” you coded works? How do you test this “thing” everyday to make sure it still works? How does someone else make sure that the “thing” continues to work long after you, the original developer, are off the project? The answer is obvious. Write unit tests.

Some of the challenges often encountered in projects are:

  • Schedule is tight and has not accounted for unit testing. So developers have no time to write unit tests.
  • Management (often inexperienced in good software development practices) does not understand the importance of unit testing.
  • Developers do not take the time to write tests.
  • Test coverage cannot be achieved on all portions of the system and that sometimes causes misunderstanding. Questions like “hey you said you had unit tests for A, B and C, why is there none for X and Y scenarios”. Please understand that for a fast paced project getting some unit tests out there is better than none. Writing unit test is a coding task that requires time.

Remember that in spite of unit tests, defects will show up during testing. On a recent project the QA lead went on to make this an issue. Often these kinds of statements come from immature managers/leads that really have very little software development process background. Or they are just plain ignorant. Getting total coverage is impossible given the schedules for many fast-paced projects.

So what are some of the options for us Java developers to write unit tests? There are many options but I will cover the following in brief:

  1. JUnit.
  2. TestNG.
  3. Custom framework using JDK 5.0 annotations.

JUnit

JUnit has been around for a while now. It is a simple framework that allows one to write Java test classes. Your classes follow a certain convention in naming the test methods.

import junit.framework.*;

public class MyTestCase extends TestCase {

protected void setUp() {

.. set up test data …

}

protected void tearDown() {

}

public void testAddVisaTransaction() {

}

public void testAddMasterCardTransaction() {

}

}

  • Your class extends TestCase which is a JUnit framework class.
  • You can optionally extend the framework method setUp and do just that.
  • Name your test methods starting with the word ‘test’.
  • You can optionally extend the framework method tearDown to perform some clean up.
  • You can choose to group your tests using a TestSuite by overriding the method “public static Test suite()”. This allows you to run multiple tests and execute them in a certain order.

You can easily integrate the execution of the tests in an Ant script. You can use the optional JUnit and JUnitReport ant tasks to execute the unit tests and produce a nice HTML report of the test results. Setting this up should not take you more than a day. Plug this into your nightly build cycle and you have a simple yet immensely powerful automated unit testing execution strategy. Time spent on this is time well spent.

TestNG

TestNG is a nice little framework that takes a slightly different approach in the way you write your test classes. Suffice it to say it’s less intrusive (that is if you can call JUnit intrusive). With TestNG you do not extend any framework classes nor do you have to name your tests methods in any particular format. You use annotations (either JDK 5.0 annotations or javadoc style annotations if you are using JDK 1.4.x). Let’s see an example with JDK 5.0 annotations.

import org.testng.annotations.*;

public class MyTestCase {

@Configuration(beforeTestClass = true)

public void setUp() {

.. set up test data …

}

@Test(groups = { “mygroup” })

public void testAddVisaTransaction(){

}

@Test(groups = { “mygroup” })

public void testAddMasterCardTransaction() {

}

}

Personally I like this approach better. With the introduction of annotations in JDK 5.0 this style is definitely going to become the preferred approach. Note you can name the above methods with any name you choose. I simply ported the previous JUnit test to TestNG.

Custom framework using JDK 5.0 Annotations

I would suggest you stick with TestNG, but if you want to create your own framework its not too hard now. Annotations are probably the most important new feature in JDK 5. While I have seen some examples of code with annotations which almost drove me up the wall, in most cases it is a much calmer experience.

Let’s create a set of custom annotations to create a simple test framework. We will create the following annotations

1. @TestCase – will be used to mark a class as a test case. Will allow the tester to give a description to the test case.

2. @Setup – Used to mark one or more methods as set up methods.

3. @Test – Used to designate a method as a test method to execute.

First we will create the @TestCase annotation. This will be used to mark the class as a test case and provide some useful description of the test class.

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

public @interface TestCase {

String description();

}

· Import the annotation java classes from java.lang.annotation.

· @interface – keyword is used to mark the, otherwise regular, java class as an annotation definition class.

· The annotation we are creating is itself annotated with meta-annotations.

· @Retention(RetentionPolicy.RUNTIME) – these annotations can be read at run-time.

· @Target(ElementType.METHOD) – this annotation only applies to methods.

Next we will create the @Setup annotation. This one is used to mark the methods that are set up in nature. These will run before any of the tests are executed.

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface Setup {

}

Finally we will create our @Test annotation that marks individual methods as test methods which are to be executed.

Import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface Test {

}

Now let’s use these annotations in a sample test case class.

import com.unittest.*;

@TestCase (description=”My test case description.”)

public class MyTestCase {

@Setup

public void setUpShit() {

System.out.println(“Setup invoked.”);

}

@Test

public void doTest1() {

System.out.println(“doTest1 invoked.”);

}

@Test

public void runTest2() {

System.out.println(“doTest1 invoked.”);

}

}

I am sure you will agree that the entire exercise so far is not complicated in any way. I am using the latest Eclipse 3.1 to write this code. Eclipse 3.1 supports building custom annotations and it will invoke the annotation compiler for you. Now to create the test harness that will execute the tests. The class java.lang.Class has been updated in JDK 5.0 to support annotations. You will see that in the next sample code.

import java.lang.reflect.*;

import com.unittest.*;

public class TestRunner {

public static void main(String[] args) throws Exception {

Class testClass = Class.forName(args[0]);

// Check if the method is annotated with @TestCase.

if (!testClass.isAnnotationPresent(TestCase.class)) {

System.out.println(“Test classes must be annotated with @TestCase.”);

System.exit(1);

}

// Print the test case description.

TestCase testCase = (TestCase) testClass.getAnnotation(TestCase.class);

System.out.println(“TestCase description ==>> ” + testCase.description());

// Get an instance of the target test case to be executed.

MyTestCase target = (MyTestCase) testClass.newInstance();

// Execute only the 1st @Setup annotated method (if one exists).

for (Method method : testClass.getDeclaredMethods()) {

if (method.isAnnotationPresent(Setup.class)) {

method.invoke(target);

break;

}

}

// Execute the @Test annotated methods.

for (Method method : testClass.getDeclaredMethods()) {

if (method.isAnnotationPresent(Test.class)) {

method.invoke(target);

}

}

}

}

That’s it we are done. Pass the test case class name to the TestRunner and you will see your test executing. To keep this simple I have not included any exception handling to any of the code above.

Conclusion

There are many options to build your unit testing strategy. Pick any one that suites your needs but just use something.