Skip navigation
All People > rkolliva > Kollivakkam Raghavan's Blog

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.

Every software developer knows the importance and significance of unit testing yet I am constantly surprised at how little we practice this very basic principle.  Most unit testing I have observed is done manually with little to no repeatability or predictability.  In a waterfall world, we may well have gotten away with it but as the world changes rapidly around us, this approach is no longer acceptable or even justifiable.  Continuous integration and Continuous Development (CI/CD) have evolved and matured to a point where not automating the building and testing of the software we write is surely going to lead to failure either because of quality problems (if we focus purely on velocity of feature development) or because we're too slow to introduce features (if we focus only on releasing with reasonably high quality).

 

Like many things, automation is also a multi-faceted problem:

 

  1. Build process automation: In this stage we run automated unit tests with every commit that goes into our SCM systems.  If we wrote good quality unit tests that cover a broad range of functionality, doing this should give us a high degree of confidence in making changes and ensure no regressions are introduced as a result.  We cannot write software in a fast changing world, without constantly improving the quality of the code and that necessarily involves refactoring and improving the code that provides functionality.  Tools like Maven, TestNG and JUnit are used (with Java code) make it relatively easy to achieve this automation;
  2. Integration/System Test Automation: In this phase of CI/CD, the entire system gets built using the build tool chains and a full set of automated integration tests are run against the system.  In this phase, in addition to building the individual components (perhaps using #1 above), the entire system is built from these components and deployed usually in a virtualized environment.  Once the system is deployed, the tests are run, results evaluated, and the systems are torn down if all the tests pass.  If the tests fail, the system state is preserved for debugging purposes. Tools like Jenkins or Hudson are used to make automating  system tests.

The focus of the next few blog articles will be on #1 above.  There are several aspects to be discussed in writing good automated unit tests.

  1. Test driven development: There are many great articles written on this topic that are worth reading but the focus of my blog is beyond the dogma or religious fervor with which this topic is discussed.  I'll focus more on some practical approaches which I have found useful in developing tests for my code while writing the code (instead of after the fact as it is usually done);
  2. Test independence: Tests must be written as much as possible to be independent and stand on their own.  I have seen dependencies created between tests that result in essentially making the entire test suite run only one test at a time (#4 below).  This not only significantly degrades build performance, but also makes it harder to change/add more tests because at some point we will be unable to track all the pre-requisites for a test;
  3. Randomize inputs: To write high quality tests there must be little to no hard coding of inputs.  This topic has not received much attention, but I think it is critically important to do to produce tests that are robust and stand the test of time;
  4. Multi-threaded: This is a critically important aspect.  In our current project we got to 5000+ unit tests in a relatively short period of time.  However, because tests were not written to be independent (#2) we ended up forcing us to run the tests in a single-threaded manner.  This significantly impacted our build times;
  5. Coverage (Branch/line): When we started out we focused exclusively on line coverage of code.  While this was a good starting point for us, given the maturity of the organization and its capabilities, line coverage is not adequate in judging the quality of the unit tests and may in fact even be misleading.  Branch coverage is a much better metric to use when judging the quality of the tests.  Tools like SonarQube generate reports that provide visibility into unit test coverage.  Using these metrics will significantly impact the quality of the code;
  6. Mock external service interactions: Many classes interact with external systems.  It is sometimes believed that such systems cannot be unit tested.  In this approach we demonstrate the use of Interface driven development to show how to unit test code that interacts with external systems; and
  7. Test classifications (groups): As mentioned in #3, we got to more than 5000 tests in a relatively short period of time.  In order for automation to be effective, we need to group tests into logical units that serve different purposes (nightly, sanity, functional, full regression etc.).  Doing this early will prevent time consuming and mind numbing classification later.

 

Hopefully at the end of these series of blog articles it will help us become better software engineers and write quality unit tests.

The Intercloud Fabric for Business (ICFB) recently released its newest version code named Diablo (v3.1.1).  The product was a nearly complete rewrite of the underlying platform, and boasts a modern UI, and a full complement of REST APIs.  The implementation uses the following technologies are also used in the platform:

  1. Mongo DB (for the configuration database):  We chose MongoDB because it was a commercial product with an open source version which gave us the option to engage with the Mongo services should the need arise.  The choice of a NoSQL database provided us with a lot of advantages in terms of programming ease, upgradeability, clustering etc.
  2. Spring: The Spring platform offers many frameworks that make development in Java extremely productive.  Specifically we use the following Spring framework components:
    1. Spring Data and Spring Data Mongo DB: This framework auto generates most of the code that we need to perform database CRUD operations.  This saves us a significant amount of work;
    2. Spring Security: For securing our web application
    3. Spring Boot: Built in microservices support for our different services (encryption services, workflow engine etc)
    4. Spring MVC: For REST Controllers and REST API support (we also use Jersey in the 3.1.1 version of the platform but are hoping to convert to Spring MVC completely over time)
    5. Spring Websocket: For websocket support for the ICF UI
    6. Spring JMS: For JMS access to the message broker (see #4 below)
  3. Apache Activiti Workflow Engine: For long running tasks, we use the wonderfully architected Open Source Apache Activiti Workflow engine.  The engine provides full BPMN 2.0 support which includes transactional support and greatly simplifies what could have otherwise been quite complicated to implement;
  4. Active MQ Messaging Server: For interprocess communication as well as to build in resiliency we use an Active MQ message broker and communicate using messages between the various components.
  5. Sencha ExtJS: We use this for our UI stack.  We chose this because this is a commercial package with friendly licensing terms and provides us with a library of reusable widgets.  It minimizes the number of different JavaScript technologies that we have to integrate with (and there's a new one seemingly coming out every day).  We also use AM Charts for our charting components.

 

In addition we have started to use Swagger annotations to generate documentation for our REST APIs.  In upcoming blog entries, I will attempt to hit on some of the lessons we learnt and things that we did during the course of the year long development of the product.

Filter Blog