Tuesday, July 22, 2008

Unit test with Spring Dynamic Modules

Follow our story about developing services by using Spring Dynamic Modules. We are developing our application and we have already migrated our application to support osgi. During migration, we have some problems with our test classes, we wanted to run our unit test in osgi environment (so we called integration test - integration test in osgi environment should be the better choice because we can check the class resolving among bundles besides of making sure all business rules are run properly). We already have a lot of unit tests written by JUnit 4 (which are based on Unitils and we already developed a bunch of test module such as Servlet Container, Ldap etc) and unfortunately they can not run in osgi environment. This post is our experience while we develop our integration test base on Spring DM testing framework, actually the cost of migration from our unit test is not much, most of time for researching how to do and simply replaces the revelant things of Unit 4 to Unit 3. Spring DM testing framework supports integration test but for JUnit 3 only now. Here is the general scenerio of Spring DM supports to run integration testing (quoted from Spring DM reference document)

  • Start the OSGi framework (Equinox, Knopflerfish, Felix)

  • install and start any specified bundles required for the test

  • package the test case itself into a on the fly bundle, generate the manifest (if none is provided) and install it in the OSGi framework

  • execute the test case inside the OSGi framework

  • shut down the framework

  • passes the test results back to the originating test case instance that is running outside of OSGi

Setting the integration test environment

Make sure that the following necessary files belong your class-path:

  • spring-osgi-core-1.1.0
  • spring-osgi-extender-1.1.0
  • spring-osgi-io-1.1.0
  • spring-osgi-test-1.1.0

Creating the first Osgi integration test

All integration test class must inherit the org.springframework.osgi.test.AbstractConfigurableBundleCreatorTest. The first step is creating our base class for all osgi integration testing:

/**
 * The base class of integration testing of Engroup. All integration testing
 * classes must be derived from this class
 *
 */
public class AbstractEngroupOsgiTest extends
  AbstractConfigurableBundleCreatorTests {
   ...
}

Configuring boot bundles of Spring DM

By default, Spring DM testing frameworks will load some default bundles before loading specific bundles for your test. You can see the default bundles loaded by Spring DM testing framework at org.springframework.osgi.test.internal.boot-bundles of spring-test project. In some cases, you need to replace the boot bundles of Spring DM testing framework by your bundles. The typical scenario that you need to do that is case you already have some bundles, in these bundles you add some extra osgi meta data (such as DynamicImport-Package field to allow the bundle can dynamic load class at runtime). Having the same package with the same version is prohibited. In addition, we are doing integration test, so make the test environment is similar with real environment is a must. Spring DM allows you can inject the new configuration of boot bundles by simply override the method getTestingFrameworkBundlesConfiguration of class AbstractConfigurableBundleCreatorTest.

Example 1: Inject the new configuration of boot bundles for Spring DM testing

private static final String TEST_FRRAMEWORK_BUNDLES_CONF_FILE = "/META-INF/boot-bundles.properties";
...
@Override
protected Resource getTestingFrameworkBundlesConfiguration() {
  return new InputStreamResource(AbstractEngroupOsgiTest.class
    .getResourceAsStream(TEST_FRRAMEWORK_BUNDLES_CONF_FILE));
}

Customizing the bundle content

Due to your test class is bundled in on-the-fly bundle. You can customize the manifest file of this bundle. In some cases, it is the must for complex case while the test class use the class of other bundles run during test executing. To do this, you simply override the method getManifestLocation (note that you can change the content of manifest file programatically by override the method getManifest, however we prefer to use the manifest file)

Example 2: Customizing the test manifest file

@Override
protected String getManifestLocation() {
    return "classpath:META-INF/MANIFEST.MF";
}

Example 3: Customized manifest file

Manifest-Version: 1.0
Embed-Directory: lib
Implementation-Title: Engroup Osgi Integration Test
Spring-Version: 2.5.5
Bundle-Activator: org.springframework.osgi.test.JUnitTestActivator
Implementation-Version: 2.5.5
Tool: Bnd-0.0.160
Bundle-Name: engroup-integration-test
Created-By: 1.6.0 (Sun Microsystems Inc.)
Bundle-Version: 0.0.1
Bnd-LastModified: 1207763595575
Bundle-ManifestVersion: 2
Import-Package:
 com.engroup.module.common.domain,
 com.engroup.module.common.service,
 …
 com.mysql.jdbc,
 javax.jcr,
 javax.naming,
 javax.naming.directory,
 javax.naming.ldap,
 javax.sql,
 junit.framework,
 org.dbunit,
 org.dbunit.database,
 org.dbunit.dataset,
 org.dbunit.dataset.xml,
 org.dbunit.operation,
 org.osgi.framework,
 org.springframework.context,
 …
Bundle-ClassPath: .,
 {src/test/resources}
Bundle-SymbolicName: engroup.server.engroup-integration-test
Include-Resource: {src\test\resources}

Some tips of writing Osgi Integration Test

  1. Set the default wait timer of creating spring context:
    Waiting time is set to wait until all dependencies are resolved before running test execution. Base on practice the waiting time is varied but limit under 20s (in our project). However, to support debugging while running integration test, the longer value should be set to prevent Spring DM interupt the test while developers are debugging the test program.Example 4: Set the wait time override the default value of Spring DM
        @Override
        protected long getDefaultWaitTime() {
            return 3000;
        }
  2. Inject spring beans in test class:
    We use Spring DM with the purpose that Spring beans could be exposed as the osgi services and used in other bundles. Of course, we would like to test spring beans in integration testing. To use spring, make sure the integration test class load the spring context files firstExample 5: Load the spring context files
         @Override
         protected String[] getConfigLocations() {
             return new String[] {
                 "classpath:META-INF/spring/hr-context-osgi-test.xml",
                 "classpath:META-INF/spring/common-context-osgi-test.xml",
                 "META-INF/spring/db-context-osgi-test.xml" };
         }

    Note: All spring context files should only declare only spring beans are exposed by other bundles and remember to import the necessary packages in your manifest file

    Example 6: Spring context file declares the spring bean service exported by other bundles and they serve for testing purpose

    <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:osgi="http://www.springframework.org/schema/osgi"
     xsi:schemaLocation=
      "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd">
     <osgi:reference id="companyService"
      interface="com.engroup.module.hr.service.CompanyService" />
    
     <osgi:reference id="divisionService"
      interface="com.engroup.module.hr.service.DivisionService" />
    ...

    Now, it is ready to inject our spring beans to test class by injecting the spring bean to protected objects of test class

    Example 7: Inject the spring bean to test class protected fields

       protected CompanyService companyService;
    
       public AbstractEngroupOsgiTest() {
           setPopulateProtectedVariables(true);
       }

    Now, you can use companyService spring bean is exposed from other bundles in your test method.

by haiphucnguyen

No comments: