This article is based on Enterprise OSGi in Action, to be published on March 2012. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) eBooks and pBooks. MEAPs are sold exclusively through Manning.com. All pBook purchases include free PDF, mobi and epub. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information. [ Use promotional code ‘java40beat’ and get 40% discount on eBooks and pBooks ]
also read:
- Java Tutorials
- Java EE Tutorials
- Design Patterns Tutorials
- Java File IO Tutorials
Introduction
If you’re like a lot of developers, you’ll probably divide your testing into a few phases. The lowest level tests you’ll run are simple unit tests that exercise individual classes but not their interactions with one another. The next group of tests in your test hierarchy is the integration tests, which test the operation of your application. Finally, at the highest level, you may have system tests, which test the complete system, preferably in an environment that is very close to your production one.
When you’re testing OSGi bundles, each of these types of testing requires a different approach—different from the other phases and also different from how you’d do similar testing for an application intended to run on a JEE server or standalone. We’ll start by discussing unit testing, since that’s the simplest case in many ways. We’ll then show you some tools and strategies that we hope you’ll find useful for integration and system testing.
Unit testing OSGi bundles is pretty straightforward, but you’ll find you’ve got choices to make and a bewildering array of tool options when you start looking at integration testing. The good news is that, if you’ve already decided how to build your bundles, some of the choices about how best to test them will have been made for you. The bad news is that you’ve still got quite a few choices! Figure 1 shows some of these choices.
Figure 1 The ways of testing OSGi applications.Any test process should include a simple unit test phase, but the best way to do integration test depends on a number of factors, including which build tools are already being used.
Unit testing OSGi
By definition, unit tests are designed to run in the simplest possible environment. The purest unit tests have only the class you’re testing and any required interfaces on the classpath. In practice, you’ll probably also include classes that are closely related to the tested class and possibly even some external implementation classes. However, unit tests needn’t—and shouldn’t—require big external infrastructure like an OSGi framework.
So how can code that’s written to take advantage of the great things OSGi offers work without OSGi? How can such code be tested? Luckily, the answer is pretty easily.
Mocking out
The enterprise OSGi features of your runtime environment will ideally be handling most of the direct contact with the OSGi libraries for you.
Even if you need to reference OSGi classes directly, you may feel more comfortable writing your application so that it doesn’t actually have too many explicit OSGi dependencies. After all, loose coupling is good, even when the coupling is to a framework as fabulous as OSGi.
If you do have direct OSGi dependencies, using mocking frameworks to mock out OSGi classes like BundleContext can be helpful. You can even mock up service lookups, although if you’re using Blueprint or declarative services you’ll probably very rarely have cause to use the OSGi services API directly. In fact, using Blueprint has a lot of testability advantages beyond just eliminating direct OSGi dependencies.
Blueprint-driven testing
One of the really nice side effects of dependency injection frameworks like Blueprint is that unit testing becomes much easier. Although Blueprint won’t do your testing for you, testing code which was written with Blueprint in mind is an awful lot easier.
Separating out the logic to look up or construct dependencies from the work of doing things with those dependencies allows dependencies to be stubbed out without affecting the main control flow you’re trying to test. The key is to realize that, while you certainly need a Blueprint framework to be present to run your application end-to-end, you don’t actually need it at all to test components of the application in isolation. Instead, your test harness can inject carefully managed dependencies into the code you’re trying to test. You can even inject mock objects instead of real objects, which would be almost impossible if your inter-component links were all hard-wired.
To see this in action, let’s have a look at the DesperateCheeseOffer again. It depends on the Inventory, which requires container-managed JPA and a functioning database. Clearly, we don’t want to be trying to get a bunch of JPA entities and a database going just for a unit test. Instead, we’ll mock up an Inventory object and use the setter methods we created for Blueprint injection for unit testing instead.
Listing 1 Using mocked injection to unit test the desperate cheese offer
public class DesperateCheeseOfferTest { @Test public void testOfferReturnsCorrectFood() { Food food = mock(Food.class); #1 when(food.getName()).thenReturn("Green cheese"); Inventory inventory = mock(Inventory.class); List foods = new ArrayList(); foods.add(food); when(inventory.getFoodsWhoseNameContains("cheese", 1)) .thenReturn(foods); DesperateCheeseOffer offer = new DesperateCheeseOffer(); #2 offer.setInventory(inventory); assertNotNull(offer.getOfferFood()); #3 assertEquals("Green cheese", offer.getOfferFood().getName()); } } #1 Use Mockito to mock out classes #2 Initialise the cheese offer #3 Test the cheese offer behaves as expected
This sort of testing allows you to verify that if the injected Inventory object behaves as expected, the DesperateCheeseOffer does the right thing. You could also add more complex tests which confirmed that the cheese offer tolerated the case when Inventory had no cheeses in it at all, or the case when there was more than one cheese present in the inventory.
Although tests running outside an OSGi framework are straightforward and can spot a range of problems, there are several classes of problems they can’t detect. In particular, unit tests will still continue to run cleanly even if bundles fail to start or Blueprint services never get injected. To catch these issues, you’ll also want to test the end-to-end behavior of your application, inside an OSGi framework.
How closely this OSGi framework matches your production environment is a matter of taste. You may choose to do automated integration testing in a quite minimal environment, and follow it up with a final system test in a mirror of your production system. Alternatively, you may find you flush more problems out more quickly if you test in a more fully featured environment. The tools and techniques we’ll describe for testing inside an OSGi environment are broadly suitable for both integration and system testing.
Pax Exam
The gold standard for OSGi testing tools is a tool called Pax Exam. Pax Exam is part of a suite of OSGi-related tools developed by the OPS4J open source community. In contrast to other open source communities like the Apache and Eclipse Foundations, OPS4J has an interestingly flat structure that emphasizes open participation as well as open consumption. There is no notion of a committer, no barrier to committing source changes, and very little internal hierarchy.
Pax Exam builds on other tools developed by OPS4J, such as Pax Runner, to provide a sophisticated framework for launching JUnit—or TestNG—tests inside an OSGi framework and collecting the results. Under the covers, the Pax Exam framework wraps test classes into a bundle (using bnd to generate the manifest) and then automatically exposes the tests as OSGi services. Pax Exam then invokes each test in turn and records the results.
How clean is your framework?
By default, Pax Exam will start a fresh framework for each test method, which means Pax Exam tests may run rather slowly if you’ve got a lot of them. In recent versions, you can speed things up—at the risk of intertest side-effects—by specifying an @ExamReactorStrategy annotation. You can also choose whether Pax Exam launches the OSGi frameworks inside the main JVM or forks a new JVM for each framework and runs tests by RMI invocation. Not spawning a new JVM makes things far faster, and it also means you can debug your tests without having to attach remote debuggers. However, many of the more useful options for configuring frameworks are only supported for the remote framework case.
Which container to use is determined by which container you list in your Maven dependency. To use the quicker non-forking container, add the following dependency:
<dependency> <groupId>org.ops4j.pax.exam</groupId> <artifactId>Pax Exam-container-native</artifactId> <version>${paxexamversion}</version> <scope>test</scope> </dependency>
To use the more powerful but slower Pax Runner-based container, specify the following:
<dependency> <groupId>org.ops4j.pax.exam</groupId> <artifactId>Pax Exam-container-paxrunner</artifactId> <version>${paxexamversion}</version> <scope>test</scope> </dependency> - See more at: https://javabeat.net/how-to-test-osgi-applications/#sthash.pRGDVHT3.dpuf
Enabling tests for Pax Exam
A JUnit test intended for Pax Exam has a few key differences from one which runs standalone. Running your test code in an entirely different JVM from the one used to launch the test, with RMI and all sorts of network communication going on in the middle, isn’t something the normal JUnit test runner is designed to handle.
You’ll need to run with a special Pax Exam runner instead by adding a class-level annotation:
@RunWith(org.ops4j.pax.exam.junit.JUnit4TestRunner.class)
You can also choose to have a bundle context injected into your test class:
@Inject protected BundleContext ctx;
Configuring a framework
You’ll also need to tell Pax Exam what you want in your OSGi framework. This is done in a method annotated @Configuration. Pax Exam provides a fluent API for building up configuration options. Pax Exam gives you detailed control over the contents and configuration of your OSGi framework. You can specify the OSGi framework implementation (Equinox, Felix, Knopflerfish) and version or any combination of implementations and versions, system properties, and installed bundles. You can specify a list of bundles to install, as well as vm options, and OSGi frameworks. All of these can be controlled using methods statically imported from org.ops4j.pax.exam.CoreOptions, and combined into an array using the CoreOptions.options() method.
@Configuration public static Option[] configuration() { MavenArtifactProvisionOption foodGroup = mavenBundle().groupId( "fancyfoods"); Option[] fancyFoodsBundles = options( foodGroup.artifactId("fancyfoods.department.cheese").version("1.0.0"), foodGroup.artifactId("fancyfoods.api").version("1.0.0"), foodGroup.artifactId("fancyfoods.persistence").version("1.0.0"), foodGroup.artifactId("fancyfoods.datasource").version("1.0.0")); Option[] server = PaxConfigurer.getServerPlatform(); Option[] options = OptionUtils.combine(fancyFoodsBundles, server); return options; }
Here we’re installing the fancyfoods.department.cheese bundle, along with its dependencies and the bundles that make up the hosting server. Most of your tests will probably run on the same base platform (or platforms), so it’s worth pulling out common configuration code into a configuration utility, PaxConfigurer in this case. If you do this you can use OptionUtils.combine() to merge the option array and Option varargs into one big array.
Using Maven
Pax Exam is very well integrated with Maven, so one of the most convenient ways of specifying bundles to install is using their Maven coordinates and the CoreOptions.mavenBundle() method. Versions can be explicitly specified, pulled from a pom.xml using versionAsInProject(), or left implicit to default to the latest version.
Using an existing server install
In addition to your application bundles, you’ll need to list all the other bundles in your target runtime environment. If you think about using mavenBundle() calls to specify every bundle in your server runtime, you may start to feel uneasy. Do you really need to list out the Maven coordinates of every bundle in your Aries assembly—or worse yet, every bundle in your fully fledged application server?
Luckily, the answer is no—Pax Exam does provide alternate ways of specifying what should get installed into your runtime. You can install Pax Runner or Karaf features if any exist for your server or just point Pax Exam at a directory which contains your server. For testing applications intended to run on a server, this is the most convenient option—you probably don’t need to test your application with several different OSGi frameworks or see what happens with different Blueprint implementations, because your application server environment will be well-defined. Listing 2 shows how to configure a Pax Exam environment based on an Aries assembly used for testing.
Using profiles
Pax Exam also provides some methods to reference convenient sets of bundles, such as a and a set webProfile() of junitBundles(). Remember that Pax Exam installs only the bundles you tell it to install – your server probably doesn’t ship JUnit, so if you point Pax Exam at your server directory you’ll need to add in your test class’s JUnit dependencies separately. Since it can be complex, even with profiles, we find it can be convenient to share the code for setting up the test environment. Listing 2 shows a utility class for setting up a test environment that reproduces an Aries assembly used for testing.
Listing 2 A class that provides options which can be shared between tests
package fancyfoods.department.cheese.test; import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.options.extra.DirScannerProvisionOption; import static org.ops4j.pax.exam.CoreOptions.*; public class PaxConfigurer { public static Option[] getServerPlatform() { String ariesAssemblyDir = "${aries.assembly}/target"; #1 Option bootPackages = bootDelegationPackages("javax.transaction", "javax.transaction.*"); String f = "*-*.jar"; DirScannerProvisionOption unfiltered = scanDir(ariesAssemblyDir); Option ariesAsembly = unfiltered.filter(f); #2 Option osgiFramework = equinox().version("3.5.0"); return options(bootPackages, ariesAsembly, junitBundles(), osgiFramework); } } #1 The path to the Aries assembly #2 Only include jars with *-* in the name
Here, ${aries.assembly} should be replaced with an actual path—there’s no clever variable substitution going on!
WARNING: One OSGi framework good, two OSGi frameworks bad
Pax Exam will install all the bundles in the scanned directory into an OSGi framework. Since the scanned directory contains its own OSGi framework bundle, this means you may getting slightly more than you bargained for. Installing one OSGi framework into another is possible, but it the classloading gets complicated! To avoid installing multiple frameworks, you can either copy all your server bundles except for the OSGi framework itself to another directory, or rename the OSGi bundle so that it’s not captured by your filter. For example, if you rename the bundle to osgi.jar (no dash) and then specify the filter “*-*.jar”), Pax Exam will install every jar except for your framework jar (assuming all the other jars have dashes before the version number).
All this setup might seem like quite a bit of work, and it is. Luckily, once you’ve done it for one test, writing all your other tests will be much easier.
The Pax Exam test
So what kind of things should you be doing in your tests? The first thing to establish is that your bundles are present and that they’re started. (And if not, why not!) Bundles that haven’t started are one of the more common problems in OSGi applications and are especially common with Pax Exam because of the complexity of setting up the environment. Despite this, checking bundle states isn’t a first-class Pax Exam diagnostic feature. It’s worth adding in your tests some utility methods that try and start the bundles in your framework to make sure everything is started and fail the test if any bundles can’t start.
After this setup verification, what you test will depend on the exact design of your application. Verifying that all your services, including Blueprint ones, are present in the service registry is a good next step—and the final step is to make sure your services all behave as expected, for a variety of inputs. Listing 3 shows a test for the cheese bundle.
Listing 3 A Pax Exam integration test
@Test public void testOfferReturnsCorrectFood(BundleContext ctx) { Bundle bundle = getInstalledBundle(ctx, "fancyfoods.department.cheese"); try { bundle.start(); } catch (BundleException e) { fail(e.toString()); #1 } SpecialOffer offer = waitForService(bundle, SpecialOffer.class); assertNotNull("The special offer gave a null food.", #2 offer.getOfferFood()); assertEquals("Did not expect " + offer.getOfferFood().getName(), "Wensleydale cheese", offer.getOfferFood().getName()); } protected T waitForService(Bundle b, Class clazz) { try { BundleContext bc = b.getBundleContext(); ServiceTracker st = new ServiceTracker(bc, clazz.getName(), null); st.open(); Object service = st.waitForService(30 * 1000); #3 assertNotNull("No service of the type " + clazz.getName() + " was registered.", service); st.close(); return (T) service; } catch (Exception e) { fail("Failed to register services for " + b.getSymbolicName() + e.getMessage()); return null; } } #1 If the bundle can't start, find out why #2 Get the offer service #3 Give the service thirty seconds to appear
Unlike normal JUnit test methods, Pax Exam test methods can take an optional BundleContext parameter. You can use it to locate the bundles and services you’re trying to test.
WARNING: Blueprint and fast tests
An automated test framework will generally start running tests as soon as the OSGi framework is initialized. This can cause fatal problems when testing Blueprint bundles, because Blueprint initializes asynchronously. At the time you run your first test, half your services may not have been registered! You may find you suffer from perplexing failures, reproducible or intermittent, unless you slow Pax Exam down. Waiting for a Blueprint-driven service is one way of ensuring things are mostly ready, but unfortunately just because one service is enabled doesn’t mean they all will be. In the case of the cheese test, waiting just for the SpecialOffer service will do the trick, since that’s the service you’re testing.
Running the tests
If you’re using Maven, and you keep your test code in Maven’s src/test/java folder, your tests will automatically be run when you invoke the integration-test or install goals. You’ll need one of the Pax Exam containers declared as a Maven dependency. If you’re using ant instead, don’t worry—Pax Exam also supports Ant.
Tycho test
Although Pax Exam is a very popular testing framework, it’s not the only one. In particular, if you’re using Tycho to build you’re bundles, you’re better off using Tycho to test them as well. Tycho offers a nice test framework that, in many ways, is less complex than Pax Exam.
Tycho is a Maven-based tool, but like Tycho build, Tycho test uses an Eclipse PDE directory layout. Instead of putting your tests in a test folder inside your main bundle, Tycho expects a separate bundle. It relies on naming conventions to find your tests, so you’ll need to name your test bundle with a .tests suffix.
Fragments and OSGi unit testing
Tools like Pax Exam will generate a bundle for your tests, but with Tycho you’ve got control of where your tests go. To allow full white-box unit testing of your bundle, you may find it helpful to make the test bundle a fragment of the application bundle. This will allow it to share a classloader with the application bundle and drive all classes, even those which aren’t exported.
Configuring a test framework
Tycho will use your bundle’s package imports to provision required bundles into the test platform. Like any provisioning which relies solely on package dependencies, it’s unlikely that all the bundles your application needs to function properly will be provisioned. API bundles will be provisioned, but service providers probably won’t be. It’s certain that the runtime environment for your bundle won’t be exactly the same as the server you eventually intend to deploy on.
In order to ensure Tycho provisions all your required bundles, you can add them to your test bundle’s manifest using Require-Bundle. Tycho will automatically provision any dependencies of your required bundles, so you won’t need to include your entire runtime platform. However, you may find the list is long enough that it’s a good idea to make one shared ‘test.dependencies’ bundle whose only function is to require your test platform. All your other test bundles can just require the test.dependencies bundle.
Your test bundles will almost certainly have a dependency on JUnit, so you’ll need to add in one of the main Eclipse p2 repositories so that Tycho can provision the JUnit bundle.
eclipse-helios p2 http://download.eclipse.org/releases/helios
As with Pax Exam, you may find it takes you a few tries to get the Tycho runtime platform quite right. Until you’ve got a reliable platform definition, you may spend more time debugging platform issues than legitimate failures. Don’t be deterred—having a set of solid tests will pay back the effort many times over, we promise!
Rolling your own test framework
OSGi testing tools provide some way of integrating a unit test framework, like JUnit, into an OSGi runtime. In order to do this, they require you to do some fairly elaborate definition and configuration of this runtime, either in test code (Pax Exam) or in Maven scripts and manifest files (Tycho). Sometimes the benefits of running JUnit inside an OSGi framework don’t justify the complexity of getting everything set up to achieve this.
Instead of using a specialized OSGi testing framework, some developers rely on much more low-tech or hand-rolled solutions. A bundle running inside an OSGi framework can’t interact with a JUnit runner without help, but this doesn’t mean it can’t interact with the outside world. It can provide a bundle activator or eager Blueprint bean write-out files, or it can expose a servlet and write status to a web page. Your test code can find the file or hit the servlet and parse the output to work out if everything is behaving as expected. It can even use a series of JUnit tests which hit different servlets or read different log files so that you can take advantage of your existing JUnit reporting infrastructure.
This method of testing OSGi applications always feels a little inelegant to us, but it can work really well as a pragmatic test solution. Separating out the launching of the framework from the test code makes it much easier to align the framework with your production system. In fact, you can just use your production system as is to host the test bundles. It’s also much easier to debug configuration problems if the framework doesn’t start as expected. We have witnessed the sorry spectacle of confused Pax Exam users shouting at an innocent laptop “Where’s my bundle? And why won’t you tell me that six bundles in my framework failed to start?” at the end of a long day’s testing.
If you’re planning to deploy your enterprise OSGi application on one of the bigger and more muscular application servers, you may find that it’s more application server than you need for your integration testing. In this case, you may be better off preparing a sandbox environment for testing, either by physically assembling one or just by working out the right minimum set of dependencies.
The Aries assembly we’ve been using to run the examples is a good example of a hand-assembled OSGi runtime. We started with a simple Equinox OSGi framework and just added in the bundles needed to support the enterprise OSGi programming model. Alternatively, there are several open source projects that provide lightweight and flexible OSGi runtimes.
Pax Runner
Pax Runner is a slim container for launching OSGi frameworks and provisioning bundles. Pax Exam relies heavily on Pax Runner to configure and launch its OSGi framework. Pax Runner comes with a number of predefined profiles, which can be preloaded into the framework. For example, to launch an OSGi framework which is capable of running simple web applications, it’s sufficient to use the command:
pax-run.sh --"profiles=web"
At the time of writing, there isn’t a Pax Runner profile that fully supports the enterprise OSGi programming model. However, profiles are basically text files that list bundle locations, so it’s simple to write your own profiles. These profiles can be shared between members of your development team, which is nice, and fed to Pax Exam for automated testing, which is even nicer. Pax Runner also integrates with Eclipse PDE, so you can use the same framework in your automated testing and your development-time testing.
pax-run.sh --file:///yourprofile.txt
Although Pax Runner is described as a provisioning tool, Pax Runner can’t provision bundles like provisioners. You’ll need to list every bundle you want to include in your profile file.
Karaf
An alternative to Pax Runner with slightly more dynamic provisioning behavior is Apache Karaf. Like the name implies, Karaf is a little OSGi container. Karaf has some handy features missing in Pax Runner, like hot deployment and runtime provisioning. This functionality makes Karaf suitable for use as a production runtime rather than just as a test container. In fact, Karaf is so suitable for production; it underpins the Apache Geronimo server. However, Karaf isn’t so well integrated into existing unit test frameworks as Pax Runner, so if you use Karaf for your testing, you’ll mostly be limited to the “use JUnit to scrape test result logs” approach we described above.
Karaf has quite a lot of support for Apache Aries, which makes it a convenient environment for developing and testing enterprise OSGi applications. So well-integrated is Karaf with Aries, Karaf comes with Blueprint support by default.
Even better, if you list the OSGi bundles using the osgi:list, there’s a special column which shows each bundle’s Blueprint status.
HOT DEPLOYMENT
Like the little Aries Assembly we’ve been using, Karaf can install bundles from the filesystem dynamically. Karaf monitors the ${karaf.home}/deploy directory, and will silently install any bundles (or feature definitions) dropped into it. To confirm which bundles are installed and see their status, you can use the list command.
KARAF FEATURES
Karaf features, like Pax Runner profiles, are pre-canned groups of bundles that can easily be installed. As an added bonus, Karaf features have been written for much of the Apache Aries components. To see all the available features, type features:list. To install new features, type features:install -v (the -v option does a verbose install). To get a working Apache Aries runtime with Karaf features, it’s sufficient to execute the following commands:
features:install -v war features:install -v jpa features:install -v transaction features:install -v jndi
You’ll also need to enable the http server by creating a file called ${karaf.home}/etc/org.ops4j.pax.web.cfg and specifying an http port:
org.osgi.service.http.port=8080
To get our sample Fancy Foods application going, the final step is to install a database by copying one into the deploy directory, and then copy your fancy foods jars into the deploy directory. You can also install the bundles from the console using Maven coordinates.
The Karaf console is really nice, but of course it doesn’t lend itself so well to automation. Luckily, repositories can be defined, features installed, and bundles started by changing files in ${karaf.home}. If you’re very keen to automate, the Karaf console can even be driven programmatically.
Summary
The OSGi tools ecosystem is big, and many of the tools are very powerful (or complicated!). The key differentiators between many of the tool stacks is whether your OSGi development starts with an OSGi manifest, or starts with plain code. Once you’ve made this decision, you’ll find the choice between tools like bnd and Tycho starts to get made for you. As you move on to test your bundles, you’ll find that many of your build-time tools have natural testing extensions. In particular, almost all testing tools offer some level of Maven integration.