Chapter 9. Testing with Spring and OSGi

The importance of application testing was briefly addressed back in Chapter 2, where you learned to perform both unit and integration testing in the context of the Spring Framework. In doing so, you saw how Spring's POJO-based approach favors unit testing, as well as explored Spring's facilities to perform integration testing against RDBMSs.

Testing OSGi applications presents its own set of challenges. Among these are replicating an OSGi environment to verify that bundles work as expected once installed in an OSGi production environment and creating tests that use OSGi services.

Using Spring-DM simplifies this process. As it's been illustrated numerous times throughout the book, Spring-DM allows OSGi logic to be decoupled from application classes. This favors the use of POJOs in OSGi applications, which in turn facilitates the creation of tests.

This chapter will take you through all the testing facilities offered by Spring-DM.

Testing with OSGi and Spring-DM

In the introductory chapter on OSGi (Chapter 1), you might recall it was classes that needed to include the registration and lookup sequences for OSGi services using the framework's API.

Under such circumstances, testing can get complicated. With a class's code intertwined with OSGi calls, it can be difficult to create the necessary tests to guarantee the integrity of a class's OSGi and business logic. In addition, it requires thinking about tests in broader terms, such as how OSGi services need to interact with business logic, which militates against one of the basic principles of testing: simplicity.

Since Spring-DM relies on the use of descriptors to register and look up OSGi services on behalf of classes, a class's code normally doesn't contain this type of logic. This separation allows a class's business logic to be tested using very simple cases, as done in Chapter 2, while OSGi logic is also tested using separate and simple cases.

This approach is one of the cornerstones to using the Spring Framework—allowing classes to remain POJOs and define a class's dependencies elsewhere, thus facilitating the creation of tests.

Still, this type of testing done at the class level—unit testing—can become very different when OSGi comes into the picture. In a typical unit test (the business logic kind) consisting of proving the integrity of getter-setter methods, a workflow, or a mathematical operation, a testing framework has a one-on-one interaction with the class it is testing.

By "one-on-one interaction," I mean that a test sequence consists of applying an input to a class's method, holding its state, and verifying whether the return value is the desired output. It is a back-and-forth process between class and testing framework. But what happens when a class needs to get ahold of an outside resource to perform a test?

This type of testing—integration testing—is unavoidable when testing OSGi and Spring-DM applications. Since even OSGi services used in single classes need to be registered and/or looked up in an OSGi environment, this requires the presence of an OSGi environment to fulfill tests—just as an RDBMS is an outside resource required to perform integration tests, as illustrated in Chapter 2.

This implies that there is little scope for unit testing in OSGi, since all testing involving OSGi and Spring-DM is considered integration testing, due to this reliance on an outside resource (OSGi framework).

Still you may be wondering, what does testing an application using OSGi and Spring-DM really accomplish? The purpose behind integration testing in an environment like OSGi is to verify the correctness of behavior such as the following:

  • Are a bundle's packages being exported with the correct version?

  • Is a service registered with the correct interface and properties?

  • Is a service that is looked up and consumed producing the expected outcome?

  • Are a bundle's imported packages available from other bundles in the application?

All these questions revolve around OSGi and are best answered in a testing environment, where their potential for disruption is limited. In addition, OSGi testing also ensures that once an OSGi and Spring-DM code base start to evolve, a minimum benchmark or "safety net" is kept so as to not break older and relied-upon OSGi logic. In essence, the same benefits as non-OSGi testing apply to OSGi and Spring-DM testing.

After this brief overview of testing with OSGi and Spring-DM, let's revisit the same unit and integration techniques presented in Chapter 2, only this time applied to the OSGi application created in Chapter 7.

Unit and Integration Revisited: Testing OSGi Applications Without OSGi

The title to this section might sound strange, but there are non-OSGi tests that can be performed on OSGi applications. How so? This type of testing refers to unit testing an OSGi application's non-OSGi logic, as well as performing the corresponding integration tests to ensure an application works correctly with something like an RDBMS.

Chapter 7 's application, even though designed around OSGi (being composed of five bundles and operating on the basis of multiple OSGi services), still contains logic that can and should be tested outside the context of OSGi. Since Chapter 7 's application is an OSGi version ported from Chapter 2 's stand-alone Spring application, you've already created some of these tests.

Listing 2-11 in Chapter 2 illustrates a unit test that can be applied equally to a code base designed around OSGi principles. This unit test ensures the classes belonging to the application's model bundle have the correct behavior in terms of their getter-setter methods, which of course has nothing to do with OSGi.

Another aspect of testing that requires attention and is outside the scope of OSGi is related to the business logic executed against an RDBMS. Listing 2-12 in Chapter 2 illustrates the integration test applicable to the application's DAO class. This integration test ensures the class belonging to the service DAO bundle has the correct behavior when it interacts with an RDBMS.

In the case of the integration test in Listing 2-12, it should be noted that it is designed to test a DAO class using the JPA, unlike Chapter 7 's application, which introduced a JDBC-backed DAO class. As already outlined in Chapter 7, OSGi's modularity allows you to easily swap bundles using either RDBMS access strategy (JPA or JDBC).

However, for the sake of completeness and to illustrate how an RDBMS integration test can be performed against a DAO class based on JDBC, Listing 9-1 shows this test.

Example 9-1. (Non-OSGi) RDBMS Integration Test for a DAO Class Based on JDBC

package com.apress.springosgi.ch9.tests;

import java.util.Date;
import java.util.List;

import com.apress.springosgi.ch9.model.HelloWorld;
import com.apress.springosgi.ch9.model.Person;
import com.apress.springosgi.ch9.service.HelloWorldService;

import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.orm.ObjectRetrievalFailureException;

public class HelloWorldServiceIntegrationTests extends
(Non-OSGi) RDBMS Integration Test for a DAO Class Based on JDBC
AbstractTransactionalDataSourceSpringContextTests {
protected HelloWorldService helloWorldService;

    public void setHelloWorldService(HelloWorldService helloWorldService) {
        this.helloWorldService = helloWorldService;
    }

    private static long EnglishId;
    private static long SpanishId;
    private static long FrenchId;
    private static boolean databasePopulated = false;

    protected void populateDatabase() {
       ("classpath:/com/apress/springosgi/ch9/tests/helloworld-hsqldb.sql", false);
        databasePopulated = true;
    }

    protected String[] getConfigLocations() {
        return new String[] { "classpath:/com/apress/springosgi/ch9/tests/
(Non-OSGi) RDBMS Integration Test for a DAO Class Based on JDBC
helloworld-service.xml" }; } protected void onSetUpBeforeTransaction() throws Exception { // This method is executed before each test method // Database tables and data only inserted once if (!databasePopulated) { populateDatabase(); HelloWorld hw1 = new HelloWorld("English", "Hello World!", new Date(), new Person("John","Smith",45.00)); HelloWorld hw2 = new HelloWorld("Spanish", "Hola Mundo!", new Date(), new Person("Carlos","Perez",40.00)); HelloWorld hw3 = new HelloWorld("French", "Bonjour Monde!", new Date(), new Person("Pierre","LeClair",40.00)); helloWorldService.save(hw1); helloWorldService.save(hw2); helloWorldService.save(hw3); EnglishId = helloWorldService.findByLanguage("English").get(0).getId(); SpanishId = helloWorldService.findByLanguage("Spanish").get(0).getId(); FrenchId = helloWorldService.findByLanguage("French").get(0).getId(); } }
public void testFindById() {
        HelloWorld hw = helloWorldService.findById(EnglishId);
        assertNotNull(hw);
        assertEquals("English", hw.getLanguage());
    }

    try {
            HelloWorld hw = helloWorldService.findById(10000);
        } catch(EmptyResultDataAccessException expected) {
            assertTrue(true);
        }
    }

    public void testFindByLanguage() {
        List<HelloWorld> hws = helloWorldService.findByLanguage("Spanish");
        assertEquals(1, hws.size());
        HelloWorld hw = hws.get(0);
        assertEquals("Hola Mundo!", hw.getMessage());
    }

    public void testFindByBadLanguage() {
        List<HelloWorld> hws = helloWorldService.findByLanguage("Catalan");
        assertEquals(0, hws.size());
    }

    public void testFindByTranslatorFirstName() {
        List<HelloWorld> hws = helloWorldService.findByTranslatorFirstName("John");
        assertEquals(1, hws.size());
        HelloWorld hw = hws.get(0);
        assertEquals(EnglishId, hw.getId());
    }

    public void testFindByTranslatorLastName() {
        List<HelloWorld> hws = helloWorldService.findByTranslatorLastName
(Non-OSGi) RDBMS Integration Test for a DAO Class Based on JDBC
("LeClair"); assertEquals(1, hws.size()); HelloWorld hw = hws.get(0); assertEquals(FrenchId, hw.getId()); } public void testFindByTranslatorFirstNameDoesNotExist() { try{ List<HelloWorld> hws = helloWorldService.findByTranslatorFirstName
(Non-OSGi) RDBMS Integration Test for a DAO Class Based on JDBC
("Bill"); } catch(EmptyResultDataAccessException expected) { assertTrue(true); } }
public void testFindByTranslatorLastNameDoesNotExist() {
        try {
            List<HelloWorld> hws = helloWorldService.findByTranslatorLastName
(Non-OSGi) RDBMS Integration Test for a DAO Class Based on JDBC
("Matsusaka"); } catch(EmptyResultDataAccessException expected) { assertTrue(true); } } public void testFindByTranslatorHourlyRateOver() { List<HelloWorld> hws = helloWorldService.findByTranslatorHourlyRateOver
(Non-OSGi) RDBMS Integration Test for a DAO Class Based on JDBC
(42.00); assertEquals(1, hws.size()); } public void testModifyHelloWorldMessage() { String oldHelloMessage = "Bonjour Monde!"; String newHelloMessage = "Bonjour Le Monde!"; HelloWorld hw = helloWorldService.findByLanguage("French").get(0); hw.setMessage(newHelloMessage); HelloWorld hw2 = helloWorldService.update(hw); assertEquals(newHelloMessage, hw2.getMessage()); List<HelloWorld> hw3 = helloWorldService.findByMessage(oldHelloMessage); assertEquals(0, hw3.size()); hw3 = helloWorldService.findByMessage(newHelloMessage); assertEquals(1, hw3.size()); HelloWorld newhw3 = hw3.get(0); assertEquals(newHelloMessage, newhw3.getMessage()); } public void testDeleteHelloWorldCascade() { String transFirstName = "Carlos"; HelloWorld hw = helloWorldService.findByTranslatorFirstName
(Non-OSGi) RDBMS Integration Test for a DAO Class Based on JDBC
(transFirstName).get(0); int transCountBefore = countRowsInTable("person"); int helloCountBefore = countRowsInTable("helloworld"); helloWorldService.delete(hw); try { List<HelloWorld> hws = helloWorldService.findByTranslatorFirstName
(Non-OSGi) RDBMS Integration Test for a DAO Class Based on JDBC
(transFirstName); } catch(EmptyResultDataAccessException expected) { assertTrue(true); } int transCountAfter = countRowsInTable("person"); int helloCountAfter = countRowsInTable("helloworld"); assertEquals(transCountBefore −1, transCountAfter);
assertEquals(helloCountBefore −1, helloCountAfter);
    }

    public void testFindAll() {
        List<HelloWorld> hws = helloWorldService.findAll();
        assertEquals(3, hws.size());
    }

}

The first thing you should note about this listing is that it defines the same test methods as its JPA counterpart in Listing 2-12. However, the logic used to perform each test is different in that the test is being performed against a DAO class based on JDBC.

Notice how this test class extends AbstractTransactionalDataSourceSpringContextTests. This class provided by the Spring Framework facilitates the testing of JDBC-backed classes, just as the AbstractJpaTests class provided by the Spring Framework is used to facilitate testing of JPA-backed classes.

Though the mechanisms provided by these last two Spring helper classes is fairly similar, there are some differences you need to be aware of. First is the getConfigLocations method, which contains the configuration parameters to set up a test RDBMS; this RDBMS will in turn be used to inject a helloWorldService reference into the test class. Listing 9-2 illustrates the helloworld-service.xml file declared in getConfigLocations.

Example 9-2. (Non-OSGi) helloworld-service.xml Context for Setting Up a Test RDBMS Based on JDBC

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/
(Non-OSGi) helloworld-service.xml Context for Setting Up a Test RDBMS Based on JDBC
spring-beans.xsd"> <bean id="helloWorldService" class="com.apress.springosgi.ch9.servicedaojdbc.HelloWorldDAO"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="dataSource" class="org.springframework.jdbc.datasource.
(Non-OSGi) helloworld-service.xml Context for Setting Up a Test RDBMS Based on JDBC
DriverManagerDataSource"> <property name="driverClassName" value="org.hsqldb.jdbcDriver"/> <property name="url" value="jdbc:hsqldb:mem:springosgi"/> <property name="username" value="sa" /> <property name="password" value="" /> </bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.
(Non-OSGi) helloworld-service.xml Context for Setting Up a Test RDBMS Based on JDBC
DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> </beans>

Serving as basis for comparison, take a look at Listing 2-13 in Chapter 2, which illustrates the helloworld-service.xml file used for JPA testing. The only thing that is identical between these two modes of testing (JPA and JDBC) is the dataSource declaration, which corresponds to an in-memory HSQLDB RDBMS.

The transactionManager declaration in this case is only applied to the dataSource element. In Listing 2-13, note the transactionManager declaration, in addition to the dataSource element, is applied to the entityManagerFactory element, which is JPA specific.

Next you can observe the helloWorldService declaration, which is injected with the dataSource bean. This is in contrast with the JPA version, which is injected with an entityManagerFactory bean. This difference is due to how JPA- and JDBC-backing classes operate. A JPA class taps an RDBMS through an entityManagerFactory instance, whereas a JDBC class gets ahold of an RDBMS through a raw data source.

In addition, the presence of an entityManagerFactory—or lack thereof—influences how a test RDBMS is set up. If you look closely at the JPA version in Listing 2-13, you will note the <property name="generateDdl" value="true"/> statement inside the entityManagerFactory element.

This statement allows the JPA entity manager to execute Data Definition Language (DDL) against an RDBMS. As consequence, this permits a test to be started without any preexisting tables in an RDBMS. (Note that DDL corresponds to CREATE TABLE type statements to create data structures in an RDBMS.)

Since this is JPA-specific behavior, a JDBC test needs to take this into account, creating the necessary RDBMS tables (DDL) at the outset of a test, which is the purpose of the populateDatabase method in the JDBC test class (Listing 9-1).

This last method relies on the executeSqlScript method, which forms part of the Spring AbstractTransactionalDataSourceSpringContextTests class and is used to execute an SQL DDL script against the data source configured for the test class. Listing 9-3 illustrates this script.

Example 9-3. (Non-OSGi) helloworld-hsqldb.sql Script to Prepopulate RDBMS

CREATE TABLE Person(ID IDENTITY, FNAME VARCHAR(200),LNAME VARCHAR(200),
(Non-OSGi) helloworld-hsqldb.sql Script to Prepopulate RDBMS
hourlyRate DOUBLE); CREATE TABLE HelloWorld(ID IDENTITY, language VARCHAR(200),
(Non-OSGi) helloworld-hsqldb.sql Script to Prepopulate RDBMS
message VARCHAR(200), transdate DATE, translator_id INTEGER NOT NULL,
(Non-OSGi) helloworld-hsqldb.sql Script to Prepopulate RDBMS
CONSTRAINT FK1 FOREIGN KEY (translator_id) REFERENCES Person(ID))

This listing contains the two DDL statements that will be executed against the in-memory HSQLDB RDBMS. It's important to note that this script is only executed once—the same number of times the populateDatabase method is called, which is controlled by the databasePopulated field. This is due to the sequence of steps in the testing process.

When the testing process starts, Spring will instantiate an in-memory HSQLDB that will last for the entire sequence of test methods (testFindBy*, testModify, testDelete, etc.). Prior to executing each of these test methods, which rely on the presence of data in the RDBMS, the onSetUpBeforeTransaction method will be invoked on each occasion.

This behavior of the onSetUpBeforeTransaction method allows each test method to start with a clean set of data in the RDBMS (if so required), but at the same time and given its recursive nature, also executes the SQL DDL script against the RDBMS on each occasion. This process leads to errors because tables (DDL) can only be created once against the same RDBMS instance. Hence the need for a protective clause based on the databasePopulated field in order to invoke the populateDatabase method—and with it the executeSqlScript method—only once.

The test, including its class, context file, and DDL script, can be started with JUnit using a tool like Apache Ant, just as you did for the JPA DAO version in Chapter 2. The book's accompanying download includes this test in a staged form ready to be executed with Apache Ant.

This concludes the discussion on non-OSGi testing performed on OSGi applications, as well as analysis of the parts needed to test a DAO JDBC class using Spring artifacts and an in-memory RDBMS. Next, I will shift focus to OSGi testing and how Spring-DM supports this process.

Spring-DM's Testing Framework

As mentioned at the outset of this chapter, the process of performing OSGi tests requires the presence of an OSGi environment in order to guarantee that the behavior of bundles is appropriate once in a production environment. This creates the first requirement for OSGi testing: a means to bootstrap an OSGi environment in which to run tests.

Since the tests themselves need to interact with an OSGi environment's registry and services, this creates a second requirement: a need to package test classes in an OSGi bundle. This may seem awkward, especially compared to non-OSGi unit and integration test classes that can be executed directly from the file system, but in OSGi classes need to be prepped and installed in OSGi's de facto deployment format—the bundle. They simply can't be run in isolation even if they are test classes.

In addition, a third requirement may arise in the form of provisioning the OSGi testing environment with additional bundle dependencies needed to run a test. For example, in order to perform a test on an OSGi web bundle, you would have to install an OSGi'fied web container like Tomcat or Jetty into the OSGi testing environment prior to running the test itself, since the target web bundle requires such a dependency to operate.

These requirements alone are a lot of work to be fulfilling on each test case involving OSGi, which is the reason behind Spring-DM's testing framework. The purpose of Spring-DM's testing framework is to streamline the following process:

  1. Start an OSGi testing environment.

  2. Install and start any specified bundles required for the test class.

  3. Package the test class into a bundle—generating its MANIFEST.MF file if none is provided—and installing the bundle into the OSGi testing environment.

  4. Execute the test class inside the OSGi environment.

  5. Shut down the OSGi testing environment.

  6. Pass the test results back to the originating test class instance that is running outside OSGi.

By doing so, the Spring-DM testing framework lets you concentrate on the primary task of creating tests. In order to use the Spring-DM testing framework, you will need to rely on a series of classes included in Spring-DM and on which all tests should be designed. Just as you've used other Spring classes—such as AbstractTransactionalDataSourceSpring ContextTests and AbstractJpaTests—which aid in the creation of tests by allowing the placement of test methods in a class without your needing to worry about the setup process, so too are these Spring-DM test classes aimed at reducing the amount of "legwork" needed to create tests.

I will illustrate the use of these test classes shortly, but for the moment I want to continue with a few more important details you'll need to know on Spring-DM's testing framework.

Spring-DM's testing framework allows testing to be performed against any of the three major OSGi frameworks: Eclipse Equinox, Apache Felix, and Knopflerfish. This guarantees that any bundle's OSGi logic is compatible with any of these three implementations.

It's also important that you realize Spring-DM's testing framework is tightly pegged against Apache Maven, relying on this last dependency management tool for the invocation of tests and management of bundle dependencies that may be needed by the tests themselves.

Since I opted to stay away from Apache Maven due to its more demanding setup in favor of Apache Ivy in Chapter 7, all the tests presented in this chapter will be designed to operate with Apache Ivy as the dependency management tool and Apache Ant as the build tool.

Having covered this background on Spring-DM's testing framework, you may be wondering what the general process for creating tests in Spring-DM is. The following list presents this three-step sequence:

  1. Create a test class based on one of Spring-DM's supporting test classes, containing several assertions in reference to a bundle's services, classes, versions, exports, or imports.

  2. Have an OSGi framework on standby, prepped and downloaded by either Apache Maven or Apache Ivy.

  3. Execute the test aided by Apache Maven or Apache Ant.

Knowing this three-step sequence, let's create one of the most basic Spring-DM tests possible with the help of Apache Ivy and Apache Ant.

Creating a Spring-DM Test Class

Spring-DM test classes are designed around a series of classes available in Spring-DM's org.springframework.osgi.test package prefixed with the Abstract keyword and ending with the Tests keyword. These classes are listed in Table 9-1.

Table 9-1. Spring-DM Test Classes and Inheritance Hierarchy

Test Class

Function

Subclasses

AbstractOptionalDependency-
InjectionTests

JUnit superclass that creates an empty OSGi bundle appCtx when no configuration file is specified. Required for mixing Spring existing testing hierarchy with the OSGi testing framework functionality.

AbstractOsgiTests

AbstractOsgiTests

Base test for OSGi environments. Takes care of configuring the chosen OSGi platform, starting it, installing a number of bundles, and delegating the test execution to a test copy that runs inside OSGi.

AbstractConfigurableOsgiTests

AbstractConfigurableOsgiTests

Abstract JUnit superclass that configures an OSGi platform. This class offers more hooks for programmatic and declarative configuration of the underlying OSGi platform used when running a test suite.

AbstractSynchronizedOsgiTests

AbstractSynchronizedOsgiTests

JUnit superclass that offers synchronization for application context initialization. This class automatically determines Spring-powered bundles that are installed by the testing framework and (by default) will wait for their application context to fully start.

AbstractDependencyManagerTests

AbstractDependencyManagerTests

Dependency manager class that deals with locating various artifacts required by the OSGi test. The artifacts are considered to be OSGi bundles that will be installed during the OSGi platform startup. Additionally, this class installs the testing framework required bundles, such as Spring and Spring-DM.

AbstractOnTheFlyBundleCreatorTests

AbstractOnTheFlyBundleCreator
Tests

Enhanced subclass of AbstractDependencyManager Tests that facilities OSGi testing by creating a JAR at runtime using the indicated MANIFEST.MF file and resource patterns.

AbstractConfigurableBundle
CreatorTests
AbstractConfigurableBundle
CreatorTests

Abstract JUnit base class that allows easy integration testing. This class follows the traditional Spring style of integration testing in which the test simply indicates the dependencies, leaving the rest of the work to be done by its superclasses.

 

All these classes have the same purpose as other non-OSGi Spring testing classes you've met in this book: to provide a starting point with preconfigured testing behaviors that can be overridden on a case-by-case basis depending on the nature of the test.

For example, many of these Spring-DM test classes are designed to operate with some of the following behaviors, unless otherwise specified: use Eclipse Equinox as the OSGi testing environment, use an Apache Maven repository to load test dependencies, and generate a MANIFEST.MF file on the fly for the test bundle, among other things.

All these behaviors as you will see shortly can be overridden for a particular test class, so for example a test class may use Apache Felix as the OSGi testing environment, use an Apache Ivy repository to load test dependencies, or use a prebuilt MANIFEST.MF file for the test bundle.

Still with seven classes available to create Spring-DM tests, how do you decide which one is appropriate for a particular test? The short answer is it depends; however, the AbstractConfigurableBundleCreatorTests class is often the preferred choice. I will elaborate why.

As illustrated in Table 9-1, each of the Spring-DM test classes is a subclass to another (e.g., AbstractConfigurableOsgiTests is a subclass of AbstractOsgiTests, and AbstractOsgiTests is a subclass of AbstractOptionalDependencyInjectionTests). This inheritance hierarchy allows each test class access to its parent class methods.

For example, a method like getTestBundles, which is part of the AbstractDependency ManagerTests class, will be accessible and can be overridden in tests based on the AbstractOnTheFlyBundleCreatorTests and AbstractConfigurableBundleCreatorTests classes. Whether you need access to such a method or any other testing behavior will be dependent on what a test is designed to accomplish, which in turn will determine what class in the Spring-DM test hierarchy is the best option.

Inclusively, the top-level Spring-DM test class, AbstractOptionalDependencyInjection Tests, is a subclass tied to the Spring Framework's test class hierarchy. This allows Spring-DM test classes access to methods like getConfigLocations, onSetUp, addContext, and a series of assert* methods, which you've already been exposed to in non-OSGi Spring tests.

Therefore, because AbstractConfigurableBundleCreatorTests is a subclass of all the other Spring-DM testing classes, it's often the preferred choice for creating OSGi tests.

Let's create a Spring-DM test class using the AbstractConfigurableBundleCreatorTests class, designed to simply print the OSGi testing environment vendor and version, as well as the operating system used to run the test. Listing 9-4 shows this test class.

Example 9-4. Spring-DM Test Class Designed to Return the OSGi Environment Vendor and Version

package com.apress.springosgi.ch9.testsosgi;

import org.springframework.osgi.test.AbstractConfigurableBundleCreatorTests;
import org.springframework.osgi.test.provisioning.ArtifactLocator;

import org.osgi.framework.Constants;

import com.apress.springosgi.ch9.testsosgi.testivyprovisions.
Spring-DM Test Class Designed to Return the OSGi Environment Vendor and Version
LocalFileSystemIvyRepository; public class BootstrapTest extends AbstractConfigurableBundleCreatorTests { public String getRootPath() { return"file:./classes/"; }
public ArtifactLocator getLocator() {
        return new LocalFileSystemIvyRepository();
    }

    public void testOsgiPlatformStarts() throws Exception {
        System.out.println("OSGi Framework Vendor : " + bundleContext.
Spring-DM Test Class Designed to Return the OSGi Environment Vendor and Version
getProperty(Constants.FRAMEWORK_VENDOR)); System.out.println("OSGi Framework Vendor Version : " + bundleContext.
Spring-DM Test Class Designed to Return the OSGi Environment Vendor and Version
getProperty(Constants.FRAMEWORK_VERSION)); System.out.println("OS Name and Version : " + bundleContext.
Spring-DM Test Class Designed to Return the OSGi Environment Vendor and Version
getProperty(Constants.FRAMEWORK_OS_NAME) + "-" + bundleContext.
Spring-DM Test Class Designed to Return the OSGi Environment Vendor and Version
getProperty(Constants.FRAMEWORK_OS_VERSION)); } }

The test class is short, but the meaning behind each of its statements is critical. The first thing to note is that the test class inherits its behavior from the AbstractConfigurableBundleCreatorTests class. This will in turn give the test class the capacity to override Spring-DM's various testing methods.

Note that since this test relies on the getRootPath method, it's necessary to use the AbstractConfigurableBundleCreatorTests class, since only this Spring-DM test class provides this method. Were it not for this requirement, the test could be designed using a test class in the Spring-DM hierarchy as high as AbstractDependencyManagerTests, because this class provides access to the getLocator method. And barring the need for these last two methods, the test could be designed with the top-level Spring-DM test class AbstractOptionalDependencyInjectionTests, since the only remaining method, testOSGiPlatformsStarts, is a test method agnostic to the Spring-DM testing framework.

The first overridden method is getRootMethod, which is used by the Spring-DM testing framework to locate the test class. In this case, it's indicating to search for the test class under the classes directory of the present working directory. But why is it necessary to indicate where the test class is located in the file system? Especially in the test class itself?

When the test class is executed by a tool like Apache Maven or Apache Ivy via JUnit, the location of the test class is known beforehand. However, since this same test class needs to be packaged inside an OSGi bundle and deployed to an OSGi testing environment, it is necessary to provide the directory in which the test class can be located by the Spring-DM testing framework. If this method is not overridden, Spring-DM's testing framework attempts to locate the test class inside a directory named target/test-classes.

Needless to say, if the test class cannot be located in the indicated directory, the test will fail. This error can be especially misleading, since initially it is not obvious why the test class fails if it cannot find itself in a certain directory, even though you are able to execute the test class from a tool like Apache Maven or Apache Ivy.

The second overridden method is getLocator, which is used by the Spring-DM testing framework to define an ArtifactLocator. In this case, the test class is using a custom-made ArtifactLocator named LocalFileSystemIvyRepository with the purpose of using Apache Ivy as the repository to locate bundle dependencies for the test class.

An ArtifactLocator is important because it tells the Spring-DM testing framework where to locate bundle dependencies for executing the test class, such as the OSGi testing framework (Equinox, Felix, or Knopflerfish), JUnit bundles, Spring core bundles, Spring-DM bundles, and any other bundle like Apache Commons or Apache Tomcat needed to run a test.

If the getLocator method is not overridden, Spring-DM's testing framework attempts to locate bundle dependencies in an Apache Maven type repository located by default in .m2/repository of a user's home directory.

Since we relied on Apache Ivy instead of Apache Maven in Chapter 7 to fulfill dependencies, we will do the same to perform Spring-DM tests. The custom-made ArtifactLocator named LocalFileSystemIvyRepository configures the Spring-DM testing framework to load bundle dependencies from an Apache Ivy repository that by default would be located in .ivy2/cache of a user's home directory. See the book's accompanying download for this custom-made ArtifactLocator source code.

If the test class cannot locate the minimal set of dependencies needed to run a Spring-DM test in the declared ArtifactLocator, the test will fail. The minimum set of bundles that need to be present in an ArtifactLocator (Apache Maven or Apache Ivy) to run a test are shown in Listing 9-5.

Example 9-5. Minimum Set of Bundles Needed to Be Present in ArtifactLocator (Apache Maven or Apache Ivy) to Perform the Spring-DM Test

com.springsource.org.aopalliance.1.0.0,
com.springsource.junit,3.8.2,
com.springsource.org.objectweb.asm.2.2.3,
com.springsource.slf4j.api,1.5.0,
com.springsource.slf4j.log4j.1.5.0,
com.springsource.slf4j.org.apache.commons.logging,1.5.0,
org.springframework.spring-aop.2.5.5,
org.springframework.spring-beans,2.5.5,
org.springframework.spring-context,2.5.5,
org.springframework.spring-core.2.5.5,
org.springframework.spring-test.2.5.5,
org.springframework.osgi.log4j.osgi.1.2.15-SNAPSHOT,
org.springframework.osgi.spring-osgi-annotation.1.2.0-m1,
org.springframework.osgi.spring-osgi-core.1.2.0-m1,
org.springframework.osgi.spring-osgi-extender.1.2.0-m1,
org.springframework.osgi.spring-osgi-io.1.2.0-m1,
org.springframework.osgi.spring-osgi-test.1.2.0-m1

Note

This minimum set of bundles is in addition to the OSGi testing environment (Eclipse, Felix, or Knopflerfish) used to execute the tests, which also needs to be available in the ArtifactLocator.

Finally, the test class declares the testOsgiPlatformStarts method, which is executed at the outset of a test. This particular method prints out various constants belonging to the org.osgi.framework.Constants class, namely FRAMEWORK_VENDOR, FRAMEWORK_VERSION, FRAMEWORK_OS_NAME, and FRAMEWORK_OS_VERSION.

In regards to this last method, it is important to note that this method is executed once the OSGi testing framework has been bootstrapped. Therefore, the test results will print out whichever OSGi implementation is used to run the test.

The next step consists of downloading and prepping the necessary bundle dependencies needed to run the test—those in Listing 9-5.

Downloading Spring-DM Test Dependencies with Apache Ivy

Chapter 7 provides a formal introduction to the benefits and configuration parameters of Apache Ivy, so if you're not familiar with how Apache Ivy works, you may want to go back to that chapter and look at the section "Hello World Application Revisited Without the SpringSource dm Server: Data Access and Apache Ivy." Next, I will illustrate Apache Ivy's main configuration files needed to run Spring-DM tests.

Listing 9-6 shows Apache Ivy's ivy.xml file with the necessary <dependency> elements to download the minimal set of Spring-DM test dependencies.

Example 9-6. Apache Ivy ivy.xml File with Minimal Spring-DM Test Dependencies

<ivy-module version="2.0">
    <info organisation="apache" module="hello-ivy"/>
    <configurations>
         <conf name="build"/>
    </configurations>
   <!-- Download bundle dependencies for application testing (Non-OSGi and OSGi)-->
       <dependency org="org.junit" name="com.springsource.org.junit" rev="4.5.0"/>
       <dependency org="org.hsqldb" name="com.springsource.org.hsqldb" 
Apache Ivy ivy.xml File with Minimal Spring-DM Test Dependencies
rev="1.8.0.9" /> <dependency org="org.objectweb.asm"
Apache Ivy ivy.xml File with Minimal Spring-DM Test Dependencies
name="com.springsource.org.objectweb.asm" rev="2.2.3"/> <dependency org="org.aopalliance" name="com.springsource.org.aopalliance"
Apache Ivy ivy.xml File with Minimal Spring-DM Test Dependencies
rev="1.0.0"/> <dependency org="org.slf4j" name="com.springsource.slf4j.log4j"
Apache Ivy ivy.xml File with Minimal Spring-DM Test Dependencies
rev="1.5.0"/> <dependency org="org.springframework" name="spring-aop" rev="2.5.5.A"/> <dependency org="org.springframework" name="spring-beans" rev="2.5.5.A"/> <dependency org="org.springframework" name="spring-context" rev="2.5.5.A"/> <dependency org="org.springframework" name="spring-core" rev="2.5.5.A"/> <dependency org="org.springframework" name="spring-test" rev="2.5.5.A"/> <dependency org="org.springframework.osgi" name="log4j.osgi"
Apache Ivy ivy.xml File with Minimal Spring-DM Test Dependencies
rev="1.2.15-SNAPSHOT">
<artifact name="log4j.osgi" type="jar" 
Apache Ivy ivy.xml File with Minimal Spring-DM Test Dependencies
url="http://s3.amazonaws.com/maven.springframework.org/osgi/org/springframework/
Apache Ivy ivy.xml File with Minimal Spring-DM Test Dependencies
osgi/log4j.osgi/1.2.15-SNAPSHOT/log4j.osgi-1.2.15-20080314.153110-17.jar"/> </dependency> <dependency org="org.springframework.osgi" name="spring-osgi-annotation"
Apache Ivy ivy.xml File with Minimal Spring-DM Test Dependencies
rev="1.2.0-m1"/> <dependency org="org.springframework.osgi" name="spring-osgi-test"
Apache Ivy ivy.xml File with Minimal Spring-DM Test Dependencies
rev="1.2.0-m1"/> <dependency org="org.eclipse.osgi" name="org.eclipse.osgi"
Apache Ivy ivy.xml File with Minimal Spring-DM Test Dependencies
rev="3.5.0.200811061137"/> <dependency org="org.apache.felix" name="org.apache.felix.main" rev="1.0.4"/> <dependency org="knopflerfish" name="framework" rev="2.1.0"/> </dependencies> </ivy-module>

This last Apache Ivy configuration file takes care of downloading all the bundles enumerated in Listing 9-5, in addition to the three OSGi implementations that can be used to perform Spring-DM tests (Eclipse Equinox, Apache Felix, and Knopflerfish).

Besides the ivy.xml file, it is also necessary to define an ivysettings.xml file indicating the repositories from which these bundles can be downloaded. Listing 9-7 shows this file.

Example 9-7. Apache Ivy ivysettings.xml File for Spring-DM Test Dependencies

<ivysettings>

  <settings defaultResolver="chain-springosgi"/>

     <caches defaultCacheDir="${basedir}/ivylib">
     </caches>

  <resolvers>
    <chain name="chain-springosgi">
        <filesystem name="my-repository">
          <ivy pattern="${basedir}/ivylib/[organisation]/[module]/[revision]/
Apache Ivy ivysettings.xml File for Spring-DM Test Dependencies
[artifact]-[revision].[ext]"/> <artifact pattern="${basedir}/ivylib/[organisation]/[module]/[revision]/
Apache Ivy ivysettings.xml File for Spring-DM Test Dependencies
[artifact]-[revision].[ext]"/> </filesystem> <url name="spring-release-repo"> <ivy pattern="http://repository.springsource.com/ivy/bundles/
Apache Ivy ivysettings.xml File for Spring-DM Test Dependencies
release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" /> <artifact pattern="http://repository.springsource.com/ivy/bundles/
Apache Ivy ivysettings.xml File for Spring-DM Test Dependencies
release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" /> </url>
<url name="spring-external-repo">
                <ivy pattern="http://repository.springsource.com/ivy/bundles/
Apache Ivy ivysettings.xml File for Spring-DM Test Dependencies
external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" /> <artifact pattern="http://repository.springsource.com/ivy/bundles/
Apache Ivy ivysettings.xml File for Spring-DM Test Dependencies
external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" /> </url> <url name="spring-milestone-repo" m2compatible="true"> <artifact pattern="http://s3.amazonaws.com/
Apache Ivy ivysettings.xml File for Spring-DM Test Dependencies
maven.springframework.org/milestone/[organisation]/[module]/
Apache Ivy ivysettings.xml File for Spring-DM Test Dependencies
[revision]/[artifact]-[revision].[ext]"/> </url> <ibiblio name="ibiblio"/> <url name="default-repo" m2compatible="true"> <artifact pattern="http://repo1.maven.org/maven2/[organisation]/
Apache Ivy ivysettings.xml File for Spring-DM Test Dependencies
[module]/[revision]/[artifact]-[revision].[ext]"/> </url> <url name="knoper-repo" m2compatible="true"> <artifact pattern="http://springframework.svn.sourceforge.net/
Apache Ivy ivysettings.xml File for Spring-DM Test Dependencies
svnroot/springframework/repos/repo-ext/[organisation]/[module]/[revision]/
Apache Ivy ivysettings.xml File for Spring-DM Test Dependencies
[artifact]-[revision].[ext]"/> </url> </chain> </resolvers> </ivysettings>

This ivysettings.xml file is very similar to the one used in Chapter 7, with two areas worth emphasizing. A new repository, http://springframework.svn.sourceforge.net/, is added to the list, since the OSGi Knopflerfish implementation—one of the three options for performing Spring-DM tests—is only available in this repository. And the default defaultCacheDir value is assigned an ivylib value, meaning dependencies will be downloaded to a subdirectory by this name in the current working directory.

Having defined the Apache Ivy configuration files needed to download dependencies to run Spring-DM tests, it is necessary to create an Apache Ant configuration file to trigger this task. In addition, since Apache Ant will also be used to execute the Spring-DM test via JUnit, the next section will show the configuration file needed to perform these two tasks.

Executing a Spring-DM Test with Apache Ant

The Apache Ant configuration file needed to perform Spring-DM tests will consist of two parts: one related to downloading and prepping bundle dependencies using Apache Ivy and the other to using JUnit to execute the test class illustrated in Listing 9-4.

The directory layout used by this Apache Ant file—such as where it locates source files and where it places compiled classes—follows the same Hello World "playground" conventions used in other examples in the book. Listing 9-8 illustrates the Apache Ant configuration needed to perform Spring-DM tests.

Example 9-8. Apache Ant build.xml File for Spring-DM Tests

1     <?xml version="1.0"?>
2    <project xmlns:ivy="antlib:org.apache.ivy.ant" default="init" basedir=".">

3     <target name="init" description="Apress - Pro Spring-OSGi">
4       <tstamp/>
5       <property name="projectname" value="Pro Spring-OSGi"/>

6       <echo message="-------${projectname}------"/>
7       <property name="debug"          value="on"/>
8       <property name="optimize"          value="off"/>
9       <property name="deprication"      value="off"/>
10      <property name="build.compiler" value="modern"/>
11      <property name="target.vm"         value="1.5"/>
12      <property name="build.dir"         value="classes"/>
13      <property name="dist.dir"         value="dist"/>
14      <property name="src.dir"         value="src"/>
15      <property name="lib.dir"         value="lib/build"/>

16      <!-- Load JARs onto the classpath, taken from lib/build sub-dir -->

17      <path id="classpath">
18       <fileset dir="${lib.dir}">
19        <include name="*.jar"/>
20       </fileset>
21       <pathelement location="${build.dir}"/>
22      </path>
23    </target>

24    <target name="compile" depends="init" description="Compile code">
25      <ivy:retrieve pattern="lib/[conf]/[artifact]-[revision].[ext]" />

26      <echo message="-------Compiling code for Pro-Spring OSGi------"/>

27      <mkdir dir="${build.dir}"/>
28      <mkdir dir="${dist.dir}"/>

29      <javac srcdir="${src.dir}"
30             destdir="${build.dir}"
31             debug="${debug}"
32             optimize="${optimize}"
33             deprecation="${deprecation}"
34             target="${target.vm}">
35         <classpath refid="classpath"/>
36      </javac>

37     <copy todir="${build.dir}">
38               <fileset dir="${src.dir}">
39    <!-- Some of the following statements are relevant to Ch2 -->
40    <!-- They are present here because the same compile task is used -->
41                   <include name="**/*.properties"/>
42                   <include name="**/*.xml"/>
43                   <include name="**/*.sql"/>
44                   <include name="**/*.MF"/>
45                   <exclude name="**/*.java"/>
46                   <exclude name="META-INF/**"/>
47                   <exclude name="WEB-INF/**"/>
48                   <exclude name="GUI/**"/>
49                </fileset>
50      </copy>
51    </target>

52    <target name="ch9" depends="compile" description="Build Chapter 9
Apache Ant build.xml File for Spring-DM Tests
Spring-OSGi Application"> 53 <echo message="-------------- Building Chapter 9 Spring-OSGi Application
Apache Ant build.xml File for Spring-DM Tests
for Pro Spring-OSGi --------------"/> 54 <property name="ch9.dir" value="${dist.dir}/ch9/"/> 55 <mkdir dir="${ch9.dir}"/> 56 <property name="test.dir" value="${ch9.dir}/tests"/> 57 <mkdir dir="${test.dir}"/> 58 <echo message="-------------- Starting Unit and Integration Testing
Apache Ant build.xml File for Spring-DM Tests
(OSGi) --------------"/> 59 <junit printsummary="yes" fork="yes"> 60 <sysproperty key="localRepository" value="ivylib"/> 61 <sysproperty key="org.springframework.osgi.test.framework"
Apache Ant build.xml File for Spring-DM Tests
value="org.springframework.osgi.test.platform.KnopflerfishPlatform"/>
62    <!--           <sysproperty key="org.springframework.osgi.test.framework"
Apache Ant build.xml File for Spring-DM Tests
value="org.springframework.osgi.test.platform.FelixPlatform"/> --> 63 <!-- <sysproperty key="org.springframework.osgi.test.framework"
Apache Ant build.xml File for Spring-DM Tests
value="org.springframework.osgi.test.platform.EquinoxPlatform"/> --> 64 <classpath refid="classpath"/> 65 <formatter type="brief"/> 66 <batchtest todir="${test.dir}"> 67 <fileset dir="${build.dir}"> 68 <include name="com/apress/springosgi/ch9/testsosgi/*"/> 69 </fileset> 70 </batchtest> 71 </junit> 72 <echo message="-------------- Ending Unit and Integration Testing
Apache Ant build.xml File for Spring-DM Tests
(OSGi) --------------"/> 73 </target> 74 </project>

I will explain the Apache Ivy-related configuration first. Notice how the top-level <project> element (Line 2) declares the Apache Ivy namespace needed to execute Ivy tasks. Next, you will find various properties used for compiling classes, chief among them the <path> element (Lines 17 to 22) with a value of lib/build, which will be the directory containing JARs needed to compile a project's classes.

Next, you will find the compile target (Line 24) used for compiling classes. Notice how the first statement in this target is <ivy:retrieve pattern="lib/[conf]/[artifact]-[revision].[ext]" /> (Line 25); this is used to trigger the retrieval of Ivy dependencies—those defined in ivy.xml (Listing 9-6).

In accordance with this last Apache Ivy configuration file, this will initiate the download of bundles into the ivylib directory (the defaultCacheDir value). Once the downloading of bundles is complete, and this time in accordance with the <ivy:retrieve> element defined in the Apache Ant configuration file, copies of downloaded bundles will be made on the basis of the pattern value lib/[conf]/[artifact]-[revision].[ext].

In this value, the [conf] snippet represents <configurations>/<conf> values in ivy.xml (Listing 9-6). Since there is only one value— <conf name="build"/>—and neither <dependency> element has a conf attribute, this means all dependencies in ivylib will be copied over to lib/build/, which is precisely where the Apache Ant <path> element will attempt to load JARs needed to compile a project's classes. Chapter 7 contains a more elaborate scenario on this copying process, including a visual description of the entire process in Figure 7-3.

With all dependencies downloaded by Apache Ivy—for both compiling and executing Spring-DM tests—the compilation of classes takes place with Apache Ant's <javac> element (Lines 29 to 36). Here it's worth noting that the <javac> element places compiled classes inside destdir="${build.dir}" where {build.dir} has a value of classes. This location is where the Spring-DM test class was configured to locate the compiled test class—the getRootPath() method in Listing 9-4.

Next, we come to the Apache Ant ch9 target (Line 52), which triggers the actual Spring-DM test using the <junit> task (Lines 59 to 71). This task is the same one you employed in Chapter 2 to perform non-OSGi tests using Apache Ant, except this time it's equipped with a few additional parameters needed to run Spring-DM tests.

The first difference comes in the form of the attribute fork="yes"(Line 59).This attribute allows the test to run in a separate Java Virtual Machine (JVM), which is important since all tests require bootstrapping an OSGi environment. If this attribute is not used, the OSGi testing environment is bootstrapped in the same JVM as Apache Ant, a process that can cause security access errors in some OSGi environments; therefore, it's recommended to run tests in their own JVM.

Next, observe the <sysproperty key="localRepository" value="ivylib"/> element (Line 60), which is used to pass a variable named localRepository to the ArtifactLocator defined in the Spring-DM test.

By default, the custom-made ArtifactLocator defined for the test class attempts to locate bundle dependencies to run the test in .ivy2/cache of a user's home directory. However, since this class's bundle dependencies have already been downloaded into a directory named ivylib, this property overrides the location where the ArtifactLocator attempts to locate bundle dependencies.

Note

This same localRepository value can be used to override Spring-DM's default Apache Maven ArtifactLocator. So instead of attempting to locate bundle dependencies to run a test in .m2/repository of a user's home directory, an alternative directory containing a Maven repository can be used.

The second <sysproperty> element (Line 61) is used to define the OSGi environment on which to perform the test. For this purpose, the org.springframework.osgi.test.framework key value is used, with a value of either org.springframework.osgi.test.platform.EquinoxPlatform, org.springframework.osgi.test.platform.FelixPlatform, or org.springframework.osgi.test.platform.KnopflerfishPlatform, indicating Eclipse Equinox, Apache Felix, or Knopflerfish, respectively.

If this value is omitted, the Spring-DM testing framework will attempt to run the test with Eclipse Equinox. It should also be pointed out that the Spring-DM testing framework will attempt to locate whatever OSGi environment is specified in the ArtifactLocator.

Note

An alternative to specifying the OSGi testing environment using this last system property is to hard-code the desired OSGi testing environment in the test class itself by overriding the method getPlatformName().

Next, you will find the <classpath> element (Line 64), used by JUnit to load the necessary runtime libraries, that points toward the classpath <path> or lib/build directory. This is followed by the <formatter> element (Line 65), which generates brief test reports.

Finishing off the <junit> task, you will encounter the <batchtest> element (Lines 66 to 70), which is used to indicate where to place test reports—in this case ${test.dir}, which corresponds to dist/ch9/tests—and where to load tests classes from—in this case classes/com/apress/springosgi/ch9/testsosgi/, which is where the compiled Spring-DM test class is placed.

Upon invoking this Ant task, ch9, the <junit> task (Lines 59 to 71) will trigger the Spring-DM test, placing the test results in a text file inside the dist/ch9/tests/ directory. Listing 9-9. illustrates the results of running the test using Eclipse Equinox on a Linux operating system.

Example 9-9. Test Results of Running Spring-DM Test Class BootstrapTest

Testsuite: com.apress.springosgi.ch9.testsosgi.BootstrapTest
Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 2.197 sec

------------- Standard Output ---------------

18:25:48.708 [main] DEBUG c.a.s.ch9.testsosgi.BootstrapTest - About to start
Test Results of Running Spring-DM Test Class BootstrapTest
Equinox OSGi Platform 18:25:49.614 [main] INFO c.a.s.ch9.testsosgi.BootstrapTest - Equinox OSGi
Test Results of Running Spring-DM Test Class BootstrapTest
Platform [3.5.0.200811061137] started 18:25:49.641 [main] DEBUG c.a.s.ch9.testsosgi.BootstrapTest - Default framework
Test Results of Running Spring-DM Test Class BootstrapTest
bundles :{org.aopalliance,com.springsource.org.aopalliance,1.0.0, org.junit,com.springsource.junit,3.8.2, org.objectweb.asm,com.springsource.org.objectweb.asm,2.2.3, org.slf4j,com.springsource.slf4j.api,1.5.0, org.slf4j,com.springsource.slf4j.log4j,1.5.0, org.slf4j,com.springsource.slf4j.org.apache.commons.logging,1.5.0, org.springframework,spring-aop,2.5.5, org.springframework,spring-beans,2.5.5, org.springframework,spring-context,2.5.5, org.springframework,spring-core,2.5.5, org.springframework,spring-test,2.5.5, org.springframework.osgi,log4j.osgi,1.2.15- SNAPSHOT, org.springframework.osgi,spring-osgi-annotation,1.2.0-m1, org.springframework.osgi,spring-osgi-core,1.2.0-m1, org.springframework.osgi,spring-osgi-extender,1.2.0-m1, org.springframework.osgi,spring-osgi-io,1.2.0-m1, org.springframework.osgi,spring- osgi-test,1.2.0-m1} 18:25:49.646 [main] INFO c.a.s.c.t.t.LocalFileSystemIvyRepository - Local Ivy2
Test Results of Running Spring-DM Test Class BootstrapTest
repository used: [ivylib]
<OTHER INFO AND DEBUG OUTPUT OMITTED FOR BREVITY>
18:25:50.566 [main] DEBUG c.a.s.ch9.testsosgi.BootstrapTest - Looking for
Test Results of Running Spring-DM Test Class BootstrapTest
Spring/OSGi powered bundles to wait for... OSGi Framework Vendor : Eclipse OSGi Framework Vendor Version : 1.5.0 OS Name and Version : Linux-2.6.27

The initial lines of the test results will reflect a very long sequence of bundle installations performed by the Spring-DM testing framework, similar to the output generated by manually installing bundles in an OSGi shell.

Once all the required bundles for the Spring-DM testing framework and the test bundle containing the test class are installed, the test class is executed, outputting in this case the values of the OSGi implementation vendor and version, as well as the operating system used to run the test.

Now that you are familiar with how the different parts needed to run a Spring-DM test work together, I will show you how to create more elaborate Spring-DM test classes.

Hello World Application Spring-DM Test

Your first Spring-DM test didn't do all that much, except obtain values from the underlying OSGi testing environment and operating system used to run the test. A more interesting Spring-DM test would consist of guaranteeing a bundle publishes an OSGi service correctly, or the consumption of an OSGi service produces its expected outcome.

As it turns out, from Chapter 7 we have five bundles we can put to the test in order to avoid any surprises once they enter a production environment. So let's get started creating a Spring-DM test class with one of these bundles.

The test will consist of guaranteeing the integrity of the helloworld-db.jar bundle. This particular bundle is charged with providing a connection to a MySQL RDBMS and making it available as an OSGi service for other bundles in the application. Given this purpose, the following test will assert the OSGi service is published correctly and that the service can be used to perform a query against the RDBMS. Listing 9-10 shows the test class for the helloworld-db.jar bundle.

Example 9-10. Spring-DM DataSourceBundleTest for the helloworld-db.jar Bundle

1     package com.apress.springosgi.ch9.testsosgi;

2     import org.springframework.osgi.test.AbstractConfigurableBundleCreatorTests;
3     import org.springframework.osgi.test.provisioning.ArtifactLocator;

4     import java.util.List;
5     import java.util.ArrayList;
6     import java.util.Collections;
7      import org.springframework.core.io.Resource;
8     import org.springframework.core.io.FileSystemResource;

9     import org.osgi.framework.BundleContext;
10    import org.osgi.framework.ServiceReference;

11    import javax.sql.DataSource;
12    import org.apache.commons.dbcp.BasicDataSource;
13    import java.sql.Connection;
14    import java.sql.Statement;
15    import java.sql.ResultSet;

16    import com.apress.springosgi.ch9.testsosgi.testivyprovisions.
Spring-DM DataSourceBundleTest for the helloworld-db.jar Bundle
LocalFileSystemIvyRepository; 17 public class DataSourceBundleTest extends
Spring-DM DataSourceBundleTest for the helloworld-db.jar Bundle
AbstractConfigurableBundleCreatorTests { 18 protected String getRootPath() { 19 return"file:./classes/"; 20 } 21 protected ArtifactLocator getLocator() { 22 return new LocalFileSystemIvyRepository(); 23 } 24 protected String[] getTestBundlesNames() { 25 List col = new ArrayList(); 26 col.add("com.mysql.jdbc, com.springsource.com.mysql.jdbc, 5.1.6"); 27 col.add("org.apache.commons, com.springsource.org.apache.commons.dbcp,
Spring-DM DataSourceBundleTest for the helloworld-db.jar Bundle
1.2.2.osgi"); 28 col.add("org.apache.commons, com.springsource.org.apache.commons.pool, 1.3.0"); 29 return (String[]) col.toArray(new String[col.size()]); 30 } 31 protected Resource[] getTestBundles() { 32 Resource[] repoBundles = locateBundles(getTestBundlesNames()); 33 Resource[] testBundles = new Resource[] { 34 new FileSystemResource("dist/ch9/helloworld-db.jar") 35 }; 36 List<Resource> allBundles = new ArrayList<Resource>(); 37 Collections.addAll(allBundles, repoBundles); 38 Collections.addAll(allBundles, testBundles); 39 return allBundles.toArray(new Resource[] {}); 40 }
41         public void testDataSourceService() {
42            waitOnContextCreation("com.apress.springosgi.ch9.db");
43            //The superclass provides access to the testing bundle's context,
Spring-DM DataSourceBundleTest for the helloworld-db.jar Bundle
which can then get ahold of published services 44 ServiceReference ref = bundleContext.getServiceReference(DataSource.
Spring-DM DataSourceBundleTest for the helloworld-db.jar Bundle
class.getName()); 45 assertNotNull("Service Reference is null", ref); 46 try { 47 DataSource dataSourceService = (DataSource) bundleContext.
Spring-DM DataSourceBundleTest for the helloworld-db.jar Bundle
getService(ref); 48 assertNotNull("Cannot find the service", dataSourceService); 49 // Start connection and query sequence to the DB using
Spring-DM DataSourceBundleTest for the helloworld-db.jar Bundle
OSGi-backed service 50 Connection dataSourceConnection = dataSourceService.getConnection(); 51 Statement statement = dataSourceConnection.createStatement(); 52 ResultSet result = statement.executeQuery("select * from
Spring-DM DataSourceBundleTest for the helloworld-db.jar Bundle
HelloWorld where language = 'Spanish'"); 53 boolean rs = result.next(); 54 if (rs) { 55 assertEquals("Hola Mundo!",result.getString("message")); 56 } else { 57 throw new Exception("No record in DB belonging to Spanish
Spring-DM DataSourceBundleTest for the helloworld-db.jar Bundle
tranlsation"); 58 } 59 result.close(); 60 statement.close(); 61 dataSourceConnection.close(); 62 } catch (Exception exc) { 63 exc.printStackTrace(); 64 } finally { 65 bundleContext.ungetService(ref); 66 } 67 } 68 }

Just like the earlier test, this class inherits its behavior from Spring-DM's AbstractConfigurableBundleCreatorTests class (Line 17). This test class also overrides the getRootPath (Lines 18 to 20) and getLocator (Lines 21 to 23) methods to suit the particular needs of the project, which is to locate the test class inside the classes directory and locate bundle dependencies in an Apache Ivy repository.

Besides these two methods, which you are already familiar with, there are two new methods, getTestBundlesNames (Lines 24 to 30) and getTestBundles (Lines 31 to 40). These methods are used to load additional bundle dependencies needed to run tests in the Spring-DM testing framework.

The getTestBundlesNames method loads bundles from the ArtifactLocator defined in the test class, which in this case corresponds to a custom ArtifactLocator designed to use an Apache Ivy repository. The format used to define dependencies in the getTestBundlesNames method consists of a String array, with each string following a group, ID, and version notation, where the group, ID, and version are values in line with the conventions used to define dependencies in Apache Ivy and Apache Maven. In Apache Ivy the values for group, ID, and version map to the ivy.xml <dependency> element attributes org, name, and rev, respectively.

The getTestBundlesNames method for this particular test declares org.apache.commons.dbcp, org.apache.commons.pool, and com.mysql.jdbc as prerequisite bundles. This is because all three bundles are needed by the helloworld-db.jar bundle and as a consequence need be installed in order to perform a test on this last bundle.

Note

Upon defining dependencies in a test class's getTestBundlesNames method, you will also need to ensure the ArtifactLocator is updated to download such dependencies. In such cases, you need to add the necessary <dependency> statements to Ivy's ivy.xml configuration file.

Since not every bundle needed to run a test will be located in a repository like Apache Ivy or Apache Maven, the getTestBundles method is used to load bundles using a file system route—via the FileSystemResource class. As a consequence, this method fits perfectly for loading the application helloworld-db.jar bundle.

This particular instance of getTestBundles, in addition to loading the helloworld-db.jar bundle using a FileSystemResource instance, also appends the bundles declared in the getTestBundlesNames method. This is due to Spring-DM's testing framework design. In order to use both the getTestBundles and getTestBundlesNames in a test class, the former method needs to explicitly call the latter; otherwise, the bundles declared in getTestBundlesNames are not installed in the OSGi testing environment.

Once all the required bundles for running the test are installed, the first and only test method, testDataSourceService, is executed. This test method will assert that an OSGi DataSource service is available and perform a query against the RDBMS.

The first declaration (Line 42) inside the test method is waitOnContextCreation("com.apress.springosgi.ch9.db"). The use of this statement tells the Spring-DM testing framework to hold the test until the context of a bundle with a symbolic name com.apress.springosgi.ch9.db is created; in this case, the symbolic name corresponds to the helloworld-db.jar bundle.

The waitOnContextCreation method is an important safeguard for performing tests, since it guarantees a test will have access to the bundle context on which the test will be performed. On tests consisting of multiple bundles, this can be especially important since it ensures that certain bundles providing services to other test bundles have their context created first.

The next line in the test class (Line 44) attempts to retrieve a service reference of the type DataSource.class.getName(), which is also equal to javax.sql.DataSource. Note that this service reference is located using the bundleContext.getServiceReference call, which is a standard OSGi method belonging to the org.osgi.framework package. The bundleContext in this case refers to the test bundle created on the fly by the Spring-DM testing framework to execute the test.

Next, you can see the assertNotNull statement (Line 45), which is used to guarantee the prior service reference is not null. By design, the helloworld-db.jar bundle is configured to publish a javax.sql.DataSource service reference, so if this application bundle installed using the getTestBundles method is designed correctly, the assertion will pass; otherwise the assertion and test will fail.

Following this first assertion is a try/ catch block (Lines to 46 to 66) that will perform a query on the RDBMS. The first step consists of obtaining the OSGi service for the javax.sql.DataSource service reference (Line 47). The service reference is obtained using the statement bundleContext.getService(ref), which also corresponds to a standard OSGi method.

Immediately after the service reference is obtained, another assertion statement is made to guarantee that the service is not null (Line 48). Here again, if for some reason the service published by the helloworld-db.jar bundle is not working correctly, the assertion and test will fail.

At this point the test guarantees the OSGi service is published by helloworld-db.jar, but the test can go a step further, executing a query against the RDBMS using this same service. Since the dataSourceService is a javax.sql.DataSource, this test can be performed using the regular JDBC query process: create a Connection object, followed by a Statement object and a ResultSet object.

Since we know beforehand the dataset held in the RDBMS, we also know the query performed against the RDBMS, select * from HelloWorld where language = 'Spanish', should return a certain set of values. In this case, we expect the specified query to return an Hola Mundo! value for the message column. The final assertion in the form of assertEquals (Line 55) guarantees the data obtained from the RDBMS via an OSGi service is the correct data.

Given that this test class belongs to the same package, com.apress.springosgi.ch9.testsosgi, as the earlier test class, by placing it in the same source directory, the same Apache Ivy and Apache Ant configuration files can be used to trigger the execution of the test.

The mechanism for creating test classes for the remaining Hello World application bundles follows this same pattern, with the most variable step being the installation of dependency bundles needed to run the test from either a repository—like Apache Maven or Apache Ivy—or the file system, via the getTestBundlesNames and getTestBundles methods. For example, a test class for the Hello World application web bundle (WAR) would require the installation of Tomcat and a series of additional Spring bundles, as well as the other application bundles, since they are all dependencies for the web bundle (WAR).

As far as the test methods are concerned, the logic in such tests is open ended. As illustrated in this last test class, a test method can use APIs ranging from the OSGi framework to get ahold of services provided by other bundles in the test environment, to JDBC calls that interact with an RDBMS, to any other API required to assert a bundle's logic.

Finally, it's worth mentioning that the AbstractConfigurableBundleCreatorTests class has a few more methods that can be overridden. These range from the getManifestLocation method, used to provide a prebuilt MANIEST.MF file for the test bundle (instead of letting the Spring-DM testing framework generate one for you), to the getConfigLocations method, used to provide a prebuilt application context for the test bundle. Such methods, however, are rarely required to be overridden. You can consult the AbstractConfigurable BundleCreatorTests class's Java docs for more information on these and other methods.

This concludes our exploration into testing bundles using the Spring-DM testing framework, and with it the overall subject of testing Spring and OSGi applications.

Summary

In this chapter you learned how testing plays an important role in the overall life cycle of application development, ensuring a class's logic works as expected once in a production environment. In this same context, you explored why Spring-DM eases the creation of tests when an application leverages OSGi.

You then explored how non-OSGi tests—unit and integration with an RDBMS—can be equally applied to a code base that uses OSGi, thanks in part to the decoupling of OSGi logic achieved by using Spring-DM.

Then you continued on to explore the Spring-DM testing framework. You learned how it reduces the amount of work needed to perform tests on OSGi bundles, aiding in the bootstrapping of an OSGi testing environment and packaging test classes into bundles, as well as providing a series of base classes on which to construct tests.

You then created a test class that illustrated the basic procedure for all Spring-DM test classes. You saw how to override a test class's methods to accommodate the needs of a particular project and use an Apache Ivy repository to satisfy the bundle dependencies needed by a test, as well as use Apache Ant to execute Spring-DM tests.

Finally, you created one more test class designed around the application created in Chapter 7. This test class illustrated how a Spring-DM-powered bundle could be loaded into an OSGi testing environment, verifying that its OSGi services were published correctly and that such services were providing logical results.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.55.18