1. 程式人生 > >單元測試mock框架——jmockit實戰

單元測試mock框架——jmockit實戰

轉載地址:http://blog.csdn.net/ultrani/article/details/8993364

JMockit是google code上面的一個java單元測試mock專案,她很方便地讓你對單元測試中的final類,

JMockit的測試方式可以通過下面2個途徑實現

一.根據用例的測試路徑,測試程式碼內部邏輯

        對於這種情景,可以使用jmockit的基於行為的mock方式。在這種方式中,目的是測試單元測試及其依賴程式碼的呼叫過程,驗證程式碼邏輯是否滿足測試路徑。  由於被依賴程式碼可能在自己單測中已測試過,或者難以測試,就需要把這些被依賴程式碼的邏輯用預定期待的行為替換掉,也就是mock掉,從而把待測是程式碼隔離開,這也是單元測試的初衷。 這種方式和白盒測試接近。

二.根據測試用例的輸入輸出資料,測試程式碼是否功能執行正常。

        對於這種情景,可以使用jmockit基於狀態的mock方式。目的是從被測程式碼的使用角度出發,結合資料的輸入輸出來檢驗程式執行的這個正確性。使用這個方式,需要把被依賴的程式碼mock掉,實際上相當於改變了被依賴的程式碼的邏輯。通常在整合測試中,如果有難以呼叫的外部介面,就通過這個方式mock掉,模擬外部介面。 這種方式有點像黑盒測試。

下面根據一個簡單例子簡單介紹JMockit的幾個常用測試場景和使用方法。

       被測試類:一個猜骰子點數的類。new Guess(int n)時候指定最大猜數次數,並且生成實際點數。在n次猜測內猜中則輸出成功,n次猜測失敗後通過failHandle()輸出錯誤。結果輸出通過GuessDAO來儲存。但GuessDAO還沒實現。

  1. /** 在n次機會隨機猜骰子點數 ,結果儲存到資料庫中 */
  2.     publicclass Guess {  
  3.         privateint maxTryTime;                         // 最大重試次數
  4.         privateint tryTime = 0;                        // 當前重試次數
  5.         privateint number = (int) (Math.random() * 6); // 目標數字
  6.         private GuessDAO guessDAO;                      // 持久化依賴
  7.         public Guess(int maxRetryTime) {               
  8.             this.maxTryTime = maxRetryTime;  
  9.         }  
  10.         publicvoid doit() {  
  11.             while (tryTime++ < maxTryTime && !tryIt()) {  
  12.                 // 到達最大嘗試次數仍不成功則呼叫handle
  13.                 if (tryTime == maxTryTime) {  
  14.                     failHandle();  
  15.                 }  
  16.             }  
  17.         }  
  18.         publicboolean tryIt() {                        // 最壞情況下呼叫maxRetryTime次tryIt(),猜中則儲存資訊
  19.             if (number == randomGuess()) {  
  20.                 guessDAO.saveResult(true, number);  
  21.                 returntrue;  
  22.             }  
  23.             returnfalse;  
  24.         }  
  25.         publicvoid failHandle() {                      // 失敗處理,猜不中時呼叫
  26.             guessDAO.saveResult(false, number);  
  27.         }  
  28.         privateint randomGuess(){                      // 猜的隨機過程
  29.             return (int) (Math.random() * 6);  
  30.         }  
  31.         publicvoid setGuessDAO(GuessDAO guessDAO) {  
  32.             this.guessDAO = guessDAO;  
  33.         }  
  34.     }  

下面通過3個測試用例來說明如何使用jmockit

以下程式碼基於jmockit1.0 左右版本,新版去廢除了一些功能(如@Mocked不能修飾成員)

1. 測試當沒有一次猜中時,程式碼邏輯如何執行。

先上測試程式碼:

  1. publicclass GuessTest {  
  2.     @Tested// 表明被修飾例項是將會被自動構建和注入的例項
  3.     Guess guess = new Guess(3);  
  4.     @Injectable// 表明被修飾例項將會自動注入到@Tested修飾的例項中,並且會自動mock掉,除非在測試前被賦值
  5.     GuessDAO guessDAO;  
  6.     /** 
  7.      * 連續3次失敗 
  8.      */
  9.     @Test
  10.     publicvoid behaviorTest_fail3time() {  
  11.         new Expectations() {        // Expectations中包含的內部類區塊中,體現的是一個錄製被測類的邏輯。
  12.             @Mocked(methods="tryIt")  // 表明被修飾的類對tryIt()方法進行mock。
  13.             Guess g;  
  14.             {  
  15.                 g.tryIt();             // 期待呼叫Guess.tryIt()方法
  16.                 result = false;        // mock掉返回值為false(表明猜不中)
  17.                 times = 3;             // 期待以上過程重複3次
  18.                 guessDAO.saveResult(false, anyInt); // 期待呼叫guessDAO把猜失敗的結果儲存
  19.             }  
  20.         };  
  21.         guess.doit();               // 錄製完成後,進行實際的程式碼呼叫,也稱回放(replay)
  22.     }  
  23. }  

說明下這個測試程式碼的目的: 測試行為是guess.doit(),程式碼期望在呼叫doit()函式後,會發生:
        1.呼叫tryIt,並把結果mock為false;
        2.重複第一步3次;
        3.把結果通過guessDAO儲存。即呼叫3次均猜錯數字

        可以看出,JMockit在基於行為的測試中,體現3個步驟。第一個是指令碼錄製,也就是把期望的行為記錄下來。在上面例子中,在Expectation內部類的區塊中的程式碼就是期待發生的行為。第二是回放,也就是guess.doit()觸發的過程。第三是檢驗,在這裡沒有確切體現出,但是的確發生著檢驗:假設doit方法呼叫後,程式碼的邏輯沒有符合錄製過程中的指令碼的行為,那麼測試結果失敗(其實Jmockit有專門的Verifications做檢驗,但是這裡Expecation已經包含了這個功能,如果用NonStrictExpecation就需要有檢驗塊)。

        再介紹下這段程式碼中用到的各個JMockit元素(結論源自文件及自己程式碼測試):

@Tested和@Injectable: 對@Tested物件判斷是否為null,是則通過合適構造器初始化,並實現依賴注入。呼叫構造方法時,會嘗試使用@Injectable的欄位進行構造器注入。普通注入時,@Injectable欄位如果沒有在測試方法前被賦值,其行為將會被mock成預設值(靜態方法和建構函式不會被mock掉)。Injectable最大作用除了注入,還有就是mock的範圍只限當前註釋例項。一句話:@Injectable的例項會自動注入到@Tested中,如果沒初始賦值,那麼JMockit將會以相應規則初始化。

@Mocked:@Mocked修飾的例項,將會把例項對應類的所有例項的所有行為都mock掉(無論構造方法,還是private,protected方法,夠霸氣吧)。在Expectation區塊中,宣告的成員變數均預設帶有@Mocked,但是本例沒有省略,是因為@Mocked會mock掉所有方法,而回放的程式碼中doit函式我們是不希望它也被mock,所以通過method="tryIt"來設定被mock的類只對tryIt方法進行mock。

Expectations:這是錄製期望發生行為的地方。result和times都是其內定成員變數。result可以重定義錄製行為的返回值甚至通過Delegate來重定義行為,times是期待錄製行為的發生次數。在Expectations中發生的呼叫,均會被mock。由於沒定義result,所以guessDAO.saveResult()呼叫的結果返回空。

2. 當多次失敗後,最後一次猜數成功時,程式碼邏輯如何執行。

在上面的測試程式碼中,加多一個測試方法:

  1. /** 
  2.   * 兩次失敗,第三次猜數成功 
  3.   */
  4.  @Test
  5.  publicvoid behaviorTest_sucecess() {  
  6.      new Expectations(Guess.class) {                          // 建構函式可以傳入Class或Instance例項
  7.          {     
  8.              guess.tryIt();  
  9.              result = false;  
  10.              times=2;  
  11.              invoke(guess, "randomGuess"new Object[]{});    // invoke()能呼叫私有方法
  12.              result = (Integer)getField(guess, "number");     // getField()能操作私有成員
  13.              guessDAO.saveResult(true, (Integer)getField(guess, "number"));  
  14.          }  
  15.      };  
  16.      guess.doit();  
  17.  }  

        第二個測試用例是期待先猜2次失敗,第3次猜中。

        所以錄製中會先呼叫2次tryIt並返回false,在發生第3次呼叫時,通過invoke呼叫私有方法randomGuess,並期待其返回被測例項的私有成員number,通過這種作弊的方式,自然肯定能在第三次猜中數字。最後期待guessDAO把結果儲存。

        這段程式碼和之前的區別是,在Expectation中沒定義成員變數,而把Guess.class顯式地通過建構函式傳入。這麼做也是為了只對tryIt方法mock,因為在Expectation建構函式傳入Class物件或Instance物件後,只會區塊內Class或Instance對應的行為進行mock。

通過以上2個基於行為mock的例子,應該對JMockit如何測試程式碼內部邏輯有了解了吧。下面再對基於狀態的mock介紹:

3. 模擬正常猜骰子,觀察輸出猜中的概率

再加入第三各測試方法:

  1. /** 
  2.  * 模擬正常執行,計算抽中概率,把DAO mock掉 
  3.  */
  4. @Test
  5. publicvoid stateTest_mockDAO() {  
  6.     final Map<Integer, Integer> statMap = new HashMap<Integer, Integer>(); // statMap.get(0)為猜中次數,statMap.get(1)為失敗次數
  7.     statMap.put(00);  
  8.     statMap.put(10);  
  9.     guessDAO = new MockUp<GuessDAO>() {            // MockUp用來定義新的程式碼邏輯
  10.         @SuppressWarnings("unused")  
  11.         @Mock
  12.         publicboolean saveResult(boolean isSuccess, int n) {  
  13.           if (isSuccess) {  
  14.               statMap.put(0, statMap.get(0)+1);  
  15.               System.out.println("you guess it! dice:" + n);  
  16.           } else {  
  17.               statMap.put(1, statMap.get(1)+1);  
  18.               System.out.println("you didn't guess it. dice:" + n);  
  19.           }  
  20.           returntrue;  
  21.         }  
  22.     }.getMockInstance();    
  23.     for (int i=0; i<1000; i++) {  
  24.         Guess guess = new Guess(3);  
  25.         guess.setGuessDAO(guessDAO);  
  26.         guess.doit();  
  27.     }  
  28.     System.out.println("hit" + statMap.get(0));  
  29.     System.out.println("not hit" + statMap.get(1));  
  30.     double rate =((double) statMap.get(0)) / (statMap.get(0)+statMap.get(1));  
  31.     System.out.println("hit rate=" + rate);  
  32. }  

        第三個用例目的是,測試在指定嘗試次數下猜中數字的概率。這就不再盯著程式碼內部邏輯,而從整體功能角度進行測試,把內部無法呼叫的的依賴介面mock掉。

        在基於狀態的mock中,看不到了Expectations,@Mocked等字樣了。取而代之的是MockUp,@Mock。

        程式碼中對GuessDAO的儲存方法進行了重定義。讓其直接從控制帶輸出訊息。

        通過這種方式,不僅可以進行功能黑盒測試,還可以儘快地讓測試程式碼跑起來。

        MockUp中的泛型型別是被重定義的類,重定義的方法需要和原類中的方法簽名一致。但是,static方法可以省區static關鍵字。如:

  1. new MockUp<Calendar>() {  
  2.     @Mock
  3.     public Calendar getInstance() {  
  4.         return calendar1;  
  5.     }  
  6. };  


        至此,通過三個例子,把JMockit的2個測試方式簡單介紹了。但是JMockit的功能不僅如此,詳細能請檢視官方文件和例項。

=============

過程中遇到還未解決的疑問:

1. 基於行為的mock,需要對回放的類的具體型別類mock,沒法針對父類型別mock?

  1. Guess g = new Guess(3);  
  2. new Expectations() {  
  3.             @Mocked(methods="tryIt")  
  4.             GuessParent mg;      // 對Guess父類進行mock
  5.             {  
  6.                 mg.tryIt();result=true;  
  7.             }  
  8.         };  
  9. g.doit();  
假如宣告的mg型別是Guess的父類,則回放中呼叫Guess.doit()將不能捕捉道mg.tryIt();導致測試失敗。
除非在expectation建構函式傳入例項g才可以。

2. 基於行為的動態mock, 文件說: If the Class object for a given class is passed, the methods and constructors defined in that class are considered for mocking,but not the methods and constructors of its super-classes. If an instance of a given class is passed, then all methods defined in the whole class hierarchy, from the concrete class of the given instance up to (but not including) Object, are considered for mocking; the constructors of these classes, however, are not (since an instance was already created). 粗體的不是很理解,是說mock的父類的方法和建構函式不被mock?但測試結果卻不是這樣

其他Mock框架與jmockit對比


相關推薦

單元測試mock框架——jmockit實戰

轉載地址:http://blog.csdn.net/ultrani/article/details/8993364 JMockit是google code上面的一個java單元測試mock專案,她很方便地讓你對單元測試中的final類, JMockit的測試方式可以通過

【C#】【xUnit】【Moq】.NET單元測試Mock框架Moq初探!

在TDD開發模型中,經常是在編碼的同時進行單元測試的編寫,由於現代軟體開發不可能是一個人完成的工作,所以在定義好介面的時候我們就可以進行自己功能的開發(介面不能經常變更),而我們呼叫他人的功能時只需要使用介面即可。 但我們在編寫自己的單元測試並進行功能驗證的時候,如果介面的實現人還沒有完成程式碼怎麼

單元測試---Mock

一行 構造 ica using ike face turn public ber mock測試就是在測試過程中,對於某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來創建以便測試的測試方法. 1 using Moq; 2 3 // Assumptions:

基於spring與mockito單元測試Mock對象註入

err else archive ali spro 反射 lse ica sce 轉載:http://www.blogjava.net/qileilove/archive/2014/03/07/410713.html 1.關鍵詞   單元測試、spring、mockito

SpringMVC : Controller層單元測試Mock

程式碼 @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration(locations = { "classpath:applicationContext.

java單元測試之junit之實戰

1 編寫該文章的起因 博主是一枚小新,經常挖坑填坑。最近在工作中遇到了這樣一種情況。某天小夥伴說博主寫得一個方法有問題,並且相應的測試類也跑不通。博主一直秉著磨刀不誤砍柴工的思想,測試類都是寫得好好地並且能槓槓執行的!懷著好奇,經過一番debug,發現某句程式

關於嵌入式C語言單元測試自動化框架的搭建思路

  嵌入式C語言單元測試框架設計Demo(基於Keil專案)    Driver    TestSuite - TestCase    Assert    TestLog    板上執行測試用例   

單元測試——Mock

【背景】         單元測試的目標是一次只驗證一個方法,但是倘若遇到這樣的情況:某個方法依賴於其他一些難以操控的東西,諸如網路、資料庫,甚至是微軟最新的股票價格,那我們該怎麼辦?         要是你的測試依賴於系統的其他部分,甚至是系統的多個其他部分呢?在這種情況

springboot單元測試詳解和實戰

單元測試是檢測程式碼嚴密性的最好方式,不僅能減少和預防bug的產生,還能自己二次檢查程式碼或者考慮review必要,如果你還沒有養成這個習慣,可要開始關注了。 上節以 springboot快速實戰搭建篇 快速入門,本節主要講述單元測試使用以及多環境配置 maven依賴

單元測試模擬框架:Nsubstitute

NSubstitute 更注重替代(Substitute)概念。它的設計目標是提供一個優秀的測試替代的.NET模擬框架。它是一個模擬測試框架,用最簡潔的語法,使得我們能夠把更多的注意力放在測試工作,減輕我們的測試配置工作,以滿足我們的測試需求,幫助完成測試工作。它提供最經常需要使用的測試功能,且易於使用

Junit單元測試Spring框架介面

Web專案整合Spring後,所有的介面,類都可以載入到ApplicationContext中。專案不用啟動也可以載入到記憶體中,並通過applicationContext獲取。 使用main方法測試,如下: package testCase; import org.sp

《selenium2 python 自動化測試實戰》(21)——unittest單元測試框架解析

nbsp add pic post 二維碼 mage ron 而且 aaa unittest是展開自動化測試的基礎——這個框架很重要! 我們先自己寫一個測試類: 1、被測試類 Widthget.py: # coding: utf-8class Wi

基於mock物件和JUnit框架簡化Spring Web元件單元測試

對於Java元件開發者來說,他們都盼望擁有一組能夠對元件開發提供全面測試功能的好用的單元測試。一直以來,與測試獨立的Java物件相比,測試傳統型J2EE Web元件是一項更為困難的任務,因為Web元件必須執行在某種伺服器平臺上並且它們還要與基於HTTP的Web互動細節相聯

單元測試mock的使用及mock神器jmockit實踐

在最近的r應用的單元測試中,經常需要用到mock,可以說mock在ut (unit test)中是無處不在的。而在r的ut實踐中也找到了一種很簡潔的mock方式,不僅解決了ut中所有需要mock的地方,而且可以很少量的程式碼來完成mock。詳見下文。   一.Mock

.NET Core之單元測試(三):Mock框架Moq的使用

編寫一個API 新增一個介面 public interface IFoo { bool Ping(string ip); } 介面實現 public class Foo : IFoo { public bool Ping(string ip) { return new

單元測試之Stub和Mock

下載 我們 並且 試用 sample 註入 mes oge new 單元測試之Stub和Mock FROM:http://www.cnblogs.com/TankXiao/archive/2012/03/06/2366073.html 在做單元測試的時候,我們會發現我

單元測試(四)-隔離框架NSubstitute

靜態類 整體 txt 常用 style 實現 快捷 logger lambda表達式 之前學習了單元測試的基礎知識,以及樁對象和模擬對象的不同作用。但在實際應用中,往往不會直接手寫樁對象或者模擬對象,而是使用隔離框架動態的創建這些對象,這可以讓測試變得更簡便、快捷,還可以更

JavaScript單元測試框架-Jasmine

calling help without 還原 develop util 也不會 目錄結構 函數調用 轉載自金石開的blog:http://www.cnblogs.com/zhcncn/p/4330112.html Jasmine的開發團隊來自PivotalLabs,他

Mock單元測試

多次調用 註入 tor call stream exceptio system ive eth   單元測試的思路是在不涉及依賴的情況下測試代碼,一般是測試service層的方法,但實際的代碼常會涉及到依賴,一個可行的方法就是使用模擬對象來替換依賴對象。 1.使用Mocki

TestNG詳解(單元測試框架

參數 http () 彈出 內容 name led 大小 2.4 一、TestNG的優點   1.1 漂亮的HTML格式測試報告   1.2 支持並發測試   1.3 參數化測試更簡單   1.4 支持輸出日誌   1.5 支持更多功能的註解 二、編寫TestNG測試