我的漫漫程序之旅

          專注于JavaWeb開發(fā)
          隨筆 - 39, 文章 - 310, 評論 - 411, 引用 - 0
          數(shù)據(jù)加載中……

          EasyMock使用手記

          Mock 對象能夠模擬領(lǐng)域?qū)ο蟮牟糠中袨椋⑶夷軌驒z驗(yàn)運(yùn)行結(jié)果是否和預(yù)期的一致。領(lǐng)域類將通過與 Mock 對象的交互,來獲得一個獨(dú)立的測試環(huán)境(引自《 精通 Spring——Java 輕量級架構(gòu)開發(fā)實(shí)踐 》。

          在模仿對象中,我們定義了四個概念:

          1 )目標(biāo)對象:正在測試的對象

          2 )合作者對象:由目標(biāo)對象創(chuàng)建或獲取的對象

          3 )模仿對象:遵循模仿對象模式的合作者的子類(或?qū)崿F(xiàn))

          4 )特殊化對象:覆蓋創(chuàng)建方法以返回模仿對象而不是合作者的目標(biāo)的子類

          一般來說,應(yīng)用模仿對象的過程如下:

          1 )創(chuàng)建模仿對象的實(shí)例

          2 )設(shè)置模仿對象中的狀態(tài)和期望值

          3 )將模仿對象作為參數(shù)來調(diào)用域代碼

          4 )驗(yàn)證模仿對象中的一致性

          那么,我們應(yīng)該如何以及在哪里使用 Mock 對象呢?一般來說,對于目標(biāo)對象中的合作者對象,在測試時如果其狀態(tài)或行為的實(shí)現(xiàn)嚴(yán)重地依賴外部資源(比如數(shù)據(jù)持久化中的 DAO ,比如負(fù)責(zé)發(fā)送電子郵件的類),或者團(tuán)隊并行開發(fā)時,目標(biāo)對象的合作者對象并沒有實(shí)現(xiàn)(比如 J2EE 中,橫向分工時,負(fù)責(zé) Action 的調(diào)用 Service ,負(fù)責(zé) Service 調(diào)用 DAO 時,相應(yīng)的 Service DAO 沒有實(shí)現(xiàn)),這時我們就需要模仿這些類。其實(shí),在做 J2EE 時,傳統(tǒng)的 N 層架構(gòu)中,我們都是面向接口編程的,我們定義了 DAO 接口,我們定義了 Service 接口,這樣做的優(yōu)點(diǎn)就是我們在測試時可以構(gòu)造實(shí)現(xiàn)接口的 Mock 類。這里不得不提依賴注入,通過依賴注入,我們才能在測試時 set Mock 對象。這也說明,為了方便測試,我們不得不一步一步 重構(gòu)代碼,而模式就在重構(gòu)中自然地產(chǎn)生了。

          對于 Mock 對象,我們可以根據(jù) 合作者接口(或者是類) 實(shí)現(xiàn)具體的 Mock 類,這樣的 Mock 類實(shí)際上是 Stub 。有些情況下, Stub 是必要的。但對于諸如 DAO Service ,我們只關(guān)心在給定參數(shù)的情況下,調(diào)用的方法能夠返回預(yù)期的值,我們根本不關(guān)心其內(nèi)部實(shí)現(xiàn),這時候如果使用 Stub 的話就會產(chǎn)生不必要的代碼。我們需要的就是能 動態(tài)地生成 Mock 對象而不需要編寫它們的工具, EasyMock 就是這樣的工具。

          EasyMock 是一個 Mock 對象的類庫,現(xiàn)在的版本是 2.0 ,這個版本只支持 Mock 接口,如果需要 Mock 類,需要下載它的擴(kuò)展包。

          下面通過一個具體的例子說明一下 Mock 對象的使用,我寫的例子就是測試 Service 類中的一個方法, Mock 的對象是 DAO

          首先是一個簡單的實(shí)體 bean Account:

          package  easymocktest.domain;

          import
           org.apache.commons.lang.builder.ToStringBuilder;
          import
           org.apache.commons.lang.builder.ToStringStyle;

          public   class  Account  {

              
          private  Long id;
              
          private  String name;
              
          private  String pwd;
              
              
          public  Long getId()  {
                  
          return  id;
              }

              
          public   void  setId(Long id)  {
                  
          this .id  =  id;
              }

              
          public  String getName()  {
                  
          return  name;
              }

              
          public   void  setName(String name)  {
                  
          this .name  =  name;
              }

              
          public  String getPwd()  {
                  
          return  pwd;
              }

              
          public   void  setPwd(String pwd)  {
                  
          this .pwd  =  pwd;
              }

              
          /**
               * 
          @see  java.lang.Object#toString()
               
          */

              
          public  String toString()  {
                  
          return   new  ToStringBuilder( this , ToStringStyle.MULTI_LINE_STYLE)
                          .append(
          " name " this .name).append( " pwd " this .pwd).append( " id " ,
                                  
          this .id).toString();
              }

              
          }

                

              一個只含一個方法的 DAO AccountDAO:

          package  easymocktest.dao;

          import  easymocktest.domain. *
          ;
          public   interface  AccountDAO  {
              
          public  Account getByNameAndPwd(String name,String pwd);
          }

              

           

             一個同樣只含一個方法的 AccountService 接口:

           

          package  easymocktest.service;

          import  easymocktest.domain. *
          ;
          public   interface  AccountService  {
              
          public  Account getAccount(String name,String pwd);
          }

             與 AccountService 相應(yīng)的實(shí)現(xiàn)類:

          package  easymocktest.service.impl;

          import
           easymocktest.service.AccountService;
          import  easymocktest.dao. *
          ;
          import
           easymocktest.domain.Account;
          public   class  AccountServiceImpl  implements  AccountService  {

              
          private  AccountDAO accountDAO;

              
          public  AccountDAO getAccountDAO()  {
                  
          return  accountDAO;
              }


              
          public   void  setAccountDAO(AccountDAO accountDAO)  {
                  
          this .accountDAO  =  accountDAO;
              }


              
          public  Account getAccount(String name, String pwd)  {
                  
          return   this .accountDAO.getByNameAndPwd(name, pwd);
              }


          }


             這里我沒有實(shí)現(xiàn) AccountDAO 接口,對于 Mock 測試來說,這也是不需要的。下面就是 AccountServiceImpl 的測試類 AccountServiceTest
             

          package  easymocktest.service;

          import  junit.framework. *
          ;
          import  easymocktest.dao. *
          ;
          import  easymocktest.domain. *
          ;
          import  easymocktest.service.impl. *
          ;
          import   static
           org.easymock.EasyMock.createMock;
          import   static
           org.easymock.EasyMock.replay;
          import   static
           org.easymock.EasyMock.reset;
          import   static
           org.easymock.EasyMock.verify;
          import   static
           org.easymock.EasyMock.expect;
          public   class  AccountServiceTest  extends  TestCase {

              
          private  AccountDAO accountDAOMock;
              
          private  AccountServiceImpl accountService;
              
              @Override
              
          protected   void  setUp()  throws  Exception  {
                  accountDAOMock 
          =  createMock(AccountDAO. class );
                  accountService 
          =   new  AccountServiceImpl();
                  accountService.setAccountDAO(accountDAOMock);
              }

              
              
          public   void  testGetAccount() {
                  String name 
          =   " kafka " ;
                  String pwd 
          =   " 0102 " ;
                  Account a 
          =   new  Account();
                  a.setName(name);
                  a.setPwd(pwd);
                  a.setId(
          new  Long( 10 ));
                  reset(accountDAOMock);
          // (a)
                  expect(accountDAOMock.getByNameAndPwd(name, pwd)).andReturn(a); // (b)
                  replay(accountDAOMock); // (c)
                  Account b  =  accountService.getAccount(name, pwd);
                  assertEquals(a, b);
                  verify(accountDAOMock);
          // (d)
              }

          }

             

          下面簡要的說明一下 Mock 對象的工作過程:

          1 )在 setUp() 中,通過 “accountDAOMock = createMock(AccountDAO.class);” (這里使用了 java5 中的靜態(tài)導(dǎo)入),創(chuàng)建 AccountDAO Mock 對象,由于 EasyMock 采用了范型技術(shù),故創(chuàng)建的 Mock 對象不需要強(qiáng)制類型轉(zhuǎn)換。然后通過 “accountService.setAccountDAO(accountDAOMock);” 設(shè)置目標(biāo)對象的合作者對象。

          2 )對于測試方法 “testGetAccount()” (a) 處的 reset() 方法是將 Mock 對象復(fù)位,也就是重新設(shè)置 Mock 對象的狀態(tài)和行為。由于此處是第一次調(diào)用 Mock 對象,可以不必使用 reset() 方法。

          3 (b) expect() 是錄制 Mock 對象方法的調(diào)用,其參數(shù)就是 Mock 對象的方法,其中如果調(diào)用的方法有返回值,要通過 andReturn() 方法設(shè)置預(yù)期的返回值。

          4 (c) 處的 replay() 是結(jié)束錄制過程。 在調(diào)用 replay() 方法之前的狀態(tài), EashMock 稱之為 “record 狀態(tài)。該狀態(tài)下, Mock 對象不具備行為(即模擬接口的實(shí)現(xiàn)),它僅僅記錄方法的調(diào)用。在調(diào)用 replay() 后,它才以 Mock 對象預(yù)期的行為進(jìn)行工作,檢查預(yù)期的方法調(diào)用是否真的完成。

          5 (d) 處的 verify() 是用于在錄制和回放兩個步驟完成之后進(jìn)行預(yù)期和實(shí)際結(jié)果的檢查。這里就是檢查 accountDAOMock 是否如預(yù)期一樣調(diào)用了 getByNameAndPwd 方法。

           

          對于上面的舉例,它可能并不具有實(shí)際的價值,這里我只想拋磚引玉。在 N 層架構(gòu)的 Java 程序中, Mock 對象在單元測試中正發(fā)揮著越來越重要的作用。我現(xiàn)在看到的是,在 Service 層與 Web 層, Mock 對象能很好的被應(yīng)用。有人覺得在 Persistence 層也應(yīng)該使用 Mock 對象,但就像我們所知道的,在使用 Hibernate Ibatis ORM 工具的情況下,我們的 Persistence 層的測試主要測試的就是那些配置文件、查詢語句等(實(shí)際上是集成測試),如果還 Mock 的話,就失去了測試的意義。

             對于 Mock 的更多的信息,你可以訪問Mock Objects,在本文寫作的過程中,參考了使用模仿對象進(jìn)行單元測試

          等文章,一并感謝。



          posted on 2007-11-24 07:22 々上善若水々 閱讀(1728) 評論(0)  編輯  收藏 所屬分類: 軟件測試

          主站蜘蛛池模板: 明溪县| 柘城县| 清水河县| 罗田县| 沐川县| 阳曲县| 临沭县| 南涧| 兴仁县| 公主岭市| 南阳市| 三原县| 凤冈县| 天柱县| 鄂尔多斯市| 舒城县| 兰溪市| 虹口区| 东乌珠穆沁旗| 恩平市| 新宁县| 崇仁县| 青田县| 元江| 德格县| 来安县| 洛川县| 安溪县| 铜梁县| 虎林市| 紫金县| 运城市| 康马县| 天长市| 茂名市| 广安市| 建水县| 巴青县| 浪卡子县| 佛教| 辽阳县|