dubbo应用程序的单元测试环境搭建(springtest,powermock,mockito)
轉(zhuǎn):http://blog.csdn.net/yys79/article/details/66472797
最近,項(xiàng)目中頻繁用到dubbo,而且java工程用引用了幾十個(gè)關(guān)聯(lián)系統(tǒng)的服務(wù)(如用戶認(rèn)證,基礎(chǔ)服務(wù),客戶平臺(tái))。這些服務(wù)都是dubbo服務(wù),對(duì)我們僅提供了一個(gè)接口,服務(wù)通過(guò)zookeeper注冊(cè),并給我們提供服務(wù)。我們的項(xiàng)目都是基于spring的。spring集成dubbo,就可以對(duì)這些外部服務(wù)進(jìn)行注入和使用了。
? ? 但是對(duì)于單元測(cè)試來(lái)說(shuō)卻出現(xiàn)了難題:領(lǐng)域模型的測(cè)試不是問(wèn)題,主要都是自己的代碼,加上一些mock就可以輕松測(cè)試;但是如果我想測(cè)試應(yīng)用服務(wù)層(使用外部服務(wù)最多的地方),很多情況下就需要啟動(dòng)spring環(huán)境,而這樣就需要加載外部系統(tǒng)的服務(wù)了。問(wèn)題是外部的服務(wù)給我們的jar包中,只有服務(wù)的接口。啟動(dòng)時(shí)如果按照正常開(kāi)發(fā)環(huán)境的配置加載spring context,那么明顯是依賴了外部環(huán)境,如果沒(méi)有啟動(dòng)zookeeper或者本機(jī)不聯(lián)網(wǎng),抑或是關(guān)聯(lián)系統(tǒng)沒(méi)有啟動(dòng),spring context加載將會(huì)失敗,這是單元測(cè)試的忌諱。如果使用專門(mén)的單元測(cè)試的spring配置文件,去掉外部關(guān)聯(lián)系統(tǒng)的consumer配置,啟動(dòng)會(huì)直接失敗,更別提測(cè)試了。
?還有寫(xiě)其他問(wèn)題,如測(cè)試靜態(tài)方法,私有方法;mock框架與springtest如何集成。spring的aop代理類如何mock一些默認(rèn)的實(shí)現(xiàn),測(cè)試數(shù)據(jù)庫(kù)如何選擇。總之問(wèn)題超多。好吧,該進(jìn)入正題了。
? ?1.測(cè)試靜態(tài)類,私有方法的問(wèn)題
? ? ? ? 簡(jiǎn)單一句話,用powermock。powermock可以做到修改字節(jié)碼而改變類的行為,這不多說(shuō)了,大家自己搜一下,官網(wǎng)上例子通俗易懂。目前我在maven中的關(guān)于powermock,mockito的依賴是這樣加入的:
? ?
[html]?view plain?copy?
最后的話這個(gè)jacoco不是mock的依賴,是一個(gè)測(cè)試覆蓋率的插件。也推薦一下給大家用,哈哈。
?
? ? 2.powermock與springtest配合使用的問(wèn)題
? ? ? ? 第一個(gè)問(wèn)題解決了,不錯(cuò)!第二個(gè)問(wèn)題就來(lái)了。spring標(biāo)準(zhǔn)的Runner是SpringJUnit4ClassRunner,如果用這個(gè)Runner,那么powermock的@PrepairForTest就沒(méi)法使用了(也就是靜態(tài)mock,私有方法mock的關(guān)鍵),因此如果想使用靜態(tài)和私有方法mock就必須使用用Powemock的Runner,但是又如何啟動(dòng)spring context呢?
? ? ?經(jīng)過(guò)一些查找,終于解決了這個(gè)問(wèn)題,方法就是用powermock的代理, 在測(cè)試類上加上這樣的注解:
? ? ?
[java]?view plain?copy?
?
Runner使用PowerMockRuner(就是RunWith注解的值);使用powermock提供的代理來(lái)使用SpringJUnit4ClassRunner;@PowerMockIgnore的作用是忽略一些powermock使用的classloader無(wú)法處理的類,不使用的話,啟動(dòng)用例就會(huì)報(bào)錯(cuò)。
?
[java]?view plain?copy?
? 到此,一個(gè)基于PowerMock,springtest和Mockito的基本配置就都弄完了。
上一篇說(shuō)到powermock的配置,我一般在測(cè)試類中再加上繼承spring的測(cè)試類:extends AbstractTransactionalJUnit4SpringContextTests ,這樣就基本可以了。
再來(lái)說(shuō)說(shuō)上一篇中使用的spring配置文件。主要的不同就是test-spring.xml里面不會(huì)包含哪些引用外部服務(wù)的consumer,也就是剔除外部dubbo服務(wù)。
但是代碼里有很多注入外部服務(wù)的地方,這如何處理呢?這是第三個(gè)問(wèn)題:
? 3.注入外部的服務(wù):
? ? ? 開(kāi)始我想了個(gè)很笨的方法:在test/文件夾下給外部服務(wù)的接口都提供一個(gè)空的實(shí)現(xiàn)類(implements 接口,然后用eclpse生成默認(rèn)的方法實(shí)現(xiàn))。這樣基本上就可以啟動(dòng)了。但是實(shí)際使用中,由于外部服務(wù)接口也在不斷修改中,會(huì)出現(xiàn)不同環(huán)境的接口類不一至的情況。比如uat環(huán)境的jar包多了或一個(gè)方法(雖然我們的程序沒(méi)有直接使用),如此一來(lái),我自己搞的空實(shí)現(xiàn)類就會(huì)報(bào)編譯錯(cuò)誤了。
? ?后來(lái)想到了一個(gè)方法,在/test的代碼中增加一個(gè)普通的@Conponent注解的類,類里面使用@Bean注解標(biāo)明所有外部類的生成方法
?
[java]?view plain?copy?
? 然后在測(cè)試類中注入這個(gè)MockedOuterBeanFactory,這樣測(cè)試環(huán)境的spring就可以完整的啟動(dòng)了。外部的服務(wù)在啟動(dòng)后都是Mocktio生成的代理類,所有方法都會(huì)返回默認(rèn)值。
在實(shí)際測(cè)試中如何打樁呢?也很簡(jiǎn)單。
? ? ? ? ? ?如果我測(cè)試一個(gè)自己寫(xiě)的服務(wù)(如MyService),MyService又注入了OuterService(外部服務(wù)),那么利用spring Bean注入的單例這個(gè)特性就可以完成。在MyService的測(cè)試類中(MyServiceTest.java),同樣也注入OuterService,在執(zhí)行MyService的方法之前對(duì)OuterService進(jìn)行打樁。那么由于bean是單例的,MyServiceTest中注入的OuterService實(shí)例就是MyService注入的實(shí)例。這樣就輕松完成了打樁的工作。如果有特殊原因,main中配置的bean不是單例的,那么可以的話,在test-spring.xml中把它配置為單例的就可以。如果確實(shí)情況特殊不允許配置為單例方式,看下一篇吧。
?啟動(dòng)后
?解決了spring啟動(dòng)的問(wèn)題,然后呢?數(shù)據(jù)庫(kù)
? 4.測(cè)試數(shù)據(jù)庫(kù)的選擇
? 有時(shí)候,我們需要測(cè)試持久化的內(nèi)容,比如分頁(yè)查詢,不能說(shuō)測(cè)試覆蓋了代碼就可以,還需要驗(yàn)證查詢到的數(shù)據(jù)是否符合要求。參考了dbunits之類的東西,最后還是覺(jué)得之前使用的h2database是最好的選擇。它可以使用內(nèi)存模式,不需要外部數(shù)據(jù)庫(kù)的依賴。這樣單元測(cè)試才能獨(dú)立運(yùn)行。配置很簡(jiǎn)單,
首先加入依賴:
?
[html]?view plain?copy至于版本,就自己找個(gè)最新的吧。
?
然后在數(shù)據(jù)源的地方使用如下配置(這也是測(cè)試環(huán)境spring配置不同于main配置的主要位置):
?
[html]?view plain?copy
注意:MODE=MySQL,這是讓h2模擬mysql庫(kù),如果你使用其他類型的庫(kù),一般也會(huì)有對(duì)應(yīng)的Mode,主流數(shù)據(jù)庫(kù)都支持。注意mem項(xiàng),意思是內(nèi)存數(shù)據(jù)庫(kù),這樣配置根本不會(huì)生成數(shù)據(jù)庫(kù)文件的,特別適合單元測(cè)試(依賴外部環(huán)境就不是標(biāo)準(zhǔn)單元測(cè)試了)。至于數(shù)據(jù)源類型,按自己的工程的配置就好,只要使用h2的url和driver就行,這里用的是tomcat數(shù)據(jù)源。
?
這些配置都做好后,就可以運(yùn)行真正的powermock,mockito,springtest的單元測(cè)試了。下一篇說(shuō)說(shuō)怎么測(cè)試aop的類。
?
上兩篇中,基本環(huán)境和測(cè)試方式都說(shuō)了一下。基本的測(cè)試否沒(méi)問(wèn)題了。但是還有些問(wèn)題需要解決。在我實(shí)際的開(kāi)發(fā)中,最主要是是要做有Aop切面的Bean內(nèi)部注入的bean打樁。
基本情況是:
?MyService是個(gè)接口,其實(shí)現(xiàn)類MyServiceImpl是@Transactional注解的Bean(這樣注入的MyService實(shí)例實(shí)際上就是代理了)
MyServiceImpl注了一個(gè)Bean:InnerBean,innerBean是自己工程中實(shí)現(xiàn)或其他服務(wù)都無(wú)所謂
測(cè)試中想使用mock替換這個(gè)InnerBean。
?
在spring中,aop用代理實(shí)現(xiàn)的。PowerMock不能修改其字節(jié)碼。而在測(cè)試中,我需要替換MyService代理中的InnerBean實(shí)例。開(kāi)始傷透了腦筋啊。。。
如果不能打樁,那么必須老老實(shí)實(shí)的準(zhǔn)備fixture才能測(cè)試,比如準(zhǔn)備數(shù)據(jù)庫(kù)中多個(gè)表的數(shù)據(jù),才能保證InnerBean完成我的預(yù)期結(jié)果(這種情況還算好的,有些情況都不能打樁)。
?
這個(gè)其實(shí)真是不難,只不過(guò)之前不太熟悉spring的測(cè)試框架(以前拋棄了spring,所以也不怎么研究)。
springtest有2個(gè)Utils類,可以幫助我們拿到MyService代理中的具體實(shí)現(xiàn)類:
?
[java]?view plain?copy?
[java]?view plain?copy
?
這樣就可以拿到具體實(shí)現(xiàn)類了,再加一句impl.innerBean = mockInnerBean;就可以用自己打樁過(guò)的mock替換注入的innerBean實(shí)例了。如果多于一個(gè)測(cè)試方法,別忘了finally時(shí)候替換回來(lái)啊。
impl.innerBean 這里,我一般的注入bean都是是用package級(jí)別的,這樣便于測(cè)試,不必特別的依賴其他技術(shù)就可以替換實(shí)現(xiàn)。如果是private的,那么用ReflectionTestUtils吧,具體不用說(shuō)了,簡(jiǎn)單易用。
總結(jié)
以上是生活随笔為你收集整理的dubbo应用程序的单元测试环境搭建(springtest,powermock,mockito)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 一步步开始集中管理[为企业部署Windo
- 下一篇: linux 下实现ssh免密钥登录