詳解在Spring中進行整合測試
按照Spring的推薦(原話:You should not normally use the Spring container for unit tests: simply populate your POJOs in plain JUnit tests!),在單元測試時,你不應該依賴於Spring容器。換言之,你不應該在單元測試時啟動ApplicatonContext並從中獲取Bean,相反你應該通過模擬物件完成單元測試。
需要測試的業務介面
假設我們的應用中擁有一個UserService業務層介面,它擁有4個業務方法,其程式碼如下所示:
程式碼清單1 UserServie介面
package com.baobaotao.service; import com.baobaotao.domain.User; import org.springframework.transaction.annotation.Transactional; @Transactional public interface UserService { boolean hasMatchUser(String userName,String password); User findUserByUserName(String userName); void loginSuccess(User user); void registerUser(User user); }
我們通過UserServiceImpl對UserService提供了實現:
程式碼清單2 UserServiceImpl實現UserService介面
package com.baobaotao.service; import com.baobaotao.dao.LoginLogDao; import com.baobaotao.dao.UserDao; import com.baobaotao.domain.LoginLog; import com.baobaotao.domain.User; public class UserServiceImpl implements UserService { private UserDao userDao; private LoginLogDao loginLogDao; public boolean hasMatchUser(String userName, String password) { int matchCount =userDao.getMatchCount(userName, password); return matchCount > 0; } public User findUserByUserName(String userName) { return userDao.findUserByUserName(userName); } public void loginSuccess(User user) { user.setCredits( 5 + user.getCredits()); LoginLog loginLog = new LoginLog(); loginLog.setUserId(user.getUserId()); loginLog.setIp(user.getLastIp()); loginLog.setLoginDate(user.getLastVisit()); userDao.updateLoginInfo(user); loginLogDao.insertLoginLog(loginLog); } public void setLoginLogDao(LoginLogDao loginLogDao) { this.loginLogDao = loginLogDao; } public void setUserDao(UserDao userDao) { this.userDao = userDao; } }
UserServiceImpl引用了兩個DAO層的類(UserDao和LoginLogDao)共同實現UserService的介面,在UserServiceImpl開放使用之前,我們有必須對其進行整合測試,以保證實現邏輯的正確性。
使用傳統的方式進行整合測試
下面,我們通過傳統的方式為UserServiceImpl編寫了一個整合測試用例,測試程式碼如下所示:
程式碼清單 3 TestUserService:UserService整合測試用例
package com.baobaotao.service;
…
public class TestUserService extends TestCase {
public ApplicationContext ctx = null; //①Spring容器引用
private static String[] CONFIG_FILES = { //②Spring配置檔案
"baobaotao-dao.xml",
"baobaotao-service.xml" };
protected void setUp() throws Exception { //③啟動Spring容器
ctx = new FileSystemXmlApplicationContext(CONFIG_FILES);
}
public void testHasMatchUser() { //④測試方法一
// ④-1從容器中獲取Bean
UserService userService = (UserService) ctx.getBean("userService");
boolean b1 = userService.hasMatchUser("admin", "123456");
boolean b2 = userService.hasMatchUser("admin", "1111");
assertTrue(b1);
assertTrue(!b2);
}
public void testAddLoginLog() {⑤測試方法二
//⑤-1從容器中獲取Bean
UserService userService = (UserService) ctx.getBean("userService");
User user = userService.findUserByUserName("admin");
user.setUserId(1);
user.setUserName("admin");
user.setLastIp("192.168.12.7");
user.setLastVisit(new Date());
userService.loginSuccess(user);
}
…//省略其餘的測試方法
}
在這個測試用例中,我們使用了最原始的JUnit的TestCase進行整合測試,乍一看並沒有多大的問題,但仔細分析一下,我們就可以總結出以下四點明顯的不足:
1)導致多次Spring容器初始化問題:
根據JUnit測試方法的呼叫流程(參見錯誤!未找到引用源。小節的描述),每執行一個測試方法都會建立一個TestUserService例項並呼叫setUp()方法。由於我們在setUp()方法中初始化Spring容器,這意味著TestUserService有多少個測試方法,Spring容器就會被重複初始化多少次。雖然初始化Spring容器的速度並不會太慢,但由於可能會在Sprnig容器初始化時執行載入Hibernate對映檔案等耗時的操作,如果每執行一個測試方法都必須重複初始化Spring容器,則對測試效能的影響是不容忽視的;
2)需要使用硬編碼方式手工獲取Bean:
在④-1和⑤-1處,我們通過ctx.getBean()方法從Spring容器中獲取需要測試的目標Bean,並且還要進行強制型別轉換的造型操作。這種乏味的操作迷漫在測試用例的程式碼中,讓人覺得繁瑣不堪;
3)資料庫現場容易遭受破壞:
⑤處的測試方法會對資料庫記錄進行插入操作,雖然是針對開發資料庫進行操作,但如果資料操作的影響是持久的,可能會影響到後面的測試行為。舉個例子,你在測試方法中插入一條ID為1的User記錄,第一次執行不會有問題,第二次執行時,就會因為主鍵衝突而導致測試用例失敗。所以應該既能夠完成功能邏輯檢查,又能夠在測試完成後恢復現場,不會留下“後遺症”;
4)沒有對資料操作正確性進行檢查:
⑤處我們向登入日誌表插入了一條成功登入日誌,可是我們卻沒有對t_login_log表中是否確實添加了一條記錄進行檢查。原來我們的方式是開啟資料庫,肉眼觀察是否插入了相應的記錄,但這嚴重違背了自動測試的原則。試想,你在測試包括成千上萬個數據操作行為的程式時,如何用肉眼進行檢查?
既然使用傳統方式對Spring應用進行整合測試存在這麼多不足,Spring責無旁貸地擔當起革新之任。它通過擴充套件JUnit框架提供了一套專門測試Spring應用的有力工具。藉助Spring整合測試工具的幫助,以上所羅列的種種問題將冰消雪融、雲開霧散。Service:UserService整合測試用例Spring在org.springframework.test包中為測試提供了幾個有用的類,它們都是JUnit TestCase的子類。通過層層擴充套件,不斷豐富測試的功能,我們可以通過下圖瞭解這些類的繼承關係:
圖1 Spring測試工具類
下面,我們來逐個瞭解這棵承繼類樹中每個節點測試類的功用,第一個要認識的是直接擴充套件於TestCase的ConditionalTestCase測試類。
(1)ConditionalTestCase
如果你直接通過擴充套件TestCase建立測試用例,則所有帶test字首的測試方法都會被毫無例外地執行。而ConditionalTestCase可以讓你在某些情況下,有選擇地關閉掉一些測試方法,不讓他們在測試用例中執行。這給開發者帶來了很大的靈活性,因為他們可以在某次測試中關閉掉一些測試方法,而僅運行當前特別關注的測試方法,將問題域聚集到一定範圍內。
如果你要關閉某個測試方法行,僅需實現ConditionalTestCase的 isDisabledInThisEnvironment(String testMethodName)方法就可以了,ConditionalTestCase在執行每一個測試方法前會根據isDisabledInThisEnvironment()方法判斷是簡單放棄目標方法的執行,還是按正常方式執行之。該方法預設情況下對所有的測試方法都返回false,也即執行所有的測試方法。讓我們來看一個具體例子:
程式碼清單 4 ConditionalTest1:有條件執行測試方法
package com.baobaotao.test;
import org.springframework.test.ConditionalTestCase;
public class ConditionalTest1 extends ConditionalTestCase {
//(1)被忽略不執行的測試方法
private static String[] IGNORED_METHODS = {"testMethod1","testMethod3"};
@Override
protected boolean isDisabledInThisEnvironment(String testMethodName) {// (2)所有在
for (String method : IGNORED_METHODS) { // IGNORED_METHODS陣列中
if (method.equals(testMethodName)) { //的方法都忽略執行。
return true;
}
}
return false;
}
public void testMethod1(){ // (3)不執行
System.out.println("method1");
}
public void testMethod2(){ // (4)執行
System.out.println("method2");
}
public void testMethod3(){ // (5)不執行
System.out.println("method3");
}
}
如果我們直接承繼JUnit的TestCase,③、④及⑤處的三個測試方法都會被執行,但現在我們通過繼承ConditionalTestCase編寫測試類,並覆蓋了isDisabledInThisEnvironment()方法,當測試方法名位於IGNORED_METHODS陣列中時,測試方法就被旁路掉了。因此當執行ConditionalTest1時,你會發現只有④處的testMethod2()測試方法得到了執行,其它兩個測試方法看起來也被成功執行,只不過會程式日誌會給出報告,告訴你哪些測試方法是真正被執行,而哪些方法被“偽執行”的。 ConditionalTestCase其實可用於任何程式的單元測試中,它本身並沒有和Spring容器有任何關聯,它僅添加了一個按條件執行測試方法的功能。
(2)AbstractSpringContextTests
AbstractSpringContextTests擴充套件於ConditionalTestCase,它維護了一個static型別的快取器(HashMap),它使用鍵儲存Spring ApplicationContext例項,這意味著Spring ApplicationContext是JVM級的,不同測試用例、不同測試方法都可以共享這個例項。也就是說,在執行多個測試用例和測試方法時,Spring容器僅需要例項化一次就可以了,極大地提高了基於Spring容器測試程式的執行效率。Spring通過這個測試幫助類解決了前面我們所指出的第1)個問題。
(3)AbstractSingleSpringContextTests
AbstractSingleSpringContextTests繼承於AbstractSpringContextTests,它通過一些方法讓你方便地指定Spring配置檔案所在位置:
1) String[] getConfigLocations():該方法允許你在指定Spring配置檔案時使用資源型別字首,這些資源型別字首包括:classpath:、file:。以類似於“com/baobaotao/beans.xml”形式指定的資源被當成類路徑資源處理;
2) String[] getConfigPaths():以“/”開頭的地址被當成類路徑處理,如“/com/baobaotao/beans.xml”,而未以“/”開頭的地址被當成相對於測試類所在包的檔案路徑,如“beans.xml”表示配置檔案在測試類所在類包的目錄下;
3) String getConfigPath():和getConfigPaths()類似,在僅需指定一個配置檔案中使用。 以上三個方法,它們的優先順序和我們介紹的先後順序對應,也就是說,當你在子類中覆蓋了getConfigLocations()方法後,其它兩個方法就沒有意義了。所以你僅需選擇三者當中適合的方法進行覆蓋,而沒有必要同時覆蓋多個方法。
AbstractSingleSpringContextTests將根據這些方法指定的Spring配置檔案初始化Spring容器,然後將Spring容器引用新增到static快取中。並通過getApplicationContext()向子類開放ApplicationContext的引用。 一般情況下,所有的測試類和測試方法都可以共享這個Spring容器直到測試完結,不過在某些極端情況下,測試方法可能會對Spring容器進行改動(比如通過程式改變Bean的配置定義),如果這種改變對於其它測試方法來說是有干擾的,這就相當於“弄髒”了作為測試現場的Spring容器,因此在下一個測試方法執行前必須“抹除”這個改變。你可以簡單地在會“弄髒”Spring容器的測試方法中新增setDirty()方法向AbstractSingleSpringContextTests報告這一行為,這樣在下一個測試方法執行前,AbstractSingleSpringContextTests就會重新載入Spring容器以修補被“弄髒”的部分。
雖然你可以直接繼承AbstractSpringContextTests或AbstractSingleSpringContextTests建立自己的整合測試用例,不過你大可不必如此著急。Spring已經提供了幾個功能齊全、實踐性更強的子類,讓我們繼續探索Spring整合測試工具類的精彩篇章吧。
一般整合測試
應該說,Spring通過AbstractSpringContextTests或AbstractSingleSpringContextTests準備好了整合測試的一些基礎設施,在建築學上,這叫夯實地基,而AbstractDependencyInjectionSpringContextTests是在此地基之上建起的第一幢樓房。 AbstractDependencyInjectionSpringContextTests所新添的主要功能是其子類的屬效能被Spring容器中的Bean自動裝配,你無需手工通過ApplicationContext#getBean()從容器中獲取目標Bean自行裝配。它很好回答了前面我們所指出第2)問題,下面我們通過例項進行學習:
package com.baobaotao.test;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
import com.baobaotao.service.UserService;
public class DependencyInjectionCtxTest
extends AbstractDependencyInjectionSpringContextTests {
private UserService userService;
public void setUserService(UserService userService) {(1)該屬性設定方法會被自動調動
this.userService = userService;
}
@Override
protected String[] getConfigLocations() { (2)指定Spring配置檔案所在位置
return new String[]{"baobaotao-service.xml","baobaotao-dao.xml"};
}
public void testHasMatchUser(){ (3)測試方法
boolean match = userService.hasMatchUser("tom","123456");
assertEquals(true, match);
}
…
}
程式碼清單 5 DependencyInjectionCtxTest在②處,我們指定了Spring配置檔案所在的位置,AbstractDependencyInjectionSpringContextTests將使用這些配置檔案初始化好Spring容器,並將它們保存於static的快取中。然後馬上著手根據型別匹配機制(byType),
自動將Spring容器中匹配測試類屬性的Bean通過Setter注入到測試類中。為了方便說明這一重要的特性,我們先看一下baobaotao-service.xml的內容
<beans>
<tx:annotation-driven/>
①按型別匹配於DependencyInjectionCtxTest的userService屬性
<bean id="userService" class="com.baobaotao.service.UserServiceImpl">
<property name="userDao" ref="userDao"/>
<property name="loginLogDao" ref="loginLogDao"/>
</bean>
…
</beans>
根據baobaotao-service.xml配置檔案的內容,我們知道Spring容器中有一個UserService Bean,AbstractDependencyInjectionSpringContextTests探測到Spring容器中存在一個匹配於userService屬性的Bean後,就將其注入到DependencyInjectionCtxTest的userService屬性中。userService是這個整合測試類的測試韌體,因此我們說AbstractDependencyInjectionSpringContextTests可以自己裝配測試韌體。解決自動裝配問題
如果Spring容器中擁有多個匹配UserService型別的Bean,由於Spring沒有足夠的資訊做出取捨決策,因此會丟擲UnsatisfiedDependencyException異常。假設我們採用以下傳統的事務管理的配置方式對UserService進行配置,按型別匹配的自動裝配機制就會引發問題:
①用於被代理的目標Bean,按型別匹配於UserService
<bean id="userServiceTarget" class="com.baobaotao.service.UserServiceImpl">
<property name="userDao" ref="userDao" />
<property name="loginLogDao" ref="loginLogDao"></property>
</bean>
②通過事務代理工廠為UserServiceImpl建立的代理Bean,也按匹配於UserService <bean id="userService"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager" />
<property name="target" ref="userServiceTarget" />
<property name="transactionAttributes">
…
</property>
</bean>
由於①處和②處的Bean都按型別匹配於UserService,在對DependencyInjectionCtxTest的userService屬性進行自動裝配將會引發問題。有兩種針對該問題的解決辦法:
(1) 調整配置檔案,使按型別匹配於UserService的Bean僅有一個,具體有以下兩個方法:
1)將①處的Bean作為②處的內部Bean進行裝配;
2) 使用基於註解驅動的事務管理配置機制,這樣就無需在配置檔案中定義兩個UserService的Bean了。關於註解驅動事務管理配置的詳細資訊,請參見9.6小節的內容。
(2)改變DependencyInjectionCtxTest的自動裝配機制:Spring預設使用byType型別的自動裝配機制,但它允許你通過setAutowireMode()的方法改變預設自動裝配的機制,比如你可以呼叫setAutowireMode(AUTOWIRE_BY_NAME)方法啟用按名稱匹配的自動裝配機制。AbstractDependencyInjectionSpringContextTests定義了三個代表自動裝配機制型別的常量,分別說明如下:
1) AUTOWIRE_BY_TYPE:按型別匹配的方式進行自動裝配,這個預設的機制;
2) AUTOWIRE_BY_NAME:按名字匹配的方式進行自動裝配
3) AUTOWIRE_NO:不使用自動裝配機制,這意味著你需要手工呼叫getBean()進行裝配。
現在我們解決了在自動裝配時,因Spring容器中存在多個匹配Bean而導致的問題,接下來讓我們考察另一個自動裝配的問題。
依賴檢查
假設我們在DependencyInjectionCtxTest新增一個User型別的屬性並提供Setter方法,而Spring容器中沒有匹配該屬性的Bean:
package com.baobaotao.test;
…
import com.baobaotao.domain.User;
public class DependencyInjectionCtxTest extends AbstractDependencyInjectionSpringContextTests {
private User user;
public void setUser(User user) {
this.user = user;
}
…
}
猜想一下重新執行DependencyInjectionCtxTest將會發生什麼情況呢?答案可能讓你失望:UnsatisfiedDependencyException再次象黑幕一樣降臨。在預設情況下,
AbstractDependencyInjectionSpringContextTests要求所有屬性都能在Spring容器中找到對應Bean,否則丟擲異常。
仔細思考一下,這種執行機制並非沒有道理,因為既然你已經提供了Setter方法,就相當於給出了這樣的暗示資訊:“這個屬性測試類自身建立不了,必須由外部提供”。而在使用自動裝配機制的情況下,測試類屬性自動從Spring容器中注入匹配的屬性,一般情況下不會手工去呼叫Setter方法準備屬性。
如果你出於一些特殊的理由,希望在採用自動裝配的情況下,如果有屬性未得到裝配也不在乎,那麼你可以在測試類建構函式中呼叫setDependencyCheck(false)方法達到目的:
相關推薦
詳解在Spring中進行整合測試
在單元測試時,我們儘量在遮蔽模組間相互干擾的情況下,重點關注模組內部邏輯的正確性。而整合測試則是在將模組整合在一起後進行的測試,它的目的在於發現一些模組間整合的問題。有些功能很難通過模擬物件進行模擬,相反它們往往只能在真實模組整合後,才能真正執行起來,如事務管理就是其中比較
RabbitMQ實例詳解+Spring中的MQ使用
方法 it is col 一致性 cli 服務器 發送請求 arguments restrict RabbitMQ實例詳解 消息隊列中間件是分布式系統中重要的組件,主要解決應用解耦,異步消息,流量削鋒等問題,實現高性能,高可用,可伸縮和最終一致性架構。 Queue Q
知識儲備:詳解Spring中AOP原理(基於註解版)2
接著上一篇部落格講,上一篇部落格地址:https://blog.csdn.net/qq_36625757/article/details/83652173 8.之前我們說過,測試方法在執行時new了一個AnnotationConfigApplicationContext傳入一個配置類,呼叫了re
詳解spring中的事務管理(程式設計式的事務管理,宣告式的事務管理)
spring提供的事務管理API 1. PlatformTransactionManager:平臺事務管理器. commit(TransactionStatus status) getTransaction(TransactionDefinition de
詳解Spring 中如何控制2個bean中的初始化順序
開發過程中有這樣一個場景,2個 bean 初始化邏輯中有依賴關係,需要控制二者的初始化順序。實現方式可以有多種,本文結合目前對 Spring 的理解,嘗試列出幾種思路。 場景 假設A,B兩個 bean 都需要在初始化的時候從本地磁碟讀取檔案,其中B載入的檔案,依賴A中載入的全域性配置檔案中配
詳解Spring中的CharacterEncodingFilter過濾器,過濾url請求
在專案中有很多讓人頭疼的問題,其中,編碼問題位列其一,那麼在Spring框架中是如何解決從頁面傳來的字串的編碼問題的呢?下面我們來看看Spring框架給我們提供過濾器CharacterEncodingFilter 1.看清結構: 可以看到其繼承Gener
Spring Boot 項目在 IDEA 中 進行單元測試
res sin run ng- targe clas 單元測試 2.0 就會 Spring Boot提供了許多實用程序和註釋來幫助您測試應用程序。 測試由兩個模塊提供支持:spring-boot-test包含核心項,spring-boot-test-autoconfigur
SSM框架整合配置詳解(spring,spring mvc,mybatis)
當今SSM框架已經成為了一種主流,其中spring,spring mvc和mybatis框架的功能很強大,給我們程式設計師節省了很多力氣,可以說這三種框架簡直就是我們程式設計師的福音,但是我們都知道,框架在自身帶來便捷的同時,也存在很多的配置檔案,更別說當三個框架整合的時候那就更加的困難了,
詳解spring整合shiro許可權管理與資料庫設計
現在基本上所有的後臺系統都逃不過許可權管理這一塊,這算是一個剛需了。現在我們來整合shiro來達到顆粒化許可權管理,也就是從連線選單到頁面功能按鈕,都進行許可權都驗證,從前端按鈕的顯示隱藏,到後臺具體功能方法的許可權驗證。 首先要先設計好我們的資料庫,先來看一張比較粗糙的資
詳解Spring Boot中的RabbitMQ
種類型 情況 pic att 嘗試 uem 種類 mpp 為什麽 RabbitMQ 即一個消息隊列,主要是用來實現應用程序的異步和解耦,同時也能起到消息緩沖,消息分發的作用。 消息中間件在互聯網公司的使用中越來越多,剛才還看到新聞阿裏將 RocketMQ 捐獻給了 Apac
【Oracle】詳解Oracle中NLS_LANG變量的使用
make fault tro territory font pin onclick 添加 其中 目錄結構: // contents structure [-] 關於NLS_LANG參數 NSL_LANG常用的值 在MS-DOS模式和Batch模式中
詳解Python中的生成器表達式(generator expression)
新元素 括號 tuple 列表推導式 特點 解析式 表達式 但是 bracket 介紹 1、生成器表達式(generator expression)也叫生成器推導式或生成器解析式,用法與列表推導式非常相似,在形式上生成器推導式使用圓括號(parenth
詳解Python中的join()函數的用法
pre 說明 bsp 字符 指定 .net 絕對路徑 字典 -s 函數:string.join() Python中有join()和os.path.join()兩個函數,具體作用如下: join(): 連接字符串數組。將字符串、元組、列表中的元素以指定的字符(分
詳解WordPress中簡碼格式標簽編寫的基本方法
filter 所有 oot 執行 body 標簽 支持 script tro WordPress 簡碼是一種類似於論壇標簽的東西,格式類似於把尖括號換成中括號的 Html 標簽。簡碼很多人叫做短代碼,但官方的翻譯應該是簡碼,在這裏糾正一下。 簡碼的開發的邏輯比較簡單,主要就
詳解 javascript 中的比較(==和===)
不一致 mit 如果 asc onu tin 算法 復雜 undefine 抽象相等比較算法 比較運算 x==y, 其中 x 和 y 是值,產生 true 或者 false。這樣的比較按如下方式進行: 若 Type(x) 與 Type(y) 相同, 則 若 Type(x)
舉例詳解Python中的split()函數的使用方法
使用方法 imp count say 文章 pri 參考 詳解 參數 這篇文章主要介紹了舉例詳解Python中的split()函數的使用方法,split()函數的使用是Python學習當中的基礎知識,通常用於將字符串切片並轉換為列表,需要的朋友可以參考下 函數:spl
(轉)詳解C#中的反射
typeof ref enc sin setvalue abs class 方法測試 strac (轉)http://www.cnblogs.com/Stephenchao/p/4481995.html 反射的用途: (1)使用Assembly定義和加載程序集,加載在
詳解java中的數據結構
span 通過 組成 ret hashcode p s 函數 arr 均衡 線性表,鏈表,哈希表是常用的數據結構,在進行Java開發時,JDK已經為我們提供了一系列相應的類來實現基本的數據結構。這些類均在java.util包中。本文試圖通過簡單的描述,向讀者闡述各個類的
詳解Spring Boot配置文件之多環境配置
多個 提高效率 橫線 文件 style ica property form blog 一. 多環境配置的好處: 1.不同環境配置可以配置不同的參數~ 2.便於部署,提高效率,減少出錯~ 二. properties多環境配置 1. 配置激活選項 spring.profile
詳解webpack中的hash、chunkhash、contenthash區別
con tro 們的 tex trac extra lena fig files hash、chunkhash、contenthash hash一般是結合CDN緩存來使用,通過webpack構建之後,生成對應文件名自動帶上對應的MD5值。如果文件內容改變的話,那麽對應文件