OSGi 測試存根 1.0.0.M1

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

我很高興地宣布 SpringSource 的 OSGi 測試存根 1.0.0.M1 版本發佈。這些存根提供了一種單元測試複雜 OSGi Framework 互動的方式,而無需完整的 OSGi 容器。

問題

隨著 dm Server 團隊的開發,我們發現測試方面最大的問題之一在於 BundleActivator。我們的BundleActivators確實會向服務註冊表發佈相當多的服務,以及使用ServiceTracker來使用服務。這些任務涉及許多與BundleContextBundleServiceRegistration的交織呼叫。一開始,這些啟動器非常簡單,對它們進行的單元測試不多,我們依靠整合測試來捕獲引入的任何錯誤。但是,隨著時間的推移,啟動器變得更加複雜,單元測試也變得更加迫切。我們開始使用 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 Framework 這樣複雜的 API 時。一方面,您需要讓實作足夠簡單,以至於您不太可能引入錯誤,並且您可以讓使用者指定呼叫的行為和傳回值。另一方面,您需要一個足夠複雜的實作,以便複雜的物件 (例如ServiceTracker) 在呼叫存根時可以獲得預期的行為。

考慮到所有這些,我開始為BundleContext, Bundle, 實作測試存根。為了瞭解這些測試存根會產生什麼樣的差異,以下是在將其轉換為使用存根後的先前測試。

@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。這個 context 被傳遞到DumpBundleActivator的啟動呼叫,其中註冊了服務。但真正有趣的是斷言。

使用StubBundleContext,使用者可以斷言他們測試所需的一切。但是,測試存根套件還包括一個OSGiAssert類別,該類別使典型的斷言更易於閱讀。在這種情況下,您可以看到在呼叫start之後,我們想要註冊一個ServiceListener,一個DumpGenerator服務,以及四個DumpContributor服務。在呼叫stop之後,我們想要確保一切都已清理乾淨,並且系統處於乾淨的狀態。

未來

還有許多其他方法可以操作存根類型,以及針對常見測試案例的更多斷言。我應該警告說,現在提供的絕不是詳盡無遺的。我一直在尋找使用者對如何改進這些存根以及可以新增的斷言的要求。請下載套件或複製原始程式碼,並在 dm Server JIRA 的評論和建議中向我提供回饋。

取得 Spring 電子報

透過 Spring 電子報保持聯繫

訂閱

領先一步

VMware 提供培訓和認證,以加速您的進展。

了解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供對 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔案。

了解更多

即將舉辦的活動

查看 Spring 社群中所有即將舉辦的活動。

檢視全部