Spring Framework 3.2 RC1: 新的測試功能

工程 | Sam Brannen | 2012年11月07日 | ...

正如Juergen Hoeller在其宣佈Spring Framework 3.2 RC1釋出的博文中提到的那樣,Spring團隊在測試支援方面引入了一些令人興奮的新功能。最重要的是,我們為測試Web應用程式添加了一流的支援。[1]

      請注意:這是一篇來自我的Swiftmind公司部落格的交叉釋出

在這篇文章中,我們將首先介紹Spring Framework中一些通用的新測試功能,然後詳細討論對使用WebApplicationContext以及請求(request)會話(session)作用域bean進行測試的支援。最後,我們將介紹對ApplicationContextInitializers的支援,並簡要討論使用應用程式上下文層次結構進行測試的路線圖。

Rossen Stoyanchev稍後將發表一篇詳細介紹新的Spring MVC Test框架的博文,該框架為測試Spring MVC應用程式提供了一流的支援。因此,請務必關注,因為它基於本文後面討論的基本Web測試支援。



通用新功能和更新


構建和依賴

spring-test模組現在基於並支援JUnit 4.10和TestNG 6.5.2構建,並且spring-test現在依賴於junit:junit-dep Maven工件而不是junit:junit,這意味著您可以完全控制對Hamcrest庫(例如,hamcrest-corehamcrest-all等)的依賴。

泛型工廠方法

泛型工廠方法是使用Java泛型實現工廠方法設計模式的方法。以下是一些泛型工廠方法的示例簽名


public static <T> T mock(Class<T> clazz) { ... }

public static <T> T proxy(T obj) { ... }

在Spring配置中使用泛型工廠方法並非測試專用,但EasyMock.createMock(MyService.class)Mockito.mock(MyService.class)等泛型工廠方法通常用於在測試應用程式上下文中為Spring bean建立動態模擬。例如,在Spring Framework 3.2之前,以下配置可能無法將OrderRepository自動裝配到OrderService中。原因在於,根據應用程式上下文中bean的初始化順序,Spring可能會將orderRepository bean的型別推斷為java.lang.Object而不是com.example.repository.OrderRepository


<beans>

  <!-- OrderService is autowired with OrderRepository -->
  <context:component-scan base-package="com.example.service"/>

  <bean id="orderRepository" class="org.easymock.EasyMock"
      factory-method="createMock"
      c:_="com.example.repository.OrderRepository" />

</beans>

在Spring 3.2中,現在可以正確推斷工廠方法的泛型返回型別,並且模擬的按型別自動裝配應該按預期工作。因此,自定義的變通方法,例如MockitoFactoryBeanEasyMockFactoryBeanSpringockito,可能不再需要。

模擬物件

我們引入了MockEnvironment,它補充了現有的MockPropertySource,以完成對Spring 3.1中引入的環境和屬性源抽象的模擬支援。

關於Web元件的單元測試支援,我們為現有的Servlet API模擬(例如MockServletContextMockHttpSessionMockFilterChainMockRequestDispatcher)添加了新功能,並且我們引入了與REST Web服務相關的新模擬:客戶端的MockClientHttpRequestMockClientHttpResponse以及伺服器端的MockHttpInputMessageMockHttpOutputMessage

JDBC測試支援

在Spring 3.2中,我們已棄用SimpleJdbcTestUtils,取而代之的是改進的JdbcTestUtils類,該類除了SimpleJdbcTestUtils之前提供的所有功能外,還提供了新的countRowsInTableWhere()dropTables()實用方法。這些更改有助於避免與使用已棄用的SimpleJdbcTemplate相關的編譯器警告,並提供了一種方便的方法,可以使用WHERE子句計算表中行數以及刪除表列表。類似地,AbstractTransactionalJUnit4SpringContextTestsAbstractTransactionalTestNGSpringContextTests也已改造,添加了jdbcTemplate例項變數以及委託給JdbcTestUtils中對應方法的countRowsInTableWhere()dropTables()方法。

事務管理器配置

如果您熟悉Spring TestContext Framework中對事務性整合測試的支援,那麼您可能知道用於測試的事務管理器必須按照約定命名為"transactionManager"。自Spring 2.5以來,這可以透過@TransactionConfiguration註解(例如,@TransactionConfiguration(transactionManager="txMgr"))進行覆蓋;但是,如果應用程式上下文中只存在一個PlatformTransactionManger,則不再需要使用此註解。換句話說,只要上下文中只定義了一個事務管理器,就不再需要限定該事務管理器的名稱:如果只有一個,TestContext框架將直接使用它。

Spring 3.1引入了TransactionManagementConfigurer介面,用於在使用@Configuration類與@EnableTransactionManagement結合使用時(即,與使用<tx:annotation-driven />的XML配置相反),以程式設計方式指定用於@Transactional方法的事務管理器。因此,自Spring 3.2起,如果您的一個元件(即,通常是一個@Configuration類)實現了TransactionManagementConfigurer,TestContext框架將使用該元件指定的事務管理器。



Spring TestContext 框架


本文的其餘部分明確討論Spring TestContext框架中的新功能。如果您已經熟悉TestContext框架,請隨意跳到下一節。否則,您可能需要首先熟悉以下段落中透過連結提供的資訊。

在Spring 2.5中,我們引入了Spring TestContext Framework,它提供了註釋驅動的整合測試支援,可與JUnit或TestNG一起使用。本文中的示例將重點關注基於JUnit的測試,但此處使用的所有功能也適用於TestNG。

在Spring 3.1中,我們修訂了Spring TestContext Framework,增加了對使用@Configuration類和環境配置檔案進行測試的支援。



載入 WebApplicationContext


  • 問題:如何告知TestContext框架載入WebApplicationContext
  • 答案:只需使用@WebAppConfiguration註解您的測試類。

真的就是這麼簡單。在您的測試類上存在@WebAppConfiguration會指示TestContext框架(TCF)應該為您的整合測試載入一個WebApplicationContext(WAC)。在後臺,TCF會確保建立一個MockServletContext並提供給您的測試WAC。預設情況下,您的MockServletContext的基本資源路徑將設定為“src/main/webapp”。這被解釋為相對於您的JVM根目錄的路徑(即,通常是您的專案路徑)。如果您熟悉Maven專案中Web應用程式的目錄結構,您會知道“src/main/webapp”是您的WAR根目錄的預設位置。如果您需要覆蓋此預設值,只需向@WebAppConfiguration註解提供一個備用路徑(例如,@WebAppConfiguration("src/test/webapp"))。如果您希望從類路徑而不是檔案系統引用基本資源路徑,只需使用Spring的classpath:字首。

請注意,Spring對WebApplicationContexts的測試支援與對標準ApplicationContexts的支援保持一致。在使用WebApplicationContext進行測試時,您可以自由地透過@ContextConfiguration宣告XML配置檔案或@Configuration類。您當然也可以自由使用任何其他測試註解,例如@TestExecutionListeners@TransactionConfiguration@ActiveProfiles等。

讓我們看一些例子...

約定


@RunWith(SpringJUnit4ClassRunner.class)

// defaults to "file:src/main/webapp"
@WebAppConfiguration

// detects "WacTests-context.xml" in same package
// or static nested @Configuration class
@ContextConfiguration

public class WacTests {
	//...
}

上面的例子展示了TestContext框架對約定優於配置的支援。如果您用@WebAppConfiguration註解一個測試類但沒有指定資源基路徑,資源路徑將有效地預設為“file:src/main/webapp”。類似地,如果您宣告@ContextConfiguration但沒有指定資源locations、註解classes或上下文initializers,Spring將嘗試使用約定(即,與WacTests類在同一包中的“WacTests-context.xml”或靜態巢狀的@Configuration類)檢測您的配置是否存在。

預設資源語義


@RunWith(SpringJUnit4ClassRunner.class)

// file system resource
@WebAppConfiguration("webapp")

// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")

public class WacTests {
	//...
}

此示例演示瞭如何使用@WebAppConfiguration顯式宣告資源基路徑,以及使用@ContextConfiguration顯式宣告XML資源位置。這裡需要注意的重要一點是這兩個註解的路徑語義不同。預設情況下,@WebAppConfiguration資源路徑是基於檔案系統的;而@ContextConfiguration資源位置是基於類路徑的。

顯式資源語義


@RunWith(SpringJUnit4ClassRunner.class)

// classpath resource
@WebAppConfiguration("classpath:test-web-resources")

// file system resource
@ContextConfiguration(
    "file:src/main/webapp/WEB-INF/servlet-config.xml")

public class WacTests {
	//...
}

在第三個示例中,我們看到可以透過指定Spring資源字首來覆蓋兩個註解的預設資源語義。請將此示例中的註釋與上一個示例進行對比。



使用Web模擬


為了提供全面的Web測試支援,Spring 3.2引入了一個新的ServletTestExecutionListener,預設情況下啟用。在對WebApplicationContext進行測試時,此TestExecutionListener會在每個測試方法之前透過Spring Web的RequestContextHolder設定預設的執行緒區域性狀態,並根據透過@WebAppConfiguration配置的基本資源路徑建立MockHttpServletRequestMockHttpServletResponseServletWebRequestServletTestExecutionListener還確保MockHttpServletResponseServletWebRequest可以注入到測試例項中,並且在測試完成後它會清理執行緒區域性狀態。

一旦為您的測試載入了WebApplicationContext,您可能會發現需要與Web模擬進行互動——例如,設定您的測試夾具或在呼叫Web元件後執行斷言。以下示例演示了哪些模擬可以自動裝配到您的測試例項中。請注意,WebApplicationContextMockServletContext都在整個測試套件中快取;而其他模擬由ServletTestExecutionListener按測試方法進行管理。

注入模擬


@WebAppConfiguration
@ContextConfiguration
public class WacTests {
	
	@Autowired WebApplicationContext wac; // cached
	
	@Autowired MockServletContext servletContext; // cached
	
	@Autowired MockHttpSession session;
	
	@Autowired MockHttpServletRequest request;
	
	@Autowired MockHttpServletResponse response;
	
	@Autowired ServletWebRequest webRequest;
	
	//...
}


請求和會話作用域的Bean


請求和會話作用域的bean在Spring中已經支援了好幾年,但測試它們一直有些複雜。從Spring 3.2開始,透過遵循以下步驟測試您的請求作用域和會話作用域的bean變得輕而易舉

  1. 透過使用@WebAppConfiguration註解您的測試類,確保為您的測試載入了一個WebApplicationContext
  2. 將模擬請求或會話注入到您的測試例項中,並根據需要準備您的測試夾具。
  3. 呼叫您從配置的WebApplicationContext(即,透過依賴注入)檢索到的Web元件。
  4. 對模擬執行斷言。

以下程式碼片段顯示了登入用例的XML配置。請注意,userService bean依賴於一個請求作用域的loginAction bean。此外,LoginAction使用SpEL表示式例項化,這些表示式從當前的HTTP請求中檢索使用者名稱和密碼。在我們的測試中,我們將希望透過TestContext框架管理的模擬來配置這些請求引數。

請求作用域Bean配置


<beans>

  <bean id="userService"
      class="com.example.SimpleUserService"
      c:loginAction-ref="loginAction" />

  <bean id="loginAction" class="com.example.LoginAction"
      c:username="#{request.getParameter('user')}"
      c:password="#{request.getParameter('pswd')}"
      scope="request">
    <aop:scoped-proxy />
  </bean>
	
</beans>

RequestScopedBeanTests中,我們將UserService(即被測物件)和MockHttpServletRequest都注入到我們的測試例項中。在requestScope()測試方法中,我們透過在提供的MockHttpServletRequest中設定請求引數來設定測試夾具。當在userService上呼叫loginUser()方法時,我們可以確信使用者服務可以訪問當前MockHttpServletRequest的請求作用域loginAction(即我們剛剛設定引數的那個)。然後,我們可以根據使用者名稱和密碼的已知輸入對結果執行斷言。

請求作用域Bean測試


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class RequestScopedBeanTests {
	
	@Autowired UserService userService;
	@Autowired MockHttpServletRequest request;
	
	@Test
	public void requestScope() {
		
		request.setParameter("user", "enigma");
		request.setParameter("pswd", "$pr!ng");
		
		LoginResults results = userService.loginUser();
		
		// assert results
	}
}

以下程式碼片段與我們上面看到的請求作用域bean的程式碼片段類似;然而,這次userService bean依賴於一個會話作用域的userPreferences bean。請注意,UserPreferences bean是使用SpEL表示式例項化的,該表示式從當前HTTP會話中檢索主題(theme)。在我們的測試中,我們將需要在TestContext框架管理的模擬會話中配置一個主題。

會話作用域Bean配置


<beans>

  <bean id="userService"
      class="com.example.SimpleUserService"
      c:userPreferences-ref="userPreferences" />

  <bean id="userPreferences"
      class="com.example.UserPreferences"
      c:theme="#{session.getAttribute('theme')}"
      scope="session">
    <aop:scoped-proxy />
  </bean>

</beans>

SessionScopedBeanTests中,我們將UserServiceMockHttpSession注入到我們的測試例項中。在sessionScope()測試方法中,我們透過在提供的MockHttpSession中設定預期的“主題”屬性來設定測試夾具。當在userService上呼叫processUserPreferences()方法時,我們可以確信使用者服務可以訪問當前MockHttpSession的會話作用域userPreferences,並且我們可以根據配置的主題對結果執行斷言。

會話作用域Bean測試


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class SessionScopedBeanTests {

  @Autowired UserService userService;
  @Autowired MockHttpSession session;

  @Test
  public void sessionScope() throws Exception {

    session.setAttribute("theme", "blue");

    Results results = userService.processUserPreferences();

    // assert results
  }
}


應用上下文初始化器


Spring 3.1 引入了ApplicationContextInitializer介面,允許以程式設計方式初始化ConfigurableApplicationContext——例如,註冊屬性源或根據Spring Environment抽象啟用bean定義配置檔案。初始化器可以在web.xml中配置,透過ContextLoaderListenercontext-paramDispatcherServletinit-param來指定contextInitializerClasses

要在整合測試中使用上下文初始化器,只需透過Spring 3.2中引入的新的initializers屬性在@ContextConfiguration中宣告初始化器類。透過inheritInitializers屬性可以控制跨測試類層次結構的初始化器繼承,該屬性預設為true。由於ApplicationContextInitializer提供了一種完全程式設計化的方法來初始化應用程式上下文,因此初始化器可以選擇性地配置整個上下文。換句話說,如果聲明瞭初始化器,則透過@ContextConfiguration配置的整合測試不再絕對需要XML資源位置或註解類。最後但同樣重要的是,上下文初始化器根據Spring的Ordered介面或@Order註解進行排序

以下程式碼示例演示了在整合測試中使用上下文初始化器的各種方法。第一個示例展示瞭如何將單個初始化器與XML資源位置結合配置。下一個示例聲明瞭多個上下文初始化器。第三個列表演示了在類層次結構中使用初始化器,其中ExtendedTest中宣告的上下文初始化器列表將與BaseTest中宣告的初始化器合併。請記住,初始化器的呼叫順序受Spring的Ordered介面實現或@Order註解的存在影響。第四個示例與第三個示例相同,只是@ContextConfiguration中的inheritInitializers屬性已設定為false。結果是,父類中宣告的任何上下文初始化器都將被忽略(即被覆蓋)。最後一個列表演示了ApplicationContext可以僅從上下文初始化器載入,而無需宣告XML資源位置或註解類。

單個初始化器


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
    locations = "/app-config.xml",
    initializers = CustomInitializer.class)
public class ApplicationContextInitializerTests {}

多個初始化器


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  locations = "/app-config.xml",
  initializers = {
    PropertySourceInitializer.class,
    ProfileInitializer.class
  })
public class ApplicationContextInitializerTests {}

合併初始化器


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
    classes = BaseConfig.class,
    initializers = BaseInitializer.class)
public class BaseTest {}


@ContextConfiguration(
    classes = ExtendedConfig.class,
    initializers = ExtendedInitializer.class)
public class ExtendedTest extends BaseTest {}

被覆蓋的初始化器


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
    classes = BaseConfig.class,
    initializers = BaseInitializer.class)
public class BaseTest {}


@ContextConfiguration(
    classes = ExtendedConfig.class,
    initializers = ExtendedInitializer.class,
    inheritInitializers = false)
public class ExtendedTest extends BaseTest {}

不帶資源的初始化器


// does not declare 'locations' or 'classes'
@ContextConfiguration(
    initializers = EntireAppInitializer.class)
public class InitializerWithoutConfigFilesOrClassesTest {}


上下文快取


一旦TestContext框架為測試載入了ApplicationContext,該上下文將被快取並重新用於同一測試套件中宣告相同唯一上下文配置的所有後續測試。這裡要記住的重要一點是,ApplicationContext由其上下文快取鍵(即,用於載入它的配置引數組合)唯一標識。

從Spring 3.2開始,ApplicationContextInitializer類也包含在上下文快取鍵中。此外,如果上下文是WebApplicationContext,其基本資源路徑(透過@WebAppConfiguration定義)也將包含在上下文快取鍵中。有關快取的更多詳細資訊,請查閱參考手冊的上下文快取部分。



應用程式上下文層次結構


注意截至Spring Framework 3.2 RC1,上下文層次結構的支援尚未實現。

在Spring TestContext Framework管理的整合測試中,目前只支援扁平的、非層次結構的上下文。換句話說,沒有簡單的方法為測試建立具有父子關係的上下文。但在生產部署中支援上下文層次結構。因此,能夠測試它們會很好。

考慮到這一點,Spring團隊希望引入整合測試支援,以載入帶有父上下文的測試應用程式上下文,理想情況下將支援以下常見層次結構。

  • WebApplicationContext ← Dispatcher WebApplicationContext
  • EAR ← 根WebApplicationContext ← Dispatcher WebApplicationContext

目前的提案包括引入一個新的@ContextHierarchy註解,該註解將包含巢狀的@ContextConfiguration宣告,以及@ContextConfiguration中一個新的name屬性,可用於在上下文層次結構中合併覆蓋命名配置。

為了闡明提案,讓我們看幾個例子...

AppCtxHierarchyTests演示了在單個測試類中宣告的父子上下文層次結構,其中上下文是標準上下文(即非Web上下文)。

帶上下文層次結構的單個測試


@RunWith(SpringJUnit4ClassRunner.class)

@ContextHierarchy({
	@ContextConfiguration("parent.xml"),
	@ContextConfiguration("child.xml")
})
public class AppCtxHierarchyTests {}

ControllerIntegrationTests演示了在單個測試類中宣告的父子上下文層次結構,其中上下文是WebApplicationContexts,並模擬典型的Spring MVC部署。

根WAC和Dispatcher WAC


@RunWith(SpringJUnit4ClassRunner.class)

@WebAppConfiguration

@ContextHierarchy({
    @ContextConfiguration(
		name = "root",
		classes = WebAppConfig.class),
    @ContextConfiguration(
		name = "dispatcher",
		locations = "/spring/dispatcher-config.xml")
})
public class ControllerIntegrationTests {}

以下程式碼列表演示瞭如何在測試類層次結構中構建上下文層次結構,其中測試類層次結構中的每個級別都負責配置其在上下文層次結構中的相應級別。在所有這些子類中執行測試將導致載入(和快取)三個應用程式上下文和兩個不同的上下文層次結構。

類和上下文層次結構


@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(
  "file:src/main/webapp/WEB-INF/applicationContext.xml")
public abstract class AbstractWebTests{}

@ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml"))
public class SoapWebServiceTests extends AbstractWebTests{}

@ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml"))
public class RestWebServiceTests extends AbstractWebTests{}

歡迎反饋

如果您對上下文層次結構提案的進一步資訊感興趣或想參與討論,請隨時關注以下JIRA問題並向我們提供您的反饋。

  • SPR-5613: 上下文層次結構支援
  • SPR-9863: Web上下文層次結構支援


總結


Spring Framework 3.2引入了多項新測試功能,重點是為測試Web應用程式提供一流的支援。我們鼓勵您儘快嘗試這些功能並向我們提供反饋。此外,請繼續關注Rossen Stoyanchev關於新的Spring MVC Test框架的後續文章。如果您發現任何錯誤或有任何改進建議,現在是採取行動的時候了!



[1] 參考手冊尚未更新以反映對Web應用程式的測試支援,但這些功能肯定會在Spring 3.2 GA中得到充分文件。



獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

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

瞭解更多

獲得支援

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

瞭解更多

即將舉行的活動

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

檢視所有