Spring測試 其實(shí)很簡單
在過去的職業(yè)生涯里,我經(jīng)常發(fā)現(xiàn)有些人不寫測試代碼,而他們聲稱不寫的理由是無法輕易地寫出覆蓋多個(gè)不同模塊的測試用例。好吧,我相信他們中的大部分要么是缺乏一些比較易掌握的技術(shù)手段,要么就是沒時(shí)間來把它搞清楚,畢竟工作中總會有進(jìn)度之類的各種壓力。因?yàn)椴恢涝撊绾螠y試,所以就經(jīng)常忽略集成測試,由此帶來的問題就是越來越糟糕的軟件、越來越多的BUG和更加失望的客戶。所以我想分享一些個(gè)人的經(jīng)驗(yàn),揭開集成測試神秘的面紗。
如何對基于Spring的工程更好地進(jìn)行集成測試
使用工具: Spring, JUnit, Mockito
想象有這樣一個(gè)Spring工程,它集成了一些外部服務(wù),例如,一些銀行的web服務(wù)。那么,為這個(gè)工程寫測試用例以及在持續(xù)集成系統(tǒng)中完成這些測試時(shí)所遇到的問題基本都差不多:
1.每次測試都會有交易進(jìn)行,每次交易都需要付出金錢成本,這些成本最終由客戶承擔(dān);
2.測試時(shí)發(fā)出的過多的請求有可能被認(rèn)為是惡意請求,可能造成在銀行的賬戶被封,后果是測試失??;
3.當(dāng)使用非生產(chǎn)環(huán)境進(jìn)行測試時(shí),測試結(jié)果并不十分可靠,同樣,后果是測試失敗。
通常情況下,你對單個(gè)類進(jìn)行測試的時(shí)候,問題很容易解決,因?yàn)槟憧梢蕴摂M一些外部服務(wù)來供調(diào)用。但是當(dāng)對整個(gè)巨大的業(yè)務(wù)流程進(jìn)行測試的時(shí)候,意味你需要對多個(gè)部件進(jìn)行測試,這時(shí),需要你將這些部件都納入到Spring容器中進(jìn)行管理。所幸,Spring包含了非常優(yōu)秀的測試框架,允許你將來自生產(chǎn)環(huán)境配置文件中的bean注入到測試環(huán)境中,但是對那些被調(diào)用的外部服務(wù),需要我們自己去寫模擬實(shí)現(xiàn)。一般人第一反應(yīng)可能是在測試的setUp階段對由Spring注入的bean進(jìn)行重新注入(修改),但是這種方法需要再仔細(xì)考慮一下。
警告:通過這種方式,你的測試代碼打破了容器自身的行為,所以沒法保證在真實(shí)的環(huán)境中也如你測試的結(jié)果一樣。
事實(shí)上,我們無需先實(shí)現(xiàn)模擬類然后再把它重新注入到所需的bean中,我們可以讓Spring幫助我們一開始就注入模擬類。讓我們用代碼演示一下。
示例工程包含一個(gè)名為BankService的類,代表調(diào)用的外部服務(wù),一個(gè)名為UserBalanceService的類,它會調(diào)用BankService。UserBalanceService實(shí)現(xiàn)的非常簡單,僅僅完成將余額從String向Double類型的轉(zhuǎn)換。
BankService.java的源碼:
public interface BankService { String getBalanceByEmail(String email); }
BankServiceImpl.java的源碼:
public class BankServiceImpl implements BankService { @Override public String getBalanceByEmail(String email) { throw new UnsupportedOperationException("Operation failed due to external exception"); } }
UserBalanceService.java的源碼:
interface UserBalanceService { Double getAccountBalance(String email); }
UserBalanceServiceImpl.java的源碼:
public class UserBalanceServiceImpl implements UserBalanceService { @Autowired private BankService bankService; @Override public Double getAccountBalance(String email) { return Double.valueOf(bankService.getBalanceByEmail(email)); } }
然后是Spring的XML配置文件,添加所需要的bean聲明。
applicationContext.xml的源代碼:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="bankService" class="ua.eshepelyuk.blog.springtest.springockito.BankServiceImpl"/> <bean id="userBalanceService" class="ua.eshepelyuk.blog.springtest.springockito.UserBalanceServiceImpl"/> </beans>
下面是測試類UserBalanceServiceImplTest.java的源代碼:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:/springtest/springockito/applicationContext.xml") public class UserBalanceServiceImplProfileTest { @Autowired private UserBalanceService userBalanceService; @Autowired private BankService bankService; @Test public void shouldReturnMockedBalance() { Double balance = userBalanceService.getAccountBalance("user@bank.com"); assertEquals(balance, Double.valueOf(123.45D)); } }
如我們預(yù)料的一樣,測試方法報(bào)UnsupportedOperationException異常。我們現(xiàn)在的目的是把BankService換成我們的模擬實(shí)現(xiàn)。直接使用Mockito來生成factory bean的方法是沒問題的,但是有更好的選擇,使用Springockito框架。繼續(xù)之前可以先大概了解一下。
剩下的問題就簡單了:如何讓Spring注入模擬的bean而不是真實(shí)的bean,在Spring 3.1版之前除了新建一個(gè)XML配置文件之外沒有其他的方法。但是自從Spring引入了bean的profile定義之后,我們有了更加優(yōu)雅的解決方式,雖然這種方式也需要一個(gè)額外的專門用作測試的XML配置文件。下面是這個(gè)用來測試的配置文件testApplicationContext.xml的代碼:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mockito="http://www.mockito.org/spring/mockito" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.mockito.org/spring/mockito https://bitbucket.org/kubek2k/springockito/raw/tip/springockito/src/main/resources/spring/mockito.xsd"> <import resource="classpath:/springtest/springockito/applicationContext.xml"/> <beans profile="springTest"> <mockito:mock id="bankService" class="ua.eshepelyuk.blog.springtest.springockito.BankService"/> </beans> </beans>
做相應(yīng)修改過之后的測試類UserBalanceServiceImplProfileTest.java的源代碼:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:/springtest/springockito/testApplicationContext.xml") @ActiveProfiles(profiles = {"springTest"}) public class UserBalanceServiceImplProfileTest { @Autowired private UserBalanceService userBalanceService; @Autowired private BankService bankService; @Before public void setUp() throws Exception { Mockito.when(bankService.getBalanceByEmail("user@bank.com")).thenReturn(String.valueOf(123.45D)); } @Test public void shouldReturnMockedBalance() { Double balance = userBalanceService.getAccountBalance("user@bank.com"); assertEquals(balance, Double.valueOf(123.45D)); } }
你可能注意到了,在setUp方法里,我們定義了模擬的行為,并且在類上面加了@Profile的注解。這個(gè)注解激活了名為springTest的profile,因此使用Springockito模擬的bean就可以自動注入到任何它所需要的地方了。這個(gè)測試的運(yùn)行結(jié)果會成功,因?yàn)镾pring注入了Springockito 所模擬的版本,而不是applicationContext.xml里所聲明的版本。
繼續(xù)優(yōu)化我們的測試
如果我們能將解決這個(gè)問題的方法更加推進(jìn)一步的話,這篇文章看起來才沒有缺憾。Springockito提供了另外一個(gè)名字叫作
Springockito Annotation的框架,它允許我們在測試類中使用注解來注入模擬類。繼續(xù)看下去之前,您最好先去網(wǎng)站上大概瞧瞧。好了,下面是經(jīng)過修改后的測試代碼。
UserBalanceServiceImplAnnotationTest.java的源代碼: @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = SpringockitoContextLoader.class, locations = "classpath:/springtest/springockito/applicationContext.xml") public class UserBalanceServiceImplAnnotationTest { @Autowired private UserBalanceService userBalanceService; @Autowired @ReplaceWithMock private BankService bankService; @Before public void setUp() throws Exception { Mockito.when(bankService.getBalanceByEmail("user@bank.com")).thenReturn(String.valueOf(valueOf(123.45D))); } @Test public void shouldReturnMockedBalance() { Double balance = userBalanceService.getAccountBalance("user@bank.com"); assertEquals(balance, valueOf(123.45D)); } }
請注意,這里并沒有新引入的XML配置文件,而是直接使用了正式環(huán)境的applicationContext.xml。我們使用@ReplaceWithMock這個(gè)注解標(biāo)記了類型為BankService的bean,而后在setUp方法中對模擬類的行為進(jìn)行了定義。
后記
Springockito-annotations項(xiàng)目有個(gè)巨大的優(yōu)點(diǎn),那就是,它使我們的測試代碼建立在依賴覆蓋的基礎(chǔ)之上,通過這樣,我們既不需要定義額外的XML配置文件,也不需要為了測試而去改動生產(chǎn)環(huán)境的配置文件。如果不使用Springockito-annotations的話,我們除了定義額外的XML配置文件別無他選了。因此,我強(qiáng)烈建議您在集成測試中使用Springockito-annotations,這樣你可以最大限度減少測試用例對生產(chǎn)代碼的影響,也能消除維護(hù)額外XML配置文件的負(fù)擔(dān)。
附言
為Spring工程寫集成測試真是簡單多了吧,文章中的代碼參考自我的GitHub。
譯文鏈接:http://www.codeceo.com/article/spring-test-is-easy.html
英文原文:Test Me If You Can #1 (Spring Framework)
翻譯作者:碼農(nóng)網(wǎng) – Sandbox Wang
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Spring MVC 框架搭建配置方法及詳解
- struts2+spring+hibernate分頁代碼[比較多]
- Spring中的事務(wù)管理實(shí)例詳解
- Spring事務(wù)管理只對出現(xiàn)運(yùn)行期異常進(jìn)行回滾
- SpringMVC文件上傳 多文件上傳實(shí)例
- 讀取spring配置文件的方法(spring讀取資源文件)
- Spring實(shí)現(xiàn)文件上傳(示例代碼)
- java Spring整合Freemarker的詳細(xì)步驟
- 在Spring中用select last_insert_id()時(shí)遇到問題
- spring快速入門實(shí)例教程
相關(guān)文章
詳解Mybatis中javaType和ofType的區(qū)別
本文主要介紹了詳解Mybatis中javaType和ofType的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05Java Socket聊天室編程(二)之利用socket實(shí)現(xiàn)單聊聊天室
這篇文章主要介紹了Java Socket聊天室編程(二)之利用socket實(shí)現(xiàn)單聊聊天室的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09Java設(shè)計(jì)模式之創(chuàng)建者模式簡介
這篇文章主要介紹了Java設(shè)計(jì)模式之創(chuàng)建者模式,需要的朋友可以參考下2014-07-07Spring Boot訪問靜態(tài)資源css/js,你真的懂了嗎
在搭建springboot時(shí)經(jīng)常需要在html中訪問一些靜態(tài)資源,很多朋友不清楚如何在 Spring Boot中訪問靜態(tài)資源,本文給大家?guī)韮煞N解決方案,感興趣的朋友跟隨小編一起看看吧2021-05-05JAVA通過HttpURLConnection 上傳和下載文件的方法
這篇文章主要介紹了JAVA通過HttpURLConnection 上傳和下載文件的方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-09-09