OSGi Test Stubs 1.0.0.M1

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

我很高興地宣佈 SpringSource OSGi Test Stubs 的 1.0.0.M1 版本釋出。這些存根提供了一種在不需要完整 OSGi 容器的情況下對複雜 OSGi 框架互動進行單元測試的方法。

問題

隨著 dm Server 團隊的開發,我們發現對我們來說最大的測試問題領域之一是BundleActivator。我們的BundleActivators做了很多將服務釋出到服務登錄檔以及使用ServiceTrackers 消費服務的工作。這些型別的任務涉及對BundleContexts、Bundles、ServiceRegistrations 和ServiceReferences 的許多交織呼叫。最初,這些啟用器足夠簡單,沒有對它們進行太多單元測試,我們依賴整合測試來捕獲引入的任何錯誤。然而,隨著時間的推移,啟用器變得越來越複雜,單元測試成為一個更緊迫的需求。我們開始使用 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的啟動呼叫中,在此處註冊服務。但真正有趣的地方在於斷言。

使用StubBundleContext,使用者可以斷言測試所需的一切。但是,測試樁包還包含一個OSGiAssert類,它使典型的斷言更具可讀性。在這種情況下,您可以看到,在呼叫start之後,我們希望註冊一個ServiceListener,一個DumpGenerator服務,以及四個DumpContributor服務。在呼叫stop之後,我們希望確保所有內容都已清理乾淨,系統處於乾淨狀態。

未來

還有更多方法可以操作樁型別,以及針對常見測試用例的更多斷言。我應該提醒,目前可用的絕不是詳盡無遺的。我一直在尋找使用者對改進這些樁和新增斷言的要求。請下載軟體包或克隆原始碼,並在 dm Server JIRA 的評論和建議中向我提供反饋。

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有