dubbo應用程序的單元測試環境搭建(springtest,powermock,mockito)


轉:http://blog.csdn.net/yys79/article/details/66472797

最近,項目中頻繁用到dubbo,而且java工程用引用了幾十個關聯系統的服務(如用戶認證,基礎服務,客戶平台)。這些服務都是dubbo服務,對我們僅提供了一個接口,服務通過zookeeper注冊,並給我們提供服務。我們的項目都是基於spring的。spring集成dubbo,就可以對這些外部服務進行注入和使用了。

    但是對於單元測試來說卻出現了難題:領域模型的測試不是問題,主要都是自己的代碼,加上一些mock就可以輕松測試;但是如果我想測試應用服務層(使用外部服務最多的地方),很多情況下就需要啟動spring環境,而這樣就需要加載外部系統的服務了。問題是外部的服務給我們的jar包中,只有服務的接口。啟動時如果按照正常開發環境的配置加載spring context,那么明顯是依賴了外部環境,如果沒有啟動zookeeper或者本機不聯網,抑或是關聯系統沒有啟動,spring context加載將會失敗,這是單元測試的忌諱。如果使用專門的單元測試的spring配置文件,去掉外部關聯系統的consumer配置,啟動會直接失敗,更別提測試了。

 還有寫其他問題,如測試靜態方法,私有方法;mock框架與springtest如何集成。spring的aop代理類如何mock一些默認的實現,測試數據庫如何選擇。總之問題超多。好吧,該進入正題了。

   1.測試靜態類,私有方法的問題

        簡單一句話,用powermock。powermock可以做到修改字節碼而改變類的行為,這不多說了,大家自己搜一下,官網上例子通俗易懂。目前我在maven中的關於powermock,mockito的依賴是這樣加入的:

   

[html]  view plain  copy
 
  1. <dependency>  
  2.                 <groupId>org.powermock</groupId>  
  3.                 <artifactId>powermock-api-mockito</artifactId>  
  4.                 <version>1.6.6</version>  
  5.             </dependency>  
  6.   
  7.             <dependency>  
  8.                 <groupId>org.mockito</groupId>  
  9.                 <artifactId>mockito-all</artifactId>  
  10.                 <version>1.10.19</version>  
  11.             </dependency>  
  12.             <dependency>  
  13.                 <groupId>org.powermock</groupId>  
  14.                 <artifactId>powermock-module-junit4</artifactId>  
  15.                 <version>1.6.6</version>  
  16.                 <scope>test</scope>  
  17.             </dependency>  
  18.             <dependency>  
  19.                 <groupId>org.powermock</groupId>  
  20.                 <artifactId>powermock-module-junit4-rule-agent</artifactId>  
  21.                 <version>1.6.6</version>  
  22.                 <scope>test</scope>  
  23.             </dependency>  
  24.             <dependency>  
  25.                 <groupId>org.powermock</groupId>  
  26.                 <artifactId>powermock-module-junit4-rule</artifactId>  
  27.                 <version>1.6.6</version>  
  28.                 <scope>test</scope>  
  29.             </dependency>  
  30.             <dependency>  
  31.                 <groupId>org.jacoco</groupId>  
  32.                 <artifactId>org.jacoco.agent</artifactId>  
  33.                 <classifier>runtime</classifier>  
  34.                 <version>0.7.9</version>  
  35.                 <scope>test</scope>  
  36.             </dependency>  

 

最后的話這個jacoco不是mock的依賴,是一個測試覆蓋率的插件。也推薦一下給大家用,哈哈。

 

    2.powermock與springtest配合使用的問題

        第一個問題解決了,不錯!第二個問題就來了。spring標准的Runner是SpringJUnit4ClassRunner,如果用這個Runner,那么powermock的@PrepairForTest就沒法使用了(也就是靜態mock,私有方法mock的關鍵),因此如果想使用靜態和私有方法mock就必須使用用Powemock的Runner,但是又如何啟動spring context呢?

     經過一些查找,終於解決了這個問題,方法就是用powermock的代理, 在測試類上加上這樣的注解:

     

[java]  view plain  copy
 
  1. @PowerMockIgnore({"java.lang.management.*","javax.management.*","javax.xml.*","org.xml.sax.*","org.apache.xerces.*","org.w3c.*"})  
  2. @RunWith(PowerMockRunner.class)  
  3. @PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)  
  4. @ContextConfiguration(locations = "classpath:META-INF/spring/test-spring.xml")  

 

 

Runner使用PowerMockRuner(就是RunWith注解的值);使用powermock提供的代理來使用SpringJUnit4ClassRunner;@PowerMockIgnore的作用是忽略一些powermock使用的classloader無法處理的類,不使用的話,啟動用例就會報錯。

 

[java]  view plain  copy
 
  1. classpath:META-INF/spring/test-spring.xml 是單元測試專門的spring配置文件,和域代碼使用的配置有些不同。這個文件我放在/test/resources/spring/目錄下。  

 

  到此,一個基於PowerMock,springtest和Mockito的基本配置就都弄完了。

上一篇說到powermock的配置,我一般在測試類中再加上繼承spring的測試類:extends AbstractTransactionalJUnit4SpringContextTests ,這樣就基本可以了。

再來說說上一篇中使用的spring配置文件。主要的不同就是test-spring.xml里面不會包含哪些引用外部服務的consumer,也就是剔除外部dubbo服務。

但是代碼里有很多注入外部服務的地方,這如何處理呢?這是第三個問題:

  3.注入外部的服務:

      開始我想了個很笨的方法:在test/文件夾下給外部服務的接口都提供一個空的實現類(implements 接口,然后用eclpse生成默認的方法實現)。這樣基本上就可以啟動了。但是實際使用中,由於外部服務接口也在不斷修改中,會出現不同環境的接口類不一至的情況。比如uat環境的jar包多了或一個方法(雖然我們的程序沒有直接使用),如此一來,我自己搞的空實現類就會報編譯錯誤了。

   后來想到了一個方法,在/test的代碼中增加一個普通的@Conponent注解的類,類里面使用@Bean注解標明所有外部類的生成方法

 

[java]  view plain  copy
 
  1. @Component  
  2. public class MockedOuterBeanFactory {  
  3.     @Bean  
  4.     public OuterService outerSerive(){  
  5.         return Mocktio.mock(OuterService.class);  
  6.     }  
  7. }  



 

  然后在測試類中注入這個MockedOuterBeanFactory,這樣測試環境的spring就可以完整的啟動了。外部的服務在啟動后都是Mocktio生成的代理類,所有方法都會返回默認值。

在實際測試中如何打樁呢?也很簡單。

           如果我測試一個自己寫的服務(如MyService),MyService又注入了OuterService(外部服務),那么利用spring Bean注入的單例這個特性就可以完成。在MyService的測試類中(MyServiceTest.java),同樣也注入OuterService,在執行MyService的方法之前對OuterService進行打樁。那么由於bean是單例的,MyServiceTest中注入的OuterService實例就是MyService注入的實例。這樣就輕松完成了打樁的工作。如果有特殊原因,main中配置的bean不是單例的,那么可以的話,在test-spring.xml中把它配置為單例的就可以。如果確實情況特殊不允許配置為單例方式,看下一篇吧。

 啟動后

 解決了spring啟動的問題,然后呢?數據庫

  4.測試數據庫的選擇

  有時候,我們需要測試持久化的內容,比如分頁查詢,不能說測試覆蓋了代碼就可以,還需要驗證查詢到的數據是否符合要求。參考了dbunits之類的東西,最后還是覺得之前使用的h2database是最好的選擇。它可以使用內存模式,不需要外部數據庫的依賴。這樣單元測試才能獨立運行。配置很簡單,

首先加入依賴:

 

[html]  view plain  copy
 
  1. <dependency>  
  2.             <groupId>com.h2database</groupId>  
  3.             <artifactId>h2</artifactId>  
[html]  view plain  copy
 
  1. <span style="white-space:pre">          </span><version>1.4.191</version>  
  2.         </dependency>  

至於版本,就自己找個最新的吧。

 

然后在數據源的地方使用如下配置(這也是測試環境spring配置不同於main配置的主要位置):

 

[html]  view plain  copy
 
  1. <bean id="dataSource" class="org.apache.tomcat.jdbc.pool.DataSource"  
  2.         destroy-method="close">  
  3.         <property name="poolProperties">  
  4.             <bean class="org.apache.tomcat.jdbc.pool.PoolProperties">  
  5.                 <property name="url" value="jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=1;MODE=MySQL"/>  
  6.                 <property name="driverClassName" value="org.h2.Driver" />  
  7.                 <property name="username" value="" />  
  8.                 <property name="password" value=""/>  
  9.                 <property name="validationQuery" value="SELECT 1"/>  
  10.                 <property name="maxActive" value="8" />  
  11.                 <property name="minIdle" value="1"/>  
  12.                 <property name="maxIdle" value="4" />  
  13.                 <property name="maxWait" value="10000"/>  
  14.                 <property name="initialSize" value="1"/>  
  15.             </bean>  
  16.         </property>  
  17.     </bean>  


注意:MODE=MySQL,這是讓h2模擬mysql庫,如果你使用其他類型的庫,一般也會有對應的Mode,主流數據庫都支持。注意mem項,意思是內存數據庫,這樣配置根本不會生成數據庫文件的,特別適合單元測試(依賴外部環境就不是標准單元測試了)。至於數據源類型,按自己的工程的配置就好,只要使用h2的url和driver就行,這里用的是tomcat數據源。

 

這些配置都做好后,就可以運行真正的powermock,mockito,springtest的單元測試了。下一篇說說怎么測試aop的類。

 

上兩篇中,基本環境和測試方式都說了一下。基本的測試否沒問題了。但是還有些問題需要解決。在我實際的開發中,最主要是是要做有Aop切面的Bean內部注入的bean打樁。

基本情況是:

 MyService是個接口,其實現類MyServiceImpl是@Transactional注解的Bean(這樣注入的MyService實例實際上就是代理了)

MyServiceImpl注了一個Bean:InnerBean,innerBean是自己工程中實現或其他服務都無所謂

測試中想使用mock替換這個InnerBean。

 

在spring中,aop用代理實現的。PowerMock不能修改其字節碼。而在測試中,我需要替換MyService代理中的InnerBean實例。開始傷透了腦筋啊。。。

如果不能打樁,那么必須老老實實的准備fixture才能測試,比如准備數據庫中多個表的數據,才能保證InnerBean完成我的預期結果(這種情況還算好的,有些情況都不能打樁)。

 

這個其實真是不難,只不過之前不太熟悉spring的測試框架(以前拋棄了spring,所以也不怎么研究)。

springtest有2個Utils類,可以幫助我們拿到MyService代理中的具體實現類:

 

[java]  view plain  copy
 
  1. org.springframework.test.util.AopTestUtils;  
  2. org.springframework.test.util.ReflectionTestUtils;  

 

[java]  view plain  copy
 
  1. MyServiceImpl impl = org.springframework.test.util.AopTestUtils.getTargetObject(MyServiceBean實例);  



 

這樣就可以拿到具體實現類了,再加一句impl.innerBean = mockInnerBean;就可以用自己打樁過的mock替換注入的innerBean實例了。如果多於一個測試方法,別忘了finally時候替換回來啊。

impl.innerBean 這里,我一般的注入bean都是是用package級別的,這樣便於測試,不必特別的依賴其他技術就可以替換實現。如果是private的,那么用ReflectionTestUtils吧,具體不用說了,簡單易用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM