На прошлой неделе я показал вам, как тестировать пакеты OSGi. Мы использовали Pax Exam для запуска среды выполнения OSGi по нашему выбору, установки и запуска набора пакетов, а также для создания утверждений в отношении BundleContext и служб, зарегистрированных в реестре служб OSGi. На этой неделе мы собираемся повторить это упражнение, на этот раз с помощью поддержки тестирования Spring-DM.
Поддержка тестирования Spring-DM очень похожа на Pax Exam. Когда тест выполняется, он запускает среду выполнения OSGi, устанавливает набор пакетов, создает пакет «на лету», содержащий тестовый класс, а затем устанавливает тестовый пакет в среду выполнения OSGi, чтобы он мог выполнять свои утверждения из внутри.
В центре среды тестирования OSGi в Spring-DM находится AbstractConfigurableBundleCreatorTests, базовый класс JUnit 3, который будет расширяться всеми пакетными тестами. Он предоставляет базовую функциональность, необходимую для доступа к OSGi BundleContext, использования сервисов и утверждения ваших пакетов.
Я знаю, о чем ты думаешь … Юнит 3? В самом деле? Да, поддержка тестирования Spring-DM в настоящее время основана на JUnit 3. Но в Spring-DM 2.0.0 планируется перенести его на JUnit 4. Если вы заинтересованы в отслеживании прогресса, посмотрите на OSGI-410 . А пока нам придется работать с тем, что у нас есть.
Итак, давайте начнем писать пакетный тест Spring-DM. Мы начнем с основ и создадим их по ходу дела.
public class PigLatinTranslatorBundleTest extends AbstractConfigurableBundleCreatorTests { @Override protected String[] getTestBundlesNames() { return new String[] { "com.habuma.translator, interface, 1.0.0", "com.habuma.translator, pig-latin, 1.0.0" }; } // test methods go here }
Aside from extending AbstractConfigurableBundleCreatorTests, the first thing we’re going to do is override the getTestBundlesNames. AbstractConfigurableBundleCreatorTests doesn’t require us to override this method (or any method, for that matter), but if we don’t override this method, the OSGi runtime will be started without much to test. So, I’ve overridden this method to identify the bundles we want installed in our test scenario.
Much like how we started our Pax Exam test last week, here we’re specifying the bundles as Maven dependencies. But instead of using a fluent API, as with Pax Exam, we return an array of String where each member is a comma-separated list of group ID, artifact ID, and version number (and optionally type). Also unlike the Pax Exam version, we didn’t have to explicitly ask for Spring-DM bundles to be installed. That’s because Spring-DM is assumed and will be installed by default.
Now we can write the first test method. Just like we did with Pax Exam, we’ll start by testing that the OSGi runtime starts okay by asserting that we have a BundleContext:
public void testOsgiPlatformStarts() { assertNotNull(bundleContext); }
The bundleContext variable is a given for test classes that extend AbstractConfigurableBundleCreatorTests. If the OSGi runtime fails to start for any reason, bundleContext will be null. But if there aren’t any problems, we’ll have a reference to the BundleContext through which we can make some assertions.
Next up, we want to be sure that the Pig Latin bundle registers a Translator service in the OSGi service registry and that the service has its «translator.language» property set to indicate that it is a Pig Latin translator. There are a couple of ways to accomplish this using AbstractConfigurableBundleCreatorTests, but let’s first try by mimicking the style of test we wrote last time with Pax Exam:
public void testServiceReferenceExists() { ServiceReference serviceReference = bundleContext.getServiceReference(Translator.class.getName()); assertNotNull(serviceReference); assertEquals("Pig Latin", serviceReference.getProperty("translator.language")); }
This method should be familiar, as it is almost identical to the serviceReferenceShouldExist() method from the Pax Exam example (the name has been changed to accommodate JUnit 3’s conventions). Here, it goes directly to the BundleContext to get a ServiceReference for our service. From that ServiceReference, it makes the assertions necessary to satisfy the requirements.
The testServiceReferenceExists() method works using a more conventional programmatic approach to working with OSGi services. But this is a Spring-DM test—we can leverage Spring-DM to declaratively get a reference to the Translator service. To do that, we’ll need to create a Spring context configuration that references the service:
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/osgi" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd"> <reference id="translator" interface="com.habuma.translator.Translator" filter="(translator.language=Pig Latin)" /> </beans:beans>
The <reference> element from Spring-DM’s configuration namespace creates a bean in the Spring application context that is a proxy to the OSGi service. In this case, the service we’re looking for is one that implements com.habuma.translator.Translator and whose «translator.language» property is set to «Pig Latin».
Next, we’ll need to override AbstractConfigurableBundleCreatorTests’s getConfigLocations() method to tell our test about the Spring configuration file:
@Override protected String[] getConfigLocations() { return new String[] { "bundle-test-context.xml" }; }
The getConfigLocations() method identifies one or more Spring configuration files so that they can be included in the on-the-fly bundle. When a Spring-DM bundle test starts, it will load a Spring application context using the configuration files. In this case, that Spring context will include a reference to a Translator service.
Now we have a Spring context with a reference to a Translator service and our test class knows about the Spring context. The only setup work left to do is to create a property in the test class to hold the reference to the service:
public class PigLatinTranslatorBundleTest extends AbstractConfigurableBundleCreatorTests { private Translator translator; public void setTranslator(Translator translator) { this.translator = translator; } // ... }
When the Spring-DM test bundle starts and when its Spring application context is created, the translator property will automatically get set through the setTranslator method, wiring it with the service reference from bundle-test-context.xml. With the Translator service reference in hand, we’re ready to test the service:
public void testTranslatorService() { assertNotNull(translator); assertEquals("id-DAY is-thAY ork-wAY", translator.translate("Did this work")); }
The test method starts by asserting that we actually have a translator to work with. Then, we toss it some text to be sure that the translator can properly translate it into Pig Latin.
And that’s it. We now have a Spring-DM bundle test that’s roughly equivalent to what we wrote last week using Pax Exam. The finished test class, in its entirety, is as follows:
package com.habuma.translator.test; import org.osgi.framework.ServiceReference; import org.springframework.osgi.test.AbstractConfigurableBundleCreatorTests; import com.habuma.translator.Translator; public class PigLatinTranslatorBundleTest extends AbstractConfigurableBundleCreatorTests { private Translator translator; public void setTranslator(Translator translator) { this.translator = translator; } @Override protected String[] getTestBundlesNames() { return new String[] { "com.habuma.translator, interface, 1.0.0", "com.habuma.translator, pig-latin, 1.0.0" }; } @Override protected String[] getConfigLocations() { return new String[] { "bundle-test-context.xml" }; } public void testOsgiPlatformStarts() { assertNotNull(bundleContext); } public void testServiceReferenceExists() { ServiceReference serviceReference = bundleContext.getServiceReference(Translator.class.getName()); assertNotNull(serviceReference); assertEquals("Pig Latin", serviceReference.getProperty("translator.language")); } public void testTranslatorService() { assertNotNull(translator); assertEquals("id-DAY is-thAY ork-wAY", translator.translate("Did this work")); } }
Testing options
With Pax Exam, we were able to resolve test bundles using any means available to Pax Runner, including Maven, filesystem, and HTTP URLs. Unfortunately, Spring-DM’s testing support is limited to resolving bundles as Maven artifacts. (Although you can opt to override getTestBundles() instead of getTestBundlesNames() to resolve bundles yourself using whatever means you’d like.)
But, we can tweak the OSGi runtime. By default, Spring-DM testing will use Equinox, but you can switch to Felix by overriding the getPlatformName() method:
protected String getPlatformName() { return Platforms.FELIX; }
Or, if you’re more of a Knopflerfish kind of person, then:
protected String getPlatformName() { return Platforms.KNOPFLERFISH; }
Unlike Pax Exam, however, Spring-DM testing currently only supports a single version of these frameworks (specifically, Spring-DM 1.2.0 uses Equinox 3.2.2, Felix 1.4.1, or Knopflerfish 2.2.0). And, also unlike Pax Exam, you must choose a single runtime…you can’t ask Spring-DM to run your tests on more than one OSGi runtime. (Although you could write separate tests that use different runtimes.)
Should I use Pax Exam or Spring-DM testing?
I’m not going to tell you which testing framework to use—I’ll leave it up to you to pick which one suits you best. But I will share my opinions on the subject…maybe it will help you decide.
I like the way that Spring-DM’s testing automatically wires in service references for my test to consume so that I don’t have to look them up myself in my tests. But, given that Spring-DM’s testing support is currently based on JUnit 3, I’m a little turned off from it. Furthermore, given the limitations in bundle resolution and platform selection, I find Spring-DM testing to be somewhat inferior to Pax Exam.
That said, I believe both sides could learn a bit from each other. Spring-DM’s testing could obviously be bumped up to be based on JUnit 4 and to be more flexible with regard to bundle resolution and platform selection. Meanwhile, Pax Exam could learn a little from Spring-DM testing on how to wire test properties with OSGi services.
In any event, whether you use Pax Exam or Spring-DM testing, we can all agree that testing is a worthwhile practice. And regardless of which one you use, there’s no reason to not extend that good practice into OSGi development.
That’s it for this week. I’m still scheming on what I want to write about for next time—I won’t tell you everything that I’ve got in mind, but I can say that I’m thinking of showing you a way to test OSGi-based applications in a way that is difficult (or perhaps even impossible) without OSGi’s dynamic runtime. Keep an eye on this blog to get the details.