Gemini Blueprint参考文档 第12章 测试OSGi应用

12章 测试OSGi应用

在遵循最佳实践原则和使用Gemini Blueprint支持的情况下,你的bean类应该很容易进行单元测试,因为它们不强依赖于OSGi,你使用的少量OSGi API(例如BundleContext)是基于接口的,很容易模拟。不管你是否想要做单元测试或集成测试,Spring DM都可以减轻你的工作。

12.1. OSGi模拟

模拟与打桩:单元测试代码的策略有很多,最流行的策略就是打桩和模拟。参考Martin Fowler的文章this他详细介绍了两者之间的区别。

虽然大多数的OSGi API都是接口,用专门的库(如EasyMock)创建模拟对象是相当简单的,但是实际上由于大量的设置代码,使得代码(尤其是JDK1.4)看起来很笨重。为了保持单元测试简短,Gemini Blueprint/Spring DM提供了OSGi模拟包org.eclipse.gemini.blueprint.mock

用不用它们你说了算,然而,我们在Blueprint/Spring DM 测试套件中广泛的使用了它们。在我们的代码基线中你可以找到下面的代码片段:

private ServiceReference reference;

private BundleContext bundleContext;

private Object service;

protectedvoid setUp() throws Exception {

    reference = new MockServiceReference();

    bundleContext = new MockBundleContext() {

        public ServiceReference getServiceReference(String clazz) {

            return reference;

        }

        public ServiceReference[] getServiceReferences(String clazz, String filter)

                throws InvalidSyntaxException {

            returnnew ServiceReference[] { reference };

        }

        public Object getService(ServiceReference ref) {

            if (reference == ref)

               return service;

            super.getService(ref);

        }

    };

    ...

}

publicvoid testComponent() throws Exception {

    OsgiComponent comp = new OsgiComponent(bundleContext);

    assertSame(reference, comp.getReference());

assertSame(object, comp.getTarget());

}

作为结束语,要尝试使用它们,并选择你最感觉最舒适的风格和库。在我们的测试套件中,我们将使用上述的模拟、EasyMock库和大量的集成测试。

12.2. 集成测试

JUnit4/TestNG怎么样?

虽然JUnit4/TestNG解决了构建基本JUnit类时的类继承问题,通过注解将执行器与测试解耦,但是Gemini Blueprint/Spring DM不能使用它们,因为它必须支持Java 1.4。然而,将来可能提供一个可选的基于JVM 5的测试扩展,用上述的库集成已有的测试。

在受限环境中,例如OSGi,测试类、bundlemanifest的可见性和版本管理是很重要的。

为了减轻集成测试的负担,Gemini Blueprint工程提供了测试类等级(基于org.eclipse.gemini.blueprint.test.AbstractOsgiTests),它提供给了编写正规JUnit测试的支持,并且在OSGi环境中自动执行它们。

通常,Gemini Blueprint/Spring DM测试框架支持的场景有:

  • 启动OSGi框架(Equinox, Knopflerfish, Felix)
  • 安装和启动任何测试需要的bundle
  • 将测试用例打包为一个在线的bundle,并生成manifest(如果没有提供,并将这个bundle安装到OSGi框架
  • OSGi框架中执行测试用例
  • 关闭OSGi框架
  • 将测试结果返回给原始测试用例

[Warning]

警告

该测试框架的目标是在非OSGi环境(Ant/Maven/IDE)运行OSGi集成测试。该测试框架不是作为OSGi bundle(它也不是那样运行的)。实际上,这里的意思是测试bundle应该与它测试的bundle分开(就类似于单元测试,要与它测试的类是分离一样)

按照这个步骤,编写就JunitOSGi集成测试并集成到任何环境(IDEantmaven等等)是很繁琐的。本章剩余部分详细介绍Gemini Blueprint/Spring DM测试套件的特定。

12.2.1. 创建一个简单的OSGi集成测试

虽然测试框架包含提供特定特性的类,但是大多数情况下你的测试用例只需继承org.eclipse.gemini.blueprint.test.AbstractConfigurableBundleCreatorTests(至少,这是实际中使用的)

让我们继承这个类,并通过bundleContext字段与OSGi平台交互:

publicclassSimpleOsgiTestextendsAbstractConfigurableBundleCreatorTests {

publicvoid testOsgiPlatformStarts() throws Exception {

    System.out.println(bundleContext.getProperty(Constants.FRAMEWORK_VENDOR));

    System.out.println(bundleContext.getProperty(Constants.FRAMEWORK_VERSION));

    System.out.println(bundleContext.getProperty(Constants.FRAMEWORK_EXECUTIONENVIRONMENT));

}

}

简单的执行这个测试,就像你平常执行Junit测试一样。在Equinox 3.2.x上,输出类似于:

Eclipse

1.3.0

OSGi/Minimum-1.0,OSGi/Minimum-1.1,JRE-1.1,J2SE-1.2,J2SE-1.3,J2SE-1.4}

在测试执行期间,你可能会看到测试框架输出不同的日志语句。但是这些都是可以禁止的,因为它们只是告知信息,不影响实际执行。

注意,你不需要出具任何bundle,编写任何MANIFEST,或导入或导出,更不用启动和关闭OSGi平台。当测试执行时,测试框架会自动完成这些事。

让我们做一些查询,看看测试是在什么环境中运行的。一个简单的方式是向bundleContext查询安装的bundle

publicvoid testOsgiEnvironment() throws Exception {

    Bundle[] bundles = bundleContext.getBundles();

    for (int i = 0; i < bundles.length; i++) {

        System.out.print(OsgiStringUtils.nullSafeName(bundles[i]));

        System.out.print(", ");

    }

System.out.println();

}

输出类似于这样:

OSGi System Bundle, ObjectWeb ASM, log4j.osgi, spring-test, spring-osgi-test, spring-osgi-core,

    spring-aop, spring-osgi-io, slf4j-api,

spring-osgi-extender, etc... TestBundle-testOsgiPlatformStarts-com.your.package.SimpleOsgiTest,

正如你所看到的,测试框架安装了运行测试需要的强制依赖,例如SpringGemini Blueprint/Spring DMslf4j

12.2.2. 安装测试前提条件

OSGi友好的库

要在OSGi环境中工作,jar文件需要在MANIFEST.MF中声明导入或导出的包,即声明需要从其它bundle获取哪些类,为其它的bundle提供那些类。大多数的库都不是OSGi的,不提供合适的manifest条目,这意味着它们在OSGi环境中是不可用的。现在,开源空间有几个方案提供了合适的manifest-请参见FAQ了解更多信息。

除了Gemini Blueprint/Spring DMjar,测试自己可能依赖几个库或者自己的代码。

考虑下面的测试,它依赖于Apache Commons Lang:

import org.apache.commons.lang.time.DateFormatUtils;

...

publicvoid testCommonsLangDateFormat() throws Exception {

    System.out.println(DateFormatUtils.format(new Date(), "HH:mm:ssZZ"));

}}

运行这个测试,会产生异常:

java.lang.IllegalStateException: Unable to dynamically start generated unit test bundle

     ...

Caused by: org.osgi.framework.BundleException: The bundle could not be resolved.

Reason: Missing Constraint: Import-Package: org.apache.commons.lang.time; version="0.0.0"

    ...

... 15 more

这个测试需要org.apache.commons.lang.time 包,但是没有bundle导出这个包。解决这个问题,安装commons-langbundle(确保你用的版本为2.4以上,因为它在manifest中添加了合适的OSGi条目)

可以用getTestBundlesNames getTestBundles指定要安装那些bundle。前一个方法返回一个字符串数组,表示bundle的名字、包和版本,而后一个方法返回一个资源数组,可以安装bundle可以直接使用。即用getTestBundlesNames方法,你需要依赖别人帮你定位bundle(大多数场景),而getTestBundles方法,当你想要自己定位bundle的位置用。

默认情况下,测试套件会执行构建查找,类似于Maven2,首先搜索当前工程的相对位置,然后才去本地仓库。定位程序希望bundle字符串是逗号分隔的值,包含groupnameversiontype(可选)。将来可能会有其他不同的定位程序。通过org.eclipse.gemini.blueprint.test.provisioning.ArtifactLocator接口也可以实现自己的定位程序。

让我们修改一下集成测试,安装必须的bundle(和一些额外的OSGi库):

protected String[] getTestBundlesNames() {

     returnnew String[] {

        "javax.transaction, com.springsource.javax.transaction, 1.1.0",

        "commons-lang, commons-lang, 2.4" };

     };

}

重新运行这个测试,应该显示出这些bundle都已经安装到了OSGi平台。

[Note]

注意

上面提到的构件必须存在于maven的本地仓库


12.2.3. 测试框架的高级主题

测试框架允许进行定制。本章详细介绍已有的钩子。然而,它们是高级主题,它增加了测试基础的复杂性。

12.2.3.1. 自定义测试的manifest

有些场景,自动生成的manifest不满足测试需要。例如manifest需要不同的头或者需要导入特定的package

对于简单场景,可以直接生成manifest-下面的例子中,指定了bundle的类路径:

protected Manifest getManifest() {

    // let the testing framework create/load the manifest

    Manifest mf = super.getManifest();

    // add Bundle-Classpath:

    mf.getMainAttributes().putValue(Constants.BUNDLE_CLASSPATH, ".,bundleclasspath/simple.jar");

return mf;

}

另一种选择是覆写getManifestLocations()方法,提供自己的manifest文件:

protected String getManifestLocation() {

      return"classpath:com/xyz/abc/test/MyTestTest.MF";

}

然而,每一个manifest需要下面的条目:

“Bundle-Activator: org.eclipse.gemini.blueprint.test.JUnitTestActivator”

因为如果没有没有这个条目,测试基础的功能就不正常。另外,还需要导入基础测试套件要用的JUnitSpringGemini Blueprint/Spring DM特定的package

Import-Package: junit.framework,

  org.osgi.framework,

  org.apache.commons.logging,

  org.springframework.util,

  org.eclipse.gemini.blueprint.service,

  org.eclipse.gemini.blueprint.util,

  org.eclipse.gemini.blueprint.test,

  org.springframework.context

如果导入这些类失败,测试时就会抛出NoDefClassFoundError错误。

12.2.3.2. 自定义测试bundle的内容

默认情况下,对于运行中的bundle,测试基础用./target/test-classes文件夹下所有的类、xml和属性文件。这符合maven的文件布局。这些设置可以用两种方式配置:

  1. 以编程方式覆写AbstractConfigurableBundleCreatorTestsgetXXX方法。
  2. 创建一个属性文件,名字与测试用例类似。例如com.xyz.MyTest的属性文件命名为com/xyz/MyTest-bundle.properties。如果找到了这个文件,那么就会从文件中读取下面的属性:

12.1. 默认的测试jar内容设置

属性名

默认值

描述

root.dir

file:./target/test-classes

Jar的根目录

include.patterns

/**/*.class,/**/*.xml,/**/*.properties

逗号分隔的Ant风格的模式字符串

manifest

(empty)

Manifest位置,是一个字符串。默认情况下,它是空的,意思是由测试框架创建,而不是由用户提供

 

这个选项是很方便的,当你创建特定的测试时,需要包含特定的资源(例如本地化文件或者图片)。请查阅AbstractConfigurableBundleCreatorTestsAbstractOnTheFlyBundleCreatorTests测试类了解更多的钩子。

12.2.3.3. 理解MANIFEST.MF生成

测试框架的非常有用的特性就是基于bundle内容自动生成测试manifestManifest创建者组件使用字节码分析器来确定测试类导入的package,这样它就可以生成合适的OSGi指令。由于生成的bundle是用于测试,因此创建者会用下面的假设:

  • 没有package被导出

运行中的bundle用于运行单元测试,通常会消费OSGipackage。这个行为可以通过自定义manifest修改。

  • 不支持分离package (即来自不同bundle相同package的类)

这意思是说测试框架中的package是完整的,不会为它们生成Import-Package条目。为了避免这个问题,使用子package或将类移动到同一个bundle。注意不鼓励使用分离package,因为它们会出很多问题。

  • 测试bundle只包含测试类

字节码解析器只看测试类的层次,任何其它的类都不会包含到bundle中,不会生成导入语句。若要修改默认行为,覆写createManifestOnlyFromTestClass,返回false:

protected boolean createManifestOnlyFromTestClass() {

return false;

}

[Note]

注意

生成manifest花费的时间会随着bundle类的数量和大小而增长

此外,自定义manifest或者以内部类的方式为你测试类附加代码(这样它可以自动解析)。“lack of such features”的原因是字节码解析器简单快速出击测试的manifest-这不是创建OSGi构建的通用工具。

12.2.4. 创建OSGi应用上下文

Gemini Blueprint/Spring DM测试套件是在Spring测试类上构建的。要创建一个应用上下文(特定OSGi的),应该覆写getConfigLocations[]方法,指明应用上下文配置的位置。在运行时,创建的OSGi应用上下文在测试用例的生命周期内会被缓存起来。

protected String[] getConfigLocations() {

   return new String[] { "/com/xyz/abc/test/MyTestContext.xml" };

}

12.2.5. 指定要使用的OSGi平台

测试框架支持即拆即用,三个OSGi 4.0的实现,即EquinoxKnopflerfishFelix。要使用它们,必须把它们放到类路径中。默认情况下,测试框架尝试用Equinox平台。这可通过三种方式配置:

  1. 通过getPlatformName()方法以编程方式配置

覆写上述方法,指定一个平台接口实现的全限定名。用户可以使用Platforms类指定一个平台:

protected String getPlatformName() {

   return Platforms.FELIX;

}

  1. 通过系统属性org.eclipse.gemini.blueprint.test.framework声明

如果这个属性设置了,那么测试框架就使用它的值作为平台实现的全限定名。如果失败了,在记录一条警告消息的日志之后,则会回到Equinox。这对于构建工具是很有用的(例如maven),因为它指定一个了特定的目标环境,而无需修改测试代码。

12.2.6. 等待测试依赖

测试框架有一个内置的特性,就是在执行测试之前,能够等待所有的依赖都被部署。由于OSGi平台天生就是并发的,安装一个bundle并不意味着他的服务都是运行的。在依赖的服务初始化完成之前,运行测试会引发不定时发生的错误,而污染了测试结果。默认情况下,测试框架会检查用户安装的bundle,如果是基于Springbundle,等待,直到它们完全启动(即它们的应用上下文作为OSGi服务发布)。这个行为可以通过覆写shouldWaitForSpringBundlesContextCreation方法来禁止。查阅AbstractSynchronizedOsgiTests了解更多细节。

12.2.7. 测试框架的性能

考虑测试框架提供的所有功能,你可能会疑惑是否有性能瓶颈。首先,值得注意的是,测试基础自动完成的工作无论如何都是完成的(例如创建manifest或者创建测试bundle,或者按照bundle)。手动完成这些工作很容易导致错误和时间消耗。实际上,当前的测试基础启动很高效的自动测试,无需担心部署问题和冗余。

至于数字,当前的测试基础设施已经在内部使用了一年半的时间-我们的集成测试(大约120)在笔记本上运行大约3:30分钟。大多数的时间都消耗在启动和停止OSGi平台上:测试框架花费大约10%的时间(目前在我们的性能分析中是这样)。例如,manifest生成花费的时间不到0.5s,而jar生成大约为1s

然而,我们正在努力让它更快,更智能,这样需要更少的配置,上下文信息尽量被使用。如果你有任何想法或建议,请使用我们定位entire跟踪和论坛。

希望这篇文章能够说明Gemini Blueprint/Spring DM测试基础设施如何简化OSGI集成测试,如何定制化。请查阅javadoc了解更多信息。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值