保持領先
VMware 提供培訓和認證,助您快速進步。
瞭解更多正如 Juergen Hoeller 在宣佈 Spring Framework 3.2 RC1 釋出 的文章中提到的,Spring 團隊在測試支援方面引入了一些令人興奮的新特性。最重要的是,我們為測試 Web 應用添加了頭等支援。[1]
請注意:本文轉載自我的 Swiftmind 公司部落格的 交叉釋出文章。
在本文中,我們將首先介紹 Spring Framework 中的一些通用新增測試特性,然後詳細討論使用 WebApplicationContext
以及 request 和 session 作用域 Bean 的測試支援。最後,我們將介紹對 ApplicationContextInitializer
的支援,並簡要討論應用程式上下文層次結構測試的路線圖。
Rossen Stoyanchev 稍後將就全新的 Spring MVC Test 框架發表一篇詳細文章,該框架為測試 Spring MVC 應用提供了頭等支援。因此,請務必繼續關注,因為它建立在本文稍後討論的基本 Web 測試支援之上。
spring-test
模組現在基於並支援 JUnit 4.10 和 TestNG 6.5.2,並且 spring-test
現在依賴於 Maven artifact junit:junit-dep
而不是 junit:junit
,這意味著您可以完全控制對 Hamcrest 庫(例如,hamcrest-core
、hamcrest-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 建立動態 Mock。例如,在 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 中,工廠方法的泛型返回型別現在可以正確推斷,並且對 Mock 的 按型別自動裝配 應該可以按預期工作。因此,像 MockitoFactoryBean
、EasyMockFactoryBean
或 Springockito 這樣的自定義變通方法可能不再需要。
我們引入了 MockEnvironment
,它補充了現有的 MockPropertySource
,以完成對 Spring 3.1 中引入的環境和屬性源抽象進行 Mock 的支援。
關於 Web 元件的單元測試支援,我們為現有的 Servlet API Mock(例如 MockServletContext
、MockHttpSession
、MockFilterChain
和 MockRequestDispatcher
)添加了新特性,並且引入了與 REST Web 服務相關的新 Mock:用於客戶端的 MockClientHttpRequest
和 MockClientHttpResponse
,以及用於伺服器端的 MockHttpInputMessage
和 MockHttpOutputMessage
。
在 Spring 3.2 中,我們棄用了 SimpleJdbcTestUtils
,轉而推薦改進後的 JdbcTestUtils
類,該類除了提供 SimpleJdbcTestUtils
之前提供的所有功能外,還提供了新的 countRowsInTableWhere()
和 dropTables()
工具方法。這些更改有助於避免與使用已棄用的 SimpleJdbcTemplate
相關的編譯器警告,並提供了一種便捷的方式,可以使用 WHERE
子句計算表中的行數以及刪除表列表。同樣,AbstractTransactionalJUnit4SpringContextTests
和 AbstractTransactionalTestNGSpringContextTests
已被改造,增加了 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 Framework 中的新特性。如果您已經熟悉 TestContext 框架,請隨意跳到下一節。否則,您可能希望首先透過以下段落中的連結熟悉相關資訊。
在 Spring 2.5 中,我們引入了 Spring TestContext Framework,它提供了註解驅動的整合測試支援,可與 JUnit 或 TestNG 一起使用。本文中的示例將側重於基於 JUnit 的測試,但這裡使用的所有特性也適用於 TestNG。
在 Spring 3.1 中,我們修訂了 Spring TestContext Framework,並添加了對使用 @Configuration
類和環境 Profile 進行測試的支援。
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 測試支援,Spring 3.2 引入了一個新的 ServletTestExecutionListener
,該監聽器預設啟用。當針對 WebApplicationContext
進行測試時,此 TestExecutionListener 會在每個測試方法之前透過 Spring Web 的 RequestContextHolder
設定預設的執行緒本地狀態,並根據透過 @WebAppConfiguration
配置的基礎資源路徑建立一個 MockHttpServletRequest
、MockHttpServletResponse
和 ServletWebRequest
。ServletTestExecutionListener
還確保可以將 MockHttpServletResponse
和 ServletWebRequest
注入到測試例項中,並在測試完成後清理執行緒本地狀態。
一旦您的測試載入了 WebApplicationContext
,您可能會發現需要與 Web Mock 進行互動——例如,設定您的測試夾具或在呼叫 Web 元件後執行斷言。以下示例演示了哪些 Mock 可以自動裝配到您的測試例項中。請注意,WebApplicationContext
和 MockServletContext
在整個測試套件中都會被快取;而其他 Mock 則由 ServletTestExecutionListener
按每個測試方法進行管理。
注入 Mock
@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 Spring 已經支援好幾年了,但測試它們總是有點複雜。自 Spring 3.2 起,按照以下步驟測試您的請求作用域和會話作用域 Bean 變得非常簡單:
@WebAppConfiguration
註解您的測試類,確保為您的測試載入了 WebApplicationContext
。WebApplicationContext
中獲取的 Web 元件(即,透過依賴注入)。以下程式碼片段顯示了一個登入用例的 XML 配置。請注意,userService
Bean 依賴於一個請求作用域的 loginAction
Bean。此外,LoginAction
是使用 SpEL 表示式 例項化的,該表示式從當前的 HTTP 請求中檢索使用者名稱和密碼。在我們的測試中,我們將希望透過 TestContext 框架管理的 Mock 來配置這些請求引數。
請求作用域 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 框架管理的 Mock 會話中配置一個主題。
會話作用域 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
中,我們將 UserService
和 MockHttpSession
注入到我們的測試例項中。在我們的 sessionScope()
測試方法中,透過在提供的 MockHttpSession
中設定預期的 "theme" 屬性來設定我們的測試夾具。當在我們的 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 定義 Profile。可以透過在 web.xml
中為 ContextLoaderListener
指定 contextInitializerClasses
的 context-param
和為 DispatcherServlet
指定 init-param
來配置 Initializer。
要在整合測試中使用上下文 Initializer,只需透過 Spring 3.2 中引入的新 initializers
屬性在 @ContextConfiguration
中宣告 Initializer 類即可。可以透過 inheritInitializers
屬性(預設為 true
)控制 Initializer 在測試類層次結構中的繼承。由於 ApplicationContextInitializer
提供了初始化應用程式上下文的完全程式設計方法,因此 Initializer 可以選擇性地配置整個上下文。換句話話,如果在透過 @ContextConfiguration
配置的整合測試中聲明瞭 Initializer,則不再絕對需要 XML 資源位置或註解的類。最後同樣重要的是,上下文 Initializer 是根據 Spring 的 Ordered
介面或 @Order
註解 排序 的。
以下程式碼示例展示了在整合測試中使用上下文 Initializer 的各種方式。第一個示例展示瞭如何結合 XML 資源位置配置單個 Initializer。下一個示例聲明瞭多個上下文 Initializer。第三個示例展示了在類層次結構中使用 Initializer 的情況,其中在 ExtendedTest
中宣告的上下文 Initializer 列表將與在 BaseTest
中宣告的合併。請注意,Initializer 的呼叫順序受 Spring 的 Ordered
介面實現或 @Order
註解的存在影響。第四個示例與第三個示例相同,只是 @ContextConfiguration
中的 inheritInitializers
屬性設定為 false
。結果是,父類中宣告的任何上下文 Initializer 都將被忽略(即,被覆蓋)。最後一個示例展示了 ApplicationContext
可以僅從上下文 Initializer 載入,而無需宣告 XML 資源位置或註解的類。
單個 Initializer
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
locations = "/app-config.xml",
initializers = CustomInitializer.class)
public class ApplicationContextInitializerTests {}
多個 Initializer
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
locations = "/app-config.xml",
initializers = {
PropertySourceInitializer.class,
ProfileInitializer.class
})
public class ApplicationContextInitializerTests {}
合併的 Initializer
@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 {}
被覆蓋的 Initializer
@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 {}
不含資源的 Initializer
// 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
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 部署。
Root 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 問題並向我們提供您的反饋。
Spring Framework 3.2 引入了多項全新的測試特性,重點關注對測試 Web 應用的頭等支援。我們鼓勵您儘快嘗試這些特性並向我們提供反饋。此外,請繼續關注 Rossen Stoyanchev 關於全新 Spring MVC Test 框架的後續文章。如果您發現任何 Bug 或有任何改進建議,現在是時候採取行動了!
[1] 參考手冊尚未更新以反映對 Web 應用程式的測試支援,但這些特性在 Spring 3.2 GA 釋出時肯定會得到充分文件化。