OSGi 测试桩 1.0.0.M1

工程 | Ben Hale | 2009年6月23日 | ...

我很高兴地宣布 SpringSource 的 OSGi 测试桩的 1.0.0.M1 版本发布。这些桩提供了一种方法来单元测试复杂的 OSGi 框架交互,而无需完整的 OSGi 容器。

问题

随着 dm Server 团队的开发,我们发现测试中最大的问题之一在于BundleActivator。我们的BundleActivators在服务注册表中发布了许多服务,并使用ServiceTracker消费服务。这些类型的任务涉及对BundleContextBundleServiceRegistrationServiceReference的许多交织调用。最初,这些激活器非常简单,因此没有进行太多单元测试,我们依赖于集成测试来捕获引入的任何错误。然而,随着时间的推移,激活器变得越来越复杂,单元测试变得越来越迫切需要。我们开始在这些测试中使用 EasyMock,但发现它们非常复杂、难以维护,最重要的是难以理解。
@Test
public void startAndStop() throws Exception {
    BundleActivator bundleActivator = new DumpBundleActivator();
    BundleContext context = createMock(BundleContext.class);
    Filter filter = createMock(Filter.class);
    
    String filterString = "(objectClass=" + DumpContributor.class.getName() + ")";
    
    expect(context.createFilter(filterString)).andReturn(filter);
    context.addServiceListener((ServiceListener)anyObject(), eq(filterString));
    expect(context.getServiceReferences(DumpContributor.class.getName(), null)).andReturn(new ServiceReference[0]).atLeastOnce();
    
    ServiceRegistration generatorRegistration = createMock(ServiceRegistration.class);
    ServiceRegistration summaryRegistration = createMock(ServiceRegistration.class);
    ServiceRegistration jmxRegistration = createMock(ServiceRegistration.class);
    ServiceRegistration threadRegistration = createMock(ServiceRegistration.class);
    ServiceRegistration heapRegistration = createMock(ServiceRegistration.class);
    
    expect(context.registerService(eq(DumpGenerator.class.getName()), isA(StandardDumpGenerator.class), (Dictionary<?,?>)isNull())).andReturn(generatorRegistration);
    expect(context.registerService(eq(DumpContributor.class.getName()), isA(SummaryDumpContributor.class), (Dictionary<?,?>)isNull())).andReturn(summaryRegistration);
    expect(context.registerService(eq(DumpContributor.class.getName()), isA(JmxDumpContributor.class), (Dictionary<?,?>)isNull())).andReturn(jmxRegistration);
    expect(context.registerService(eq(DumpContributor.class.getName()), isA(ThreadDumpContributor.class), (Dictionary<?,?>)isNull())).andReturn(threadRegistration);
    expect(context.registerService(eq(DumpContributor.class.getName()), isA(HeapDumpContributor.class), (Dictionary<?,?>)isNull())).andReturn(heapRegistration);
    
    generatorRegistration.unregister();
    summaryRegistration.unregister();
    jmxRegistration.unregister();
    threadRegistration.unregister();
    heapRegistration.unregister();
    
    context.removeServiceListener((ServiceListener)anyObject());
    
    replay(context, filter, generatorRegistration, summaryRegistration, jmxRegistration, threadRegistration, heapRegistration);
    
    bundleActivator.start(context);
    bundleActivator.stop(context);
    
    verify(context, filter, generatorRegistration, summaryRegistration, jmxRegistration, threadRegistration, heapRegistration);
}

解决方案

很快变得很清楚,从长远来看,维护看起来像这样的代码是行不通的。众所周知,Spring 长期以来拥有非常有用的 测试桩 集,很明显我们需要为 OSGi 提供类似的东西。

创建一组测试桩是一项微妙的平衡行为,尤其是在涉及到像 OSGi 框架这样复杂的 API 时。一方面,您需要实现足够简单,以至于您不太可能引入错误,并且您可以允许用户指定调用的行为和返回值。另一方面,您需要一个足够复杂的实现,以便复杂的 对象(例如ServiceTracker)在调用桩时可以获得预期的行为。

考虑到所有这些,我开始为BundleContext, Bundle, ServiceReferenceServiceRegistration实现测试桩。为了了解这些测试桩带来的差异,以下是将测试转换为使用桩后的结果。

@Test
public void startAndStop() throws Exception {
    BundleActivator bundleActivator = new DumpBundleActivator();
    StubBundleContext bundleContext = new StubBundleContext().addFilter(new ObjectClassFilter(DumpContributor.class));

    bundleActivator.start(bundleContext);
    assertServiceListenerCount(bundleContext, 1);
    assertServiceRegistrationCount(bundleContext, DumpGenerator.class, 1);
    assertServiceRegistrationCount(bundleContext, DumpContributor.class, 4);

    bundleActivator.stop(bundleContext);
    assertCleanState(bundleContext);
}

如您所见,此测试现在更易于阅读和维护,但最重要的是它更易于理解。此测试的基本构建块是StubBundleContext。此上下文传递到DumpBundleActivator的 start 调用中,其中注册了服务。但真正有趣的是断言。

使用StubBundleContext,用户可以断言他们需要测试的所有内容。但是,测试桩包还包括一个OSGiAssert类,该类使典型的断言更易于阅读。在这种情况下,您可以看到,在调用start后,我们希望注册一个ServiceListener,注册一个DumpGenerator服务,并注册四个DumpContributor服务。在调用stop后,我们希望确保所有内容都已清理,并且系统处于干净状态。

未来

还有许多其他方法可以操纵桩类型,以及一些针对常见测试用例的更多断言。我应该警告,现在提供的内容绝非详尽无遗。我一直在寻找用户对如何改进这些桩以及可以添加哪些断言的要求。请下载软件包或克隆源代码,并在 dm Server JIRA 中的评论和建议中提供反馈。

获取 Spring 新闻通讯

与 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部