介紹html
使用測試驅動開發大半年了,我仍是對Stub和Mock的認識比較模糊,沒有進行系統整理。java
今天查閱了相關資料,以爲寫得很不錯,因此我試圖在博文中對資料進行整理一下,再加上一些本身的觀點。post
本文是目前我對Stub和Mock的認識,不免有誤差,歡迎你們拍磚。測試
分析this
Stub和Mock都是屬於測試替身,對類型細分的話能夠分爲:spa
前四項屬於Stub,最後的Mock Object屬於Mock。.net
測試代碼僅僅是須要使用它來經過編譯,實際上用不到它。如測試A類的run方法,須要在建立A類的實例時須要傳入B類實例,但run方法並無用到B類實例。在測試時須要傳入B類的啞對象new NullB()(如「new A(new NullB())」),讓其經過編譯。這裏的NullB是一個空類,沒有具體實現。prototype
假對象相對於啞對象來講,要對耦合的組件有一些簡單的實現,實現咱們在測試中要用到的方法,指按期望的行爲(如返回指望的值)。假對象適用於替換產品代碼中使用的全局對象,或者建立的類。這裏注意的是要先對被替換的全局對象或類進行備份,而後在測試完成後進行恢復。rest
示例1(替換全局對象):code
//產品代碼 function A(){ this.num = 0; } A.prototype.run = function(){ this.num = window.b.getNum(); }; //測試代碼 describe("測試A類的run方法", function(){ var temp = null; function backUp(){ window.b = window.b || {}; temp = YYC.Tool.extendDeep(window.b); } function restore(){ window.b = temp; } beforeEach(function(){ backUp(); }); afterEach(function(){ restore(); }); it("得到數字", function () { window.b = { //假對象 getNum: function(){ return 1; } } var a = new A(); a.run(); expect(a.num).toEqual(1); }); });
示例2(替換類):
//產品代碼 function A() { this.num = 0; this._b = new B(); } A.prototype.run = function () { this.num = this._b.getNum(); }; //測試代碼 describe("測試A類的run方法", function () { var temp = null; function backUp() { window.B = window.B || function () {}; temp = B; } function restore() { window.B = temp; } beforeEach(function () { backUp(); }); afterEach(function () { restore(); }); it("得到數字", function () { window.B = function () { }; window.B.prototype.getNum = function () { return 1; }; var a = new A(); a.run(); expect(a.num).toEqual(1); }); });
測試樁與假對象有點相似,也要實現與產品代碼耦合的組件,指按期望的行爲。這裏最大的不一樣是測試樁須要注入到產品代碼中,從而在測試產品代碼時替換組件,執行樁的行爲。使用測試樁不須要進行備份和還原。
示例:
//產品代碼 function A(b) { this.num = 0; this._b = b; } A.prototype.run = function () { this.num = this._b.getNum(); }; //測試代碼 describe("測試A類的run方法", function () { it("得到數字", function () { var stub_B = { //B類的樁 getNum: function(){ return 1; } }; var a = new A(stub_B); //注入樁 a.run(); expect(a.num).toEqual(1); }); });
與測試樁相似,可是能夠記錄樁使用的記錄,並進行驗證。
示例:
可使用jasmine的spy來舉例。
//產品代碼 function A(b) { this.num = 0; this._b = b; } A.prototype.run = function () { this.num = this._b.getNum(); }; //測試代碼 describe("測試A類的run方法", function () { it("得到數字", function () { var stub_b = { getNum: function(){ return 1; } }; spyOn(stub_b, "getNum").andCallThrough(); //嗅探樁的getNum方法 var a = new A(stub_b); //注入樁 a.run(); expect(a.num).toEqual(1); expect(stub_b.getNum).toHaveBeenCalled(); //驗證調用過樁的getNum方法 }); });
設定產品代碼中耦合的類的指望的行爲,而後驗證指望的行爲是否發生,從而達到測試產品代碼行爲的目的。適用於驗證一些void的行爲。例如:在某個條件發生時,要記錄Log。這種情景,用stub就很難驗證,由於對目標物件來講,沒有回傳值,也沒有狀態變化,就只能經過mock object來驗證目標物件是否正確的與Log介面進行互動。
示例:
//產品代碼 function A(b) { this.num = 0; this._b = b; } A.prototype.run = function () { this.num = this._b.getNum(2); }; //測試代碼(Mock爲僞代碼) describe("測試A類的run方法", function () { it("得到數字", function () { var mockB = Mock.createMock({ getNum: function(){} }); //若是B類存在的話,也能夠直接傳入B的原型:var mockB = Mock.createMock(B.prototype); Mock.expect(mockB.getNum, 2).return(1).times(1); var a = new A(mockB); a.run(); expect(a.num).toEqual(1); Mock.verify(); //驗證指望的行爲發生:mockB的getNum傳入的參數爲2;調用了1次mockB.getNum }); });
Mock(Mock Object)與Spy(Test Spy)的比較
相同點
不一樣的
Mock退化爲Stub
//產品代碼 function A(b) { this.num = 0; this._b = b; } A.prototype.run = function () { this.num = this._b.getNum(2); }; //測試代碼(Mock爲僞代碼) describe("測試A類的run方法", function () { it("得到數字", function () { var mockB = Mock.createMock({ getNum: function(){} }); //若是B類存在的話,也能夠直接傳入B的原型:var mockB = Mock.createMock(B.prototype); Mock.expect(mockB.getNum).return(1); //只指定返回值,沒有指望的參數或指望調用的次數。所以不用verify來驗證了! var a = new A(mockB); a.run(); expect(a.num).toEqual(1); }); });
也能夠用Stub來達到相同的效果:
//產品代碼 function A(b) { this.num = 0; this._b = b; } A.prototype.run = function () { this.num = this._b.getNum(); }; //測試代碼 describe("測試A類的run方法", function () { it("得到數字", function () { var stub_B = { getNum: function(){ return 1; } }; var a = new A(stub_B); a.run(); expect(a.num).toEqual(1); }); });
總結
在比較簡單的狀況下(如須要啞對象來經過編譯,或是須要測試樁來替換耦合的組件),使用Stub。
若是須要驗證耦合組件的行爲,可使用Spy或Mock。
參考資料
《xUnit測試模式--測試碼重構》