Test driven development or TDD has been around for a long time and like many software principles and best practices evokes sharp responses from believers who preach the dogma with religious fervor to the non believers who dismiss anything that the "other side" says.  Obviously the truth is somewhere in between.  In this blog, I attempt to have a practical and common sense take on this topic and hopefully make the case that having good quality automated unit tests that are written using the steps outlined here will improve the overall quality.  Rather than talk about TDD in theory (a simple web search will yield more than enough results to keep you busy for a long time) I outline how I usually write code.  Some people may recognize these steps and call it TDD while purists may argue that it's not.  Regardless, I find the steps I outline here to be quite effective in producing quality code that is automated, reliable and predictable - after all isn't that what we're ultimately after.

 

One of the first things I did when I started this project was to get IntelliJ licenses for everyone in our group.  Eclipse is a fine free IDE but I think the enterprise version of IntelliJ packs in a number of features that significantly improve productivity.  Although nothing I mention in these blogs is specific to an IDE, I may occasionally highlight how easy it is to accomplish certain tasks in IntelliJ.  I'm sure there is equivalent functionality in any other IDE that you may use for your daily development but that is really not my focus here.

 

Let's assume that you have to write a class that has a method to interact with an external system using an external client Java library method.  Through the progression of steps, I want to demonstrate how a test driven approach might allow you to make your code unit testable as well as influence the design of your code.  Writing code with a test/tests in mind will naturally lead to good design methodologies like inversion of control and programming to interfaces.

 

// import statements omitted for brevity
public class MyExternalServiceClientWrapper {
     private static final Logger LOGGER = LoggerFactory.getLogger(MyExternalServiceClientWrapper.class);

     public SomeResult performService(Object args) throws ExternalServiceException {
          ExternalServiceClient client = new ExternalServiceClient();
          try {
               return client.performService(args);
          }
          catch(ExternalClientException1 | ExternalClientException2 e) {
               LOGGER.error("Exception from external service", e);
               throw new ExternalServiceException(e);
          }
     }
}



 

Here ExternalServiceClient is some thirdparty client that we're creating a wrapper for that presumably does something useful.  The wrapper may have some other methods that it has (thereby making it useful) but are not really relevant for the purpose of this example.  At first blush, this class seems to not be unit testable - after all it's purpose is to interact with another system and return the results and that's typically out of the purview of a system test and more in the realm of integration tests.  But let's step back and take a look at this class from the standpoint of the clients that may use this class.  From the clients' perspective, this class takes some arguments, performs some service with those arguments, and returns a result (SomeResult).  In the event that an error occurs it throws a single exception, ExternalServiceException.  The client itself doesn't really care what thirdparty library provides the capability - indeed that is just encapsulated inside the wrapper class and should be completely transparent to the client.

 

NOTE - These steps are not prescriptive.  This is just one way to get started with writing tests concurrently with writing your code.  Indeed as you get more experienced at this you will skip a lot of these steps and get to the end goal in one shot.

 

STEP 1 CREATE TEST METHOD

Once I start creating my method I will usually immediately create a test for that method

Light-Bulb-icon.pngTyping Alt-Enter when the cursor is on the class name in IntelliJ displays a Create Test option which is an easy way to do this


This should create a class called MyExternalServiceClientWrapperTest.  Based on the capabilities of the wrapper class, we can immediately think of at least two tests (there will be more as the class evolves), one where the call to performService succeeds and one where it throws an exceptions.  There will be other tests that test if the input arguments are valid etc. but those are omitted for the time being for brevity.  I prefer to use TestNG for my tests since it has a few more annotations/support that make it more powerful but the general concept here applies equally to Junit or any other framework you might choose for your automated tests.

 

public class MyExternalServiceClientWrapperTest {

    @Test
    public void mustCallExternalServiceAndReturnSomeResultSuccesfully() {

    }

    @Test(expectedException = ExternalServiceException.class)
    public void mustThrowExceptionIfExternalServiceReturnsError() {

        // test implementation
    }
}




So how do we test this class that makes an external service call.  There are a couple of approaches:

 

  1. Mock the service: Mock the external service client, using mock frameworks such as Mockito, and set the mock object to return the appropriate result when the performService method is invoked (either return a result or throw an exception in our case).
  2. Dependency Injection: Inject the external service client as a dependency into the wrapper class.  This technique is only useful if we are able to send different implementations of the client into the wrapper.  If we did that then we could inject a test implementation of a client wrapper that could be controlled to return different results from the test code.

I generally prefer the second approach because the first approach can become quite complicated to debug and test.  For instance if the thirdparty class exposed the performService method as a static method, then we need to use PowerMockito which complicates matters even more.  At best it's a distraction from the task at hand (testing our wrapper class) and at worst it could be a significant drain on time - the test itself taking much longer to complete than the actual code.  Dependency injection is a simple yet powerful and very effective technique to achieve the same end result and results in a simpler design.

 

STEP 2 MAKE IMPLEMENTATION TESTABLE

With time you will naturally make your implementation testable to begin with but since we have an implementation that's currently not testable directly let's refactor it to use the dependency injection approach.  I'll do this in two steps introducing the design concept of interface driven classes to make the class truly testable.  Applying dependency injection means the class now looks like this.

 

// import statements omitted for brevity
public class MyExternalServiceClientWrapper {
     private static final Logger LOGGER = LoggerFactory.getLogger(MyExternalServiceClientWrapper.class);

     private final MyExternalClient client;

     public MyExternalServiceClientWrapper(MyExternalClient client) {
       this.client = client;
     }

     public SomeResult performService(Object args) throws ExternalServiceException {
      
          try {
               return client.performService(args);
          }
          catch(ExternalClientException1 | ExternalClientException2 e) {
               LOGGER.error("Exception from external service", e);
               throw new ExternalServiceException(e);
          }
     }
}


 

Note that even though we have injected the client into the class, this class is still not testable since the implementation is hard wired to the actual external client implementation.  In order to make this truly injectable, we need to allow the implementations to vary at the client's discretion.  In other words, a client should be able to specify which implementation of a client it wants to use.  To make the wrapper independent of a particular implementation we define an interface and make the wrapper depend only on the interface.

  • Define an Interface

public interface ExternalClientService {
   SomeResult performService(Object args) throws ExternalServiceException
}


  • Provide an implementation of this interface using the real client
public class ExternalClientServiceImpl implements ExternalClientService {

     @Override
     public SomeResult performService(Object args) throws ExternalServiceException {
          ExternalClient client = new ExternalClient();
          return client.performService(args);
     }
}


 

  • Provide a test implementation of this interface for unit testing
public class TestExternalClientServiceImpl implements ExternalClientService {

     private TestMode testMode;

     public TestExternalClientServiceImpl (TestMode mode) {
          this.testMode = mode;
     }

     public enum TestMode {
          SUCCESS, EXCEPTION;
     }
     @Override
     public SomeResult performService(Object args) throws ExternalServiceException {
          if(testMode == SUCCESS) {
               return new SomeResult();
          }
          else {
               throw new ExternalServiceException();
          }
     }
}


There are a couple of important points to notice in the test implementation:

  • The number of test modes are totally up to the developer - create as many enums as you need to reflect the different test cases;
  • The return value of this class should be asserted in the calling program.  One way to do this would be to set a custom return value depending on the mode and return it from the test implementation of the service;

The wrapper class design now becomes

// import statements omitted for brevity
public class MyExternalServiceClientWrapper {
     private static final Logger LOGGER = LoggerFactory.getLogger(MyExternalServiceClientWrapper.class);

     private ExternalClientService client;

     public MyExternalServiceClientWrapper(ExternalClientService client) {
       this.client = client;
     }

     public SomeResult performService(Object args) throws ExternalServiceException {
          // code omitted for clarity
          return client.performService(args);
     }
}


  • Write unit test for method

The unit test can now be written as follows:

public class MyExternalServiceClientWrapperTest {

    @Test
    public void mustCallExternalServiceAndReturnSomeResultSuccesfully() {
          ExternalClientService clientService = new TestExternalClientServiceImpl(SUCCESS);
         MyExternalServiceClientWrapper wrapper = new MyExternalServiceClientWrapper(clientService);
          // create args here ...
          // ....

          SomeResult result = wrapper.performService(args);
          // assert results here
          // ...
    }

    @Test(expectedException = ExternalServiceException.class)
    public void mustThrowExceptionIfExternalServiceReturnsError() {
          ExternalClientService clientService = new TestExternalClientServiceImpl(EXCEPTION);
          MyExternalServiceClientWrapper wrapper = new MyExternalServiceClientWrapper(clientService);
          // create args here ...
          // ....

          SomeResult result = wrapper.performService(args);
          // expecting exception so test should not come here
          assertTrue(false, "Should not come here since method throws exception");
   
    }
}


This blog demonstrated several best practices:

  1. Use of interfaces to make code testable
  2. Use of dependency injection to make code testable
  3. An iterative process to develop/test/refactor/test code


DEVELOPMENT/UNIT TESTING ANTI-PATTERNS

I have observed several practices that people do that I consider to be anti-patterns to be avoided.  Some of them are worse than others but if at all possible we should never do the following:

 

  1. One developer to write code and another to write the test concurrently:  This is a practice I have seen that is some convoluted combination of Pair-programming and TDD.  Just because the tests are being written concurrently doesn't make it TDD, nor is it pair programming.  The iterative process is critical to writing good code and there's no way to achieve that if they are done by two different people.  This is a terrible practice to be avoided at all costs;
  2. Writing tests after development is complete: To me this results in writing tests for code that you have written already rather than writing good tests for the code you're about to write.  While it is better than the previous step or better than not having tests at all, it probably just results in good line coverage but overall poorer quality of tests.  Writing test is not a necessary evil we are forced to live with, nor is it a chore.  It is an integral part of the development process.  We have 5000+ unit tests that we run but a number of regressions get introduced and don't get caught by unit tests that should be caught by unit tests.  Writing tests concurrently increases the quality of the code.  I usually like to see a ratio of 2 or 3 : 1 in terms of lines of test code versus the code that's being tested (don't use this as a quality metric - I use it to ask the question if there is enough test code nothing else.  We could have a 10:1 ratio of meaningless tests and it won't mean you have produced high quality code);
  3. Making the tests dependent on each other: Each test method MUST be standalone and have its own fixture and assertions.  Tests should not have side effects on other tests and running concurrently.  Writing good tests takes time and planning and should not be rushed.  They need to be done deliberately and thoughtfully.

Finally it is important to distinguish between concepts of Dependency Injection (DI) and the containers that provide support for them.  Dependency injection as a concept is independent of any Inversion of Control (IoC) container like Spring, or Jersey.  If you noticed, the tests I wrote in this article, used DI but did not make any reference to Spring.  One could have used Spring to manage the Java class lifecycles and aid with DI but it is not necessary to do so.  DI as a design pattern is a very powerful pattern that should be extensively used. Furthermore, the DI pattern showed here injected the dependency in a constructor.  This is again not mandated.  You could equally have created a property and called a setter method to set the value of the dependency - and that would still be considered DI.