http://www.developer.com/

Back to article

Test-Driven Development for Servlets


January 13, 2005

Test-Driven Development (TDD) is a practical way to produce better code faster. Although the ideas of TDD are easy to understand, its application in the real world requires not only the understanding of the concepts of TDD but also a good knowledge of certain tools needed to run the tests, proper setup of the development environment, and an understanding of how to use the tests to improve the design of the software.

The concepts of TDD are easy to understand and consist of three basic steps.

  1. Write a test that defines how you think a small part of the software should behave.
  2. Make the test run as easily and quickly as you can. Don't be concerned about the design of code; just get it to work.
  3. Clean up the code. Now that the code is working correctly, take a step back and refactor it to remove any duplication or any other problems that were introduced to get the test to run.

Using TDD to develop regular Java code is fairly straightforward and has been well explained in numerous articles and books. By using the JUnit (http://www.junit.org) framework and a mock object framework such as EasyMock (http://www.easymock.org), it is easy to develop your code, in isolation, in a simple incremental way.

Testing Servlet Code

Servlets are much harder to develop using TDD because servlet code requires a container to run; this is harder to simulate. There are a number of ways that you can test servlets as you develop them:

  • Use mock objects to simulate the parts of the servlet container needed by the servlets being developed.
  • Use a real servlet container and develop the servlet and JSP using in-container testing.
  • Use an already developed simulated servlet container such as Mockrunner or ServletUnit.

Using mock objects to simulate the servlet container is not really a good idea because simulating the entire container would be a very complex task and, if you don't simulate it correctly, when you deploy your servlets in a real container you may find additional problems that should have been caught in testing.

On the other hand, using in-container testing to test servlets also has its problems. To use in-container testing to run the tests inside the container, you have to deploy the application to a real Web or application server and then write some code to exercise the servlet. This would work, but would take a lot of effort.

You could reduce the effort it would take by using a testing framework such as Cactus (http://jakarta.apache.org/cactus) that provides an easy way to run tests inside the container of any application server. Cactus is a good framework and has a lot of support in the Java community, but it has some disadvantages. The main disadvantage is the overhead and complexity that Cactus adds to the process. To run tests using Cactus, you first have to package your application like you would if you were deploying it to a real J2EE application server, which means putting the servlets in a war file with the corresponding web.xml file.

Cactus also requires some configuration to run the tests. Setting up Cactus is not hard, but it is an extra step in the development process. All this extra overhead adds time to the test-code cycle and makes it harder to make changes to the code and quickly see the results.

Although in-container testing with tools, like Cactus, is a good idea and should be done as part of the development process, there is an easier way to test servlets using a simulate servlet container. Simulated servlet containers such as the ones provided by ServletUnit(http://www.httpunit.org) or Mockrunner(http://mockrunner.sourceforge.net) enable developers to test servlets without the overhead of in-container tests or the complexity of having to mock out all the methods of the servlet container. These tools are relatively easy to use and don't require a lot of overhead.

The only drawback to using a simulated servlet container for testing servlets is that when the servlet is deployed to the real container, it may show some problems that were not caught in the tests because the simulated servlet container works slightly differently than the real one. For the most part, though, this is not a problem and the simulated servlet container is the best way to start developing servlets because of its low overhead and simplicity; it provides the easiest solution to the problem of testing servlets. This is the TDD way, because one of the goals of TDD is to keep the tests as simple as possible. As your project progresses, you may need to use in-container testing to fully test all aspects of the servlets; but, for the most part, the simulated servlet container will allow you to quickly develop servlets using TDD.

To illustrate how to develop a servlet using a simulate servlet container, you can go through a simple example and learn how the process works. For the example, you are going to develop the servlet code needed to get the set of games for the weekly football pool. For this example, you will use the ServletUnit framework to simulate the servlet container. Before you start the example, take a look at the ServletUnit framework so you can understand how to use it in the example.

Servlet Unit Overview

ServletUnit is part of the HttpUnit framework and can be used to test servlets without a servlet container. ServletUnit allows you to not only execute a servlet and verify the results; it also allows you to individually test all its methods. ServletUnit includes a simulated servlet container that provides all the functionality of a real servlet container. This means that tests are very quick and easy to set up and run. To test a servlet using ServletUnit, you would have to perform the following steps:

  1. Create an instance of the ServletRunner class. This is the class that simulates the servlet container and allows access to the internal object's servlets being run.
  2. Register the servlet(s) that you plan to test with the ServletRunner.
  3. Create a ServletUnitClient. This client will enable you to access the different parts of the registered servlets.
  4. Create a WebRequest that is used to call your servlet. (Note: ServletUnit does not really use the host or port part of the URL of the WebRequest and only uses the part of the URL starting with the path.)
  5. Get the InvocationContext. The InvocationContext represents the invocation of a servlet. This interface enables you to access the servlet as well as its request and response objects.

Once you have the InvocationContext, you can get a handle to the servlet and execute any of its methods. This enables you to test the servlet a little piece at a time instead of testing just the final result.

Using ServletUnit enables you to develop servlets the TDD way, because it provides a means to test small parts of a servlet as it is being developed. For more detailed information on ServletUnit, see the HttpUnit site at http://httpunit.org.

PlayerPicksServlet Example

Now that we understand how to test a servlet let's start the example (The full set of source code can be downloaded here). Unzip the files from both downloads into one directory. Using TDD, of course, you start the development by writing a test. The test you write should test the behavior that you need to implement. For this example, you are not going to worry about how the servlet is called or how the data from the servlet will be displayed; you are just going to concentrate on implementing the desired behavior. The behavior that you want to implement is pretty simple; you want to create a servlet that can get a list of games for the weekly football pool. From this behavior, you write a normal JUnit test with the following test method.

public void testDisplayPlayerPicks()
   {
      // Set up ServletUnit to run the PlayerPickServlet
      ServletRunner sr = new ServletRunner();
      sr.registerServlet("PlayerPickServlet",
                         PlayerPickServlet.class.getName());
      ServletUnitClient sc = sr.newClient();
      WebRequest request =
         new PostMethodWebRequest("http://localhost/PlayerPickServlet");
      try
      {
         // Use the InvocationContext to create an instance
         // of the servlet
         InvocationContext ic = sc.newInvocation(request);
         PlayerPickServlet ppickServlet = (PlayerPickServlet)
                                          ic.getServlet();
         assertNull("A session already exists",
                    ic.getRequest().getSession(false));
         HttpServletRequest ppickServletRequest = ic.getRequest();

         // Call the servlets getOpenPool() method
         FootballPool openPool =
            ppickServlet.getOpenPool(ppickServletRequest);

         // Check the return football pool to make sure it is correct
         assertEquals("Kansas City", openPool.getAwayTeam(0));
         assertEquals("Green Bay", openPool.getHomeTeam(0));
         assertEquals("Houston", openPool.getAwayTeam(1));
         assertEquals("Tennessee", openPool.getHomeTeam(1));
         assertEquals("Carolina", openPool.getAwayTeam(2));
         assertEquals("Indianapolis", openPool.getHomeTeam(2));
         assertEquals("NY Giants", openPool.getAwayTeam(3));
         assertEquals("New England", openPool.getHomeTeam(3));
         assertEquals("Chicago", openPool.getAwayTeam(4));
         assertEquals("New Orleans", openPool.getHomeTeam(4));
         assertEquals("Oakland", openPool.getAwayTeam(5));
         assertEquals("Cleveland", openPool.getHomeTeam(5));
         assertEquals("Philadelphia", openPool.getAwayTeam(6));
         assertEquals("Dallas", openPool.getHomeTeam(6));
         assertEquals("Tampa Bay", openPool.getAwayTeam(7));
         assertEquals("Washington", openPool.getHomeTeam(7));
         assertEquals("Miami", openPool.getAwayTeam(8));
         assertEquals("Jacksonville", openPool.getHomeTeam(8));
         assertEquals("Pittsburgh", openPool.getAwayTeam(9));
         assertEquals("Denver", openPool.getHomeTeam(9));
         assertEquals("Buffalo", openPool.getAwayTeam(10));
         assertEquals("NY Jets", openPool.getHomeTeam(10));
         assertEquals("Baltimore", openPool.getAwayTeam(11));
         assertEquals("Arizona", openPool.getHomeTeam(11));
         assertEquals("San Francisco", openPool.getAwayTeam(12));
         assertEquals("Seattle", openPool.getHomeTeam(12));
         assertEquals("Atlanta", openPool.getAwayTeam(13));
         assertEquals("St. Louis", openPool.getHomeTeam(13));

      }
      catch (Exception e)
      {
         fail("Error testing DisplayPlayerPickServlet Exception
               is " + e);
         e.printStackTrace();
      }
   }

Listing 1: Test for getting a list of the weekly football games

If you look at Listing 1, you can see that I used ServletUnit to write a test that calls the getOpenPool() method of the servlet to get the set of games for the football pool and then checks to make sure the pool returned is correct. You do this by first setting up ServletUnit's ServletRunner and ServletUnitClient and then using the InvocationContext object to execute one of the servlet's methods and check the results.

As you can see, writing the test first helped you make some decisions about the design of the servlet. You have decided that the servlet will have a getOpenPool() method that will take the request object as an argument and return a FootballPool. Also, as you can see, one of the nice features of ServletUnit is that it enables you to test the internal methods of a servlet. This allows you to do fine grain testing, which makes TDD easier.

At the moment, you cannot run the test because you have not created the actual servlet yet. Now that you understand the requirements of this method of the servlet, you can implement the servlet so that you can run the test (see Listing 2).

public class PlayerPickServlet extends HttpServlet
{
   public FootballPool getOpenPool(HttpServletRequest request)
   {
      PoolDatabase poolData =
         (PoolDatabase)request.getSession().getAttribute("PoolDatabase");
      FootballPool pool = poolData.getOpenPool();
      return(pool);
   }
}

Listing 2: getOpenPool method for the servlet

As you see, the implementation of the getOpenPool() method is pretty simple. You have made the assumption that the servlet will communicate with a database to get the data for the football pool. You have created a PoolDatabase interface call to define the methods the servlet will use to interact with the database (see the downloadable source code for details).

Now that you have written the servlet code, you now can run the test, which fails miserably. The problem is that when the servlet tries to get the PoolDatabase object, it gets a null pointer because it was never created. Creating a PoolDatabase object that the servlet can use could be a lot of work because it means creating an implementation of the PoolDatabase interface and hooking it up to a database. This could be a real hassle and make the test hard to run and maintain. Luckily, there is a better way to create a PoolDatabase object: by using a MockObject framework such as EasyMock. By using EasyMock, you can add some code to your test that will enable you to easily create a PoolDatabase object that the servlet can use. To do so, you add the following code to your test (updated code in bold).

public void testDisplayPlayerPicks()
   {
      MockControl poolDataMockcontrol;

      // Create a mock control to simulate the PoolDatabase class
      poolDataMockcontrol =
         MockControl.createControl(PoolDatabase.class);

      //Get mock object
      PoolDatabase mockPoolData = (PoolDatabase)
                                  poolDataMockcontrol.getMock();
      //Train the mock object
      mockPoolData.getOpenPool();
      //Create return value for mock method
      FootballPool fbPool = createFbPool();
      poolDataMockcontrol.setReturnValue(fbPool);
      poolDataMockcontrol.replay();
      // Set up servlet unit to run the PlayerPickServlet
      ServletRunner sr = new ServletRunner();
      sr.registerServlet("PlayerPickServlet",
                         PlayerPickServlet.class.getName());
      ServletUnitClient sc = sr.newClient();
      WebRequest request =
         new PostMethodWebRequest("http://localhost/PlayerPickServlet");
      try
      {
         // Use the InvocationContext to create and instance
         // of the servlet
         InvocationContext ic = sc.newInvocation(request);
         PlayerPickServlet ppickServlet = (PlayerPickServlet)
                                           ic.getServlet();
         assertNull("A session already exists",
                    ic.getRequest().getSession(false));
         HttpServletRequest ppickServletRequest = ic.getRequest();

         // Add the mock PoolDatabase class to the servlet's
         // session data
         HttpSession servletSession = ppickServletRequest.getSession();
         servletSession.setAttribute("PoolDatabase", mockPoolData);

         // Call the servlet's getOpenPool() method
         FootballPool openPool =
            ppickServlet.getOpenPool(ppickServletRequest);

         // Check the returned football pool to make sure it is
         // correct
         assertEquals("Kansas City", openPool.getAwayTeam(0));
         assertEquals("Green Bay", openPool.getHomeTeam(0));
         assertEquals("Houston", openPool.getAwayTeam(1));
         assertEquals("Tennessee", openPool.getHomeTeam(1));
         assertEquals("Carolina", openPool.getAwayTeam(2));
         assertEquals("Indianapolis", openPool.getHomeTeam(2));
         assertEquals("NY Giants", openPool.getAwayTeam(3));
         assertEquals("New England", openPool.getHomeTeam(3));
         assertEquals("Chicago", openPool.getAwayTeam(4));
         assertEquals("New Orleans", openPool.getHomeTeam(4));
         assertEquals("Oakland", openPool.getAwayTeam(5));
         assertEquals("Cleveland", openPool.getHomeTeam(5));
         assertEquals("Philadelphia", openPool.getAwayTeam(6));
         assertEquals("Dallas", openPool.getHomeTeam(6));
         assertEquals("Tampa Bay", openPool.getAwayTeam(7));
         assertEquals("Washington", openPool.getHomeTeam(7));
         assertEquals("Miami", openPool.getAwayTeam(8));
         assertEquals("Jacksonville", openPool.getHomeTeam(8));
         assertEquals("Pittsburgh", openPool.getAwayTeam(9));
         assertEquals("Denver", openPool.getHomeTeam(9));
         assertEquals("Buffalo", openPool.getAwayTeam(10));
         assertEquals("NY Jets", openPool.getHomeTeam(10));
         assertEquals("Baltimore", openPool.getAwayTeam(11));
         assertEquals("Arizona", openPool.getHomeTeam(11));
         assertEquals("San Francisco", openPool.getAwayTeam(12));
         assertEquals("Seattle", openPool.getHomeTeam(12));
         assertEquals("Atlanta", openPool.getAwayTeam(13));
         assertEquals("St. Louis", openPool.getHomeTeam(13));

         poolDataMockcontrol.verify();

      }
      catch (Exception e)
      {
         fail("Error testing DisplayPlayerPickServlet Exception
               is " + e);
         e.printStackTrace();
      }
   }

Listing 3: Updated Test for getting a list of the weekly football games

If you look at the updated code, you can see that you first used the EasyMock framework to create a mock PoolDatabase class; then you added this mock object to the servlet's session data. With this added code, you cannot run the test and get it to pass; this means that you are finished with the development for this method. To add other methods to the servlet, you would follow a similar pattern:

  • Create a simple test from the required behavior.
  • Add code to the servlet to get the test to compile and run.
  • Use a mock object to simulate the dependent classes needed by the servlet.
  • Refactor the test and source code to improve the code and fix any problems.

Developing servlets in this manner has many advantages, including providing a simple incremental way to develop code, having tests for all code, being able to refactor easily, ad so forth. Although it may take you a while to learn how to use tools such as Servlet Unit and EasyMock effectively, once you master the techniques shown here, you will be able to develop servlets faster with fewer bugs.

The zip file associated with this article, ServletTDD.zip, has a full set of source code for the example shown here. Experience is the best teacher, so I would encourage you to download and run the example to fully understand how to use TDD to develop servlets. I have also provided a list of reference to some TDD resources which should be useful. The list of resources includes a link to my book, Test-Driven Development: A J2EE Example, which focuses on explaining the tools and techniques needed to use TDD on a real J2EE project. It contains topics on servlets, JSP, and EJB development, as well as an explanation of how to use TDD to integrate all the pieces of a J2EE application. It's a good book with a lot of useful examples. I highly recommend it, but then again, I'm probably a little biased.

References

About the Author

Tom Hammell is a senior developer and currently works on the development of telecom network infrastruture software for the OpenCall Business Unit of Hewlett-Packard. Tom has been developing software for over 18 years and has worked on software in many different fields, such as satellite navigation, financial news wires, telecom, and J2EE application server development. Tom has published a number of articles on Java topics ranging from Swing development to Unit testing and recently wrote a book on Test-Driven Development. Tom holds a Bachelor of Science in Electrical Engineering degree and Masters of Computer Science degree from Steven's Institute of Technology. He may be reached at thomas_hammell@hp.com.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date