熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> JSP教程 >> 正文

使用MOCK對象進行單元測試

2013-11-15 09:50:54  來源: JSP教程 

  出了什麼問題?
      單元測試的目標是一次只驗證一個方法小步的前進細粒度的測試但是假如某個方法依賴於其他一些難以操控的東東比如說網絡連接數據庫連接或者是Servlet容器那麼我們該怎麼辦呢?
      要是你的測試依賴於系統的其他部分甚至是系統的多個其他部分呢?在這種情況下倘若不小心你最終可能會發現自己幾乎初始化了系統的每個組件而這只是為了給一個測試創造足夠的運行環境讓它們可以運行起來忙乎了大半天看上去我們好像有點違背了測試的初衷了這樣不僅僅消耗時間還給測試過程引入了大量的耦合因素比如說可能有人興致沖沖地改變了一個接口或者數據庫的一張表突然你那卑微的單元測試的神秘的掛掉了在這種情況發生幾次之後即使是最有耐心的開發者也會洩氣甚至最終放棄所有的測試那樣的話後果就不能想像了
   再讓我們看一個更加具體的情況在實際的面向對象軟件設計中我們經常會碰到這樣的情況我們在對現實對象進行構建之後對象之間是通過一系列的接口來實現這在面向對象設計裡是最自然不過的事情了但是隨著軟件測試需求的發展這會產生一些小問題舉個例子用戶A現在拿到一個用戶B提供的接口他根據這個接口實現了自己的需求但是用戶A編譯自己的代碼後想簡單模擬測試一下怎麼辦呢?這點也是很現實的一個問題我們是否可以針對這個接口來簡單實現一個代理類來測試模擬期望代碼生成自己的結果呢?
      幸運的是有一種測試模式可以幫助我們mock對象Mock對象也就是真實對象在調試期的替代品

  現在需要Mock對象嗎?
      關於什麼時候需要Mock對象Tim Mackinnon給我們了一些建議
         真實對象具有不可確定的行為(產生不可預測的結果如股票的行情)
        真實對象很難被創建(比如具體的web容器)
        真實對象的某些行為很難觸發(比如網絡錯誤)
         真實情況令程序的運行速度很慢
         真實對象有用戶界面
        測試需要詢問真實對象它是如何被調用的(比如測試可能需要驗證某個回調函數是否被調用了)
        真實對象實際上並不存在(當需要和其他開發小組或者新的硬件系統打交道的時候這是一個普遍的問題)
如何實現Mock對象?
      使用mock對象進行測試的時候我們總共需要個步驟分別是
       使用一個接口來描述這個對象
       為產品代碼實現這個接口
       以測試為目的在mock對象中實現這個接口
      在此我們又一次看到了針對接口編程的重要性了因為被測試的代碼只會通過接口來引用對象所以它完全可以不知道它引用的究竟是真實的對象還是mock對象下面看一個實際的例子一個鬧鐘根據時間來進行提醒服務如果過了下午點鐘就播放音頻文件提醒大家下班了如果我們要利用真實的對象來測試的話就只能苦苦等到下午五點然後把耳朵放在音箱旁我們可不想這麼笨我們應該利用mock對象來進行測試這樣我們就可以模擬控制時間了而不用苦苦等待時鐘轉到下午點鐘了下面是代碼

   public interface Environmental {   
private boolean playedWav = false;   
public long getTime();   
public void playWavFile(String fileName);   
public boolean wavWasPlayed();   
public void resetWav();   
}    

  真實的實現代碼

   public class SystemEnvironment implements Environmental {   
public long getTime() {   
return SystemcurrentTimeMillis();   
      }   
public void playWavFile(String fileName) {   
         playedWav = true;   
      }   
public boolean wavWasPlayed() {   
return playedWav;   
      }   
public void resetWav() {   
         playedWav = false;   
      }   
}    

  下面是mock對象

   public class MockSystemEnvironment implements Environmental {   
private long currentTime;   
public long getTime() {   
return currentTime;   
      }   
public void setTime(long currentTime) {   
thiscurrentTime = currentTime;   
      }   
public void playWavFile(String fileName) {   
         playedWav = true;   
      }   
public boolean wavWasPlayed() {   
return playedWav;   
      }   
public void resetWav() {   
         playedWav = false;   
      }   
}    

  下面是一個調用getTime的具體類


   import javautilCalendar;   
 
public class Checker {   
private Environmental env;   
public Checker(Environmental env) {   
thisenv = env;   
      }   
public void reminder() {   
         Calendar cal = CalendargetInstance();   
         calsetTimeInMills(envgetTime());   
int hour = calget(CalendarHOUR_OF_DAY);   
if(hour >= ) {   
            envplayWavFile(quit_whistlewav);   
         }   
      }   
}   

  使用envgetTime()的被測代碼並不知道測試環境和真實環境之間的區別因為它們都實現了相同的接口現在你可以借助mock對象通過把時間設置為已知值並檢查行為是否如預期那樣來編寫測試

  

  import javautilCalendar;   
import junitframeworkTestCase;   
 
public class TestChecker extends TestCase {   
public void testQuittingTime() {   
            MockSystemEnvironment env = new MockSystemEnvironment();   
            Calendar cal = CalendargetInstance();   
            calset(CalendarYEAR );   
            calset(CalendarMONTH );   
            calset(CalendarDAY_OF_MONTH);   
            calset(CalendarHOUR_OF_DAY );  

  calset(CalendarMINUTE );   
long t = calgetTimeInMillis();   
            envsetTime(t);   
            Checker checker = new Checker(env);   
            checkerreminder();   
            assertFalse(envwavWasPlayed());   
             t += (**);   
            envsetTime(t);   
            checkerreminder();   
            assertTrue(envwavWasPlayed());   
            envresetWav();   
            t += ***;   
            envsetTime(t);   
            checkerreminder();   
           assertTrue(envwavWasPlayed());   
      }   
}  

 

  這就是mock對象的全部偽裝出真實世界的某些行為使你可以集中精力測試好自己的代碼

  好像有一些麻煩
      如果每次都像上面那樣自己寫具體的mock對象問題雖然解決了但是好像有一些麻煩不要著急已經有一些第三方現成的mock對象供我們使用了使用Mock Object進行測試主要是用來模擬那些在應用中不容易構造(如HttpServletRequest必須在Servlet容器中才能構造出來)或者比較復雜的對象(如JDBC中的ResultSet對象)從而使測試順利進行的工具目前在Java陣營中主要的Mock測試工具有JMockMockCreatorMockrunnerEasyMockMockMaker等在微軟的Net陣營中主要是NmockNetMock等
      下面就以利用EasyMock模擬測試Servlet組件為例代碼如下      編譯並將其當做一個Test Case運行會發現兩個測試方法均測試成功我們可以看到easymock已經幫助我們實現了一些servlet組件的mock對象這樣我們就可以擺脫web容器和servlet容器來輕松的測試servlet了

   import orgeasymock*;   
import junitframework*;   
import javaxservlethttp*;   
 
public class MockRequestTest extends TestCase{   
private MockControl control;   
private HttpServletRequest mockRequest;   
public void testMockRequest(){   
//創建一個Mock HttpServletRequest的MockControl對象
         control = MockControlcreateControl(HttpServletRequestclass);     
//獲取一個Mock HttpServletRequest對象
         mockRequest = (HttpServletRequest) controlgetMock();   
//設置期望調用的Mock HttpServletRequest對象的方法
         mockRequestgetParameter(name);   
//設置調用方法期望的返回值並指定調用次數
//以下後兩個參數表示最少調用一次最多調用一次
         controlsetReturnValue(kongxx );          
//設置Mock HttpServletRequest的狀態
//表示此Mock HttpServletRequest對象可以被使用
         controlreplay();   
//使用斷言檢查調用
         assertEquals(kongxxmockRequestgetParameter(name));   
//驗證期望的調用
         controlverify();   
     }   
}   

  編譯並將其當做一個Test Case運行會發現兩個測試方法均測試成功我們可以看到easymock已經幫助我們實現了一些servlet組件的mock對象這樣我們就可以擺脫web容器和servlet容器來輕松的測試servlet了

  底層技術是什麼?
      讓我們來回憶一下如果用戶使用C++和java的程序的生成C++在最後的階段還需要連接才能生成一個整體程序這在靈活性與java源代碼的機制是不能比的java的各個類是獨立的打包的那些類也是獨立的只有在加載進去才進行連接這在代碼被加載進去的時候我們還可以執行很多的動作如插入一些相關的業務需求這也是AOP的一個焦點javassit代碼庫的實現類似於這正是利用這些所以用java實現Mock對象是很簡單的

  一些相關的資源
      MockObject的主頁  ;介紹了關鍵Mock Object的基本概念和目前在各個環境下主要的Mock測試工具
      JMock的主頁;可以獲取JMock的最新代碼和開發包以及一些說明文檔
      EasyMock的主頁;可以獲取JMock的最新代碼和開發包以及一些說明文檔
      NMock的主頁;介紹了在Microsoft Net平台上進行Mock測試的開發工具


From:http://tw.wingwit.com/Article/program/Java/JSP/201311/19222.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.