領先一步
VMware 提供培訓和認證,以加速您的進展。
瞭解更多如同 Juergen Hoeller 在他發布 Spring Framework 3.2 RC1 發布 的文章中提到的,Spring 團隊在測試支援方面引入了一些令人興奮的新功能。最重要的是,我們為測試網路應用程式新增了第一級的支援。[1]
請注意:這是一篇來自我的 交叉貼文,源自我公司 Swiftmind 的部落格。
在這篇文章中,我們將首先看看 Spring Framework 中一些通用的新測試功能,然後我們將詳細介紹使用 WebApplicationContext
以及請求和會話範圍 bean 進行測試的支援。最後,我們將看看對 ApplicationContextInitializers
的支援,並簡要討論應用程式上下文階層測試的路線圖。
Rossen Stoyanchev 稍後將發布一篇詳細的文章,介紹新的 Spring MVC Test 框架,該框架為測試 Spring MVC 應用程式提供第一級的支援。因此,請務必持續關注,因為它建立在本文稍後討論的基本網路測試支援之上。
spring-test
模組現在針對並支援 JUnit 4.10 和 TestNG 6.5.2 進行建置,並且 spring-test
現在相依於 junit:junit-dep
Maven 構件,而不是 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 支援。
關於網路元件的單元測試支援,我們為現有的 Servlet API mock 物件新增了功能,例如 MockServletContext
、MockHttpSession
、MockFilterChain
和 MockRequestDispatcher
,並且我們引入了與 REST Web Services 相關的新 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
結合使用時(即,與使用 XML 配置 <tx:annotation-driven />
相反),以程式設計方式指定與 @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
類別和環境配置檔進行測試 的支援。
WebApplicationContext
?@WebAppConfiguration
註解您的測試類別即可。實際上就是這麼簡單。測試類別上 @WebAppConfiguration
的存在指示 TestContext 框架 (TCF) 應該為您的整合測試載入 WebApplicationContext
(WAC)。在幕後,TCF 確保建立 MockServletContext
並提供給您測試的 WAC。預設情況下,您的 MockServletContext
的基本資源路徑將設定為 "src/main/webapp"。這被解釋為相對於 JVM 根目錄的路徑(即,通常是專案的路徑)。如果您熟悉 Maven 專案中網路應用程式的目錄結構,您就會知道 "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
類別或靜態巢狀 @Configuration
類別位於同一套件中的 "WacTests-context.xml")。
預設資源語意
@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 資源前綴來覆寫這兩個註解的預設資源語意。請將此範例中的註解與上一個範例進行比較。
為了提供全面的網路測試支援,Spring 3.2 引入了一個新的 ServletTestExecutionListener
,預設情況下已啟用。當針對 WebApplicationContext
進行測試時,此 TestExecutionListener 在每個測試方法之前,透過 Spring Web 的 RequestContextHolder
設定預設的執行緒區域狀態,並根據透過 @WebAppConfiguration
配置的基本資源路徑建立 MockHttpServletRequest
、MockHttpServletResponse
和 ServletWebRequest
。ServletTestExecutionListener
還確保 MockHttpServletResponse
和 ServletWebRequest
可以注入到測試實例中,並且一旦測試完成,它會清理執行緒區域狀態。
一旦您為測試載入了 WebApplicationContext
,您可能會發現需要與網路 mock 物件互動,例如,設定您的測試夾具或在調用您的網路元件後執行斷言。以下範例示範了哪些 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
檢索的網路元件(即,透過依賴注入)。以下程式碼片段顯示了登入用例的 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 定義配置檔。初始化器可以在 web.xml
中配置,方法是透過 ContextLoaderListener
的 context-param
和 DispatcherServlet
的 init-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
WebApplicationContext
← Dispatcher WebApplicationContext
目前的提案包括引入新的 @ContextHierarchy
註解,其中包含巢狀 @ContextConfiguration
宣告,以及 @ContextConfiguration
中的新 name
屬性,可用於合併或覆寫上下文階層中的命名配置。
為了闡明該提案,讓我們看一些範例...
AppCtxHierarchyTests
示範了在單個測試類別中宣告的父子上下文階層,其中上下文是標準上下文(即,非網路)。
具有上下文階層的單個測試
@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 問題並向我們提供您的意見回饋。
Spring Framework 3.2 引入了多項新的測試功能,重點是為測試網路應用程式提供第一級的支援。我們鼓勵您盡快試用這些功能並向我們提供意見回饋。此外,請繼續關注 Rossen Stoyanchev 關於新的 Spring MVC Test 框架的後續文章。如果您發現任何錯誤或對改進有任何建議,現在是 採取行動 的時候了!
[1] 參考手冊尚未更新以反映對網路應用程式的測試支援,但這些功能肯定會在 Spring 3.2 GA 中得到充分記錄。