Test-driven development in an SOA environment: Part 1: Testing data maps

This article introduces you to the theory of test-driven development for SOA environments. It shows you how to write test cases for SDO data maps first, even prior to releasing them for use by other SCA modules, and provides step-by-step instructions for writing these test cases and executing them using JUnit, Cactus, and IBM® WebSphere® Integration Developer. This content is part of the IBM WebSphere Developer Technical Journal.

Share:

Donald Vines (dhvines@us.ibm.com), Executive IT Architect, IBM

Donald Vines is currently an Executive IT Architect at IBM responsible for the WebSphere migration practice within North America.



30 July 2008

Also available in Chinese Russian

Introduction

Two recent, big changes in software engineering have been the growing use of test-driven development and continuous integration. As part of the shift to Agile software development methodologies, these practices have been used for a number of years in the development of traditional Java™-based applications, and more recently for the development of SOA solutions.

To that end, a recent developerWorks article describes a new feature of IBM WebSphere Integration Developer V6.1 that enables you to create test cases and run them unattended with the touch of a button. Another article series explains how to use JUnit and Cactus to test Service Component Architecture (SCA) components, BPEL components, and human tasks. These articles will be helpful to all Java developers working in an SOA environment. To supplement those resources, this two-part article looks at two additional and equally important topics:

  • Unit testing of data maps is extremely useful during the early phases of an SOA project. Testing data maps is essential for immature service interfaces, where the data is either not well established or unstable, and is also useful for more mature service interfaces. These tests will enable you to be less concerned about refactoring data types. You can make a few changes, retest the maps, and then retest the modules that use them.

  • Continuous integration, a fully automated and reproducible build (including testing) that runs many times a day, is a well accepted development practice for traditional applications. Applying this Agile development practice to SOA environments requires the ability to execute unit tests as part of your regularly scheduled integration build. This is done outside your integrated development environment (IDE), such as WebSphere Integration Developer, so running the unit tests via Ant is generally required.

This article shows you how to test data maps independent of the SCA modules that use them. Testing data maps compliments the standard practice of defining common data types and data maps in a shared library by enabling you to test the library before releasing it for use by the modules. The library can even be tested before creating any of the modules. This is consistent with test-driven development practices.

Part 2 will cover the use of a continuous integration in an SOA environment, with an automated build tool like Ant to not only run the unit tests, but also to generate test reports, and e-mail developers if there are test failures.


Unit testing data maps

To understand how to test data maps, consider this scenario:

  • You have an address cleansing service that checks the validity of addresses, but requires an address to be given as a comma delimited value; that is, there is an AddressCSV data type in the library.

  • Other parts of the SOA solution require addresses with separate fields for the elements of an address, such as company, addressLine1, and so on; that is, there is an Address data type in the library.

  • The library has two data maps, one of which maps from Address to AddressCSV (Figure 1), and the other maps from AddressCSV to Address (Figure 2).

    Figure 1. Address_To_AddressCSV data map
    Figure 1. Address_To_AddressCSV data map
    Figure 2. AddressCSV_To_Address data map
    Figure 2. AddressCSV_To_Address data map

As you can see, the above maps are fairly simple, with the field by field mapping being relatively straightforward. For large business objects, however, the maps can be quite complex, often making it will difficult to tell which attributes map to one another. Creating data maps can be a tedious and error prone task, which highlights the importance of repeated testing.

To test the above data maps, you will usually write a couple of JUnit test cases. For this example scenario, code samples are provided for you to download. The sample code is in a project interchange file that you can import into a WebSphere Integration Developer V6.1 workspace. After importing it, open the Business Integration perspective to see three sample projects:

  • L_AddressCleansingService: This library has the data types and data maps for the composite application, and will be reused by the modules that make up the SOA solution.
  • T_AddressCleansingService: This module is used for testing the data maps, and will be deployed to IBM WebSphere Process Server only in the testing environments. It is not part of the SOA solution.
  • T_AddressCleansingServiceJUnitWeb: This dynamic Web project holds the JUnit test cases that are to be executed inside the WebSphere Process Server container. It is a part of the T_AddressCleansingService test module.

This scenario assumes that the library’s data types and maps already exist, so this article will not cover how they are created. You can view the library to see their definitions. You can also check out this guided tour of WebSphere Integration Developer for information on how to create data maps.

The remainder of this article looks at three high level steps for testing data maps:

  1. Create the test module and Web project
  2. Create the test cases and data maps
  3. Run the test cases

These steps are described in detail in the sections that follow.

1. Create the test module and Web project

For this step, you have the option of following these instructions or downloading and importing the project interchange file so you can use the supplied test module and Web project. If you choose the latter, skip ahead to step 2.

To create the test module:

  1. In the Business Integration perspective of WebSphere Integration Developer, open the Business Integration view. Right-click then select New => Module. In the New Module dialog, enter the name T_AddressCleansingService and click Finish.

  2. To create the dynamic Web project, switch to the J2EE perspective. Right-click on T_AddressCleansingServiceApp, and select New => Dynamic Web Project. In the New Dynamic Web Project dialog, enter T_AddressCleansingServiceJUnitWeb and click Finish.

  3. Next, create the association between the test module and the library that contains the data types to map and the data maps to test. To do that, switch to the Business Integration perspective. Select T_AddressCleansingService and double-click to open the Dependency editor. Expand Libraries and use the Add function to add L_AddresCleansingService.

  4. Next, you will need to add the Log4J, JUnit, and the Cactus framework to the test module. To do that, add these libraries to the web-inf\lib folder of the T_AddressCleansingServiceJUnitWeb project:

  5. Finally, you will need to add Cactus servlet definitions to the web.xml file in your Web application. To do that, cut and paste the code in Listing 1 directly into the dynamic Web application deployment descriptor between the existing description and welcome file:

    Listing 1
    <display-name>T_AddressCleansingServiceJUnitWeb</display-name>
    
            <servlet>
                    <servlet-name>ServletRedirector</servlet-name>
                    <servlet-class>
                           org.apache.cactus.server.ServletTestRedirector
                    </servlet-class>
                    <init-param>
    	           <param-name>param1</param-name>
    	            <param-value>value1 used for testing</param-value>
    	   </init-param>
            </servlet>
    
            <servlet>
                    <servlet-name>ServletTestRunner</servlet-name>
                    <servlet-class>
                            org.apache.cactus.server.runner.ServletTestRunner
                    </servlet-class>
            </servlet>
    	
            <servlet-maping>
    	    <servlet-name>ServletRedirector</servlet-name>
    	    <url-pattern>/ServletRedirector</url-pattern>
            </servlet-maping>
    	
            <servlet-maping>
    	    <servlet-name>ServletTestRunner</servlet-name>
    	    <url-pattern>/ServletTestRunner</url-pattern>
            </servlet-maping>
    
            <welcome file list>

2. Create the test cases and data maps

Next, you will write a test case for the Address_To_AddressCSV data map. Your test case will populate an input Address object, invoke the map to produce an output AddressCSV object, and then compare the resulting AddressCSV object with the expected results. If they are equal, the test will be considered successful; if they are not equal, the test will fail. (The included project interchange file also contains a unit test case for the AddressCSV_To_Address map.

Creating a unit test involves these steps:

  1. Create the test class

    The typical JUnit programming model creates a test class by extending the TestCase class provided by JUnit. The Cactus framework is an extension of JUnit that enables you to run unit tests inside a container. Since you are using the Cactus framework, you will extend the Cactus test class org.apache.cactus.ServletTestCase. To do this:

    1. Go to the Web perspective, Project Explorer view, and expand Dynamic Web Projects => T_AddressCleansingServiceJUnitWeb => Java Resources => JavaSource.
    2. Right-click on Java Source and select New => Package to create a suitable package. Right-click the package and select New => Class to create the class, entering code such as:
      Listing 2
      package com.ibm.issw.service.addresscleansing.test;
      
      import org.apache.cactus.ServletTestCase;
      
      public class AddressCleansingServiceTest extends ServletTestCase {
  2. Write the setup() and teardown() methods

    The JUnit framework provides methods for you to implement to set up the environment for a test case, and for tearing down the environment after the test case is run. The sequence of events is: setUp() is called, testxxx() method is called, tearDown() is called. This sequence of events occurs for each testXXX() method in the test class. Listing 3 shows sample setUp() and tearDown() methods.

    Listing 3
    private DataObject expectedAddress;
    private String expectedWholeAddress;
    private DataObject expectedAddressCSV;
    private MapService mService;
    
    private static Logger logger = Logger.getLogger(
        AddressCleansingServiceTest.class );
    
    protected void setUp() throws Exception {
        expectedAddress = createAddress(
            "IBM Corporation","1133 Westchester Avenue",
            "","White Plains","NY","10604");
        expectedWholeAddress = createWholeAddress(
            "IBM Corporation","1133 Westchester Avenue",
            "","White Plains","NY","10604");
        expectedAddressCSV = createAddressCSV(expectedWholeAddress);
    		
        // Get the map service so we can invoke the mapping operation.
        mService = (MapService) ServiceManager.INSTANCE.locateService(
            "com/ibm/wbiserver/map/MapService");
    }
    
    protected void tearDown() throws Exception {
        expectedAddress = null;
        expectedAddressCSV = null;
        expectedWholeAddress = null;
        mService = null;
        }

    Listing 3 shows:

    • Definition of private data members that are shared by the test methods. These data members will be initialized in setUp(), used in testXX(), and destroyed in tearDown().
    • Creation of the logging service so you can log inside the test methods.
    • Creation and initialization of the "actual" input SDO so you can pass it to the map via the createAddress() method. This simply creates an Address SDO and initializes it to the given values. (See the project interchange file for its implementation.)
    • Creation and initialization of the "expected" output SDO so you can compare it with the actual output SDO that is returned by the map. This is done via the createWholeAddress() and createAddressCSV() methods. (See the project interchange file for their implementations.)
  3. Create the test methods

    The JUnit naming convention states that all test method names should begin with the word "test." The methods take no parameters and are void. Test failures are indicated by throwing unchecked exceptions, typically by using JUnit-provided assertion and failure methods. Test success is indicated by completion of a test method. The test method for this example looks like Listing 4.

    Listing 4
    public void testAddress_To_AddressCSVMap() 
    throws WBIMapNotFoundException, WBIMapFailureException, WBIMapServiceException {
       try {
    	logger.debug("Invoking Address_To_AddressCSV map.");
    
    	BOFactory bof = (BOFactory)ServiceManager.INSTANCE.locateService(
             "com/ibm/websphere/bo/BOFactory");
    	DataObject actualAddressCSV = bof.create(
             "http://L_AddressCleansingService", "AddressCSV");
    
    	HashMap inputMap = new HashMap();
    	HashMap outputMap = new HashMap();
    	inputMap.put("Address", expectedAddress);
    	outputMap.put("AddressCSV", actualAddressCSV);
    	mService.transform("http://L_AddressCleansingService",
             "Address_To_AddressCSV", inputMap, outputMap, (ExecutionContext)null);
          assertEquals(actualAddressCSV.getString("wholeAddress"),
             expectedAddressCSV.getString("wholeAddress"));
       } catch (WBIMapNotFoundException e) {
          logger.debug("WBIMapNotFoundException: " + e.getMessage());
    	throw e;
       } catch (WBIMapFailureException e) {
    	logger.debug("WBIMapFailureException: " + e.getMessage());
    	throw e;
       } catch (WBIMapServiceException e) {
    	logger.debug("WBIMapServiceException: " + e.getMessage());
    	throw e;
       }
    }

    Listing 4 shows:

    • The usage of Log4J to print out some messages during the test. Notice that the Log4J.properties file needs to be on the classpath to specify the appenders to use, which is done here by adding a folder to the application server’s classpath. To do this:
      1. Open the administrative console.
      2. Navigate to Application servers => server1 => Process Definition => Java Virtual Machine.
      3. Add the name of the folder where you put the log4j.properties file to the classpath, as shown in Listing 5.
      Listing 5
      log4j.rootLogger=WARN, RootAppender, ConsoleAppender
      
      log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender
      log4j.appender.ConsoleAppender.layout=org.apache.log4j.PatternLayout 
      log4j.appender.ConsoleAppender.layout.ConversionPattern=[%d]
        %-5p %c{2} - %m%n
      
      log4j.logger.com.ibm.issw=DEBUG
    • Invocation of the data map using the transform() method. The general transform() method, which can invoke maps that convert one or more SDOs to another SDO, is used in this example, but you could have used the simpleTransform() method to invoke the data map instead, since it converts from one SDO to another. The code fragment in Listing 6 shows how to use the simpleTransform().
      Listing 6
      mService.simpleTransform(
         " http://L_AddressCleansingService”,
         "Address_To_AddressCSV"
         input,
         output);
    • A succession of assertions about the data returned by the operation; in particular, you will look at the definition of the map and include assertions for each transformation. To do this, you will use the get methods on the input and output data object and use the JUnit assertEquals().

With this code in place, you are now ready to run the test.

3. Run the test cases

To run your test:

  1. First, add the test module to the server and make sure the server is started.

  2. In the Business Integration perspective, go to the Servers view. Right-click on the server, select Add Remove Projects, and add T_AddressCleansingServiceApp to the server. You now have your test class available for execution.

  3. To execute the test class, go to the J2EE perspective, Project Explorer view. Select the AddressCleansingServiceTest class and then select Run... from the Run menu.

  4. In the Run dialog, select JUnit from the list of possible Configurations and click New.

  5. Select the Arguments pane. Under VM arguments, enter the -D argument shown in Listing 7. (This argument specifies the localhost and port 9083; if your server or port for HTTP requests are different, adjust this string as necessary to match your values.)

    -Dcactus.contextURL=http://localhost:9083/T_AddressCleansingServiceJUnitWeb
  6. Select Run to initiate the test. The JUnit view then shows the result of running the test (Figure 3).

    Figure 3. Results of test execution
    Figure 3. Results of test execution

Now that you have established this launch configuration, you can run this class repeatedly simply by selecting it and clicking Run => JUnit Test.


Summary

This article demonstrated how to write test cases for data maps and to run them inside the WebSphere Process Server container using JUnit and Cactus. This enables you to write test cases for the SDO data types and data maps first before implementing the business processes (SCA modules) that use them. The availability of these tests provides rapid feedback on the data maps and supports subsequent refactoring of the data types that might be required in an SOA solution. In Part 2, you will see how to run these unit test cases in a continuous integration server.


Download

DescriptionNameSize
Code sampleTestingExample.zip972 KB

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere, SOA and web services
ArticleID=324713
ArticleTitle=Test-driven development in an SOA environment: Part 1: Testing data maps
publish-date=07302008