我對Stub和Mock的理解

介紹html

使用測試驅動開發大半年了,我仍是對Stub和Mock的認識比較模糊,沒有進行系統整理。java

今天查閱了相關資料,以爲寫得很不錯,因此我試圖在博文中對資料進行整理一下,再加上一些本身的觀點。post

本文是目前我對Stub和Mock的認識,不免有誤差,歡迎你們拍磚。測試

分析this

Stub和Mock都是屬於測試替身,對類型細分的話能夠分爲:spa

  • Dummy Object
  • Fake Object
  • Test Stub
  • Test Spy
  • Mock Object

前四項屬於Stub,最後的Mock Object屬於Mock。.net

類型分析

Dummy Object(啞對象)

測試代碼僅僅是須要使用它來經過編譯,實際上用不到它。如測試A類的run方法,須要在建立A類的實例時須要傳入B類實例,但run方法並無用到B類實例。在測試時須要傳入B類的啞對象new NullB()(如「new A(new NullB())」),讓其經過編譯。這裏的NullB是一個空類,沒有具體實現。prototype

Fake Object(假對象)

假對象相對於啞對象來講,要對耦合的組件有一些簡單的實現,實現咱們在測試中要用到的方法,指按期望的行爲(如返回指望的值)。假對象適用於替換產品代碼中使用的全局對象,或者建立的類。這裏注意的是要先對被替換的全局對象或類進行備份,而後在測試完成後進行恢復。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);
    });
});

Test 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 = {  //B類的樁
            getNum: function(){
                return 1;
            }
        };

        var a = new A(stub_B); //注入樁
        a.run();

        expect(a.num).toEqual(1);
    });
});

Test Spy(嗅探樁)

與測試樁相似,可是能夠記錄樁使用的記錄,並進行驗證。

示例:

可使用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方法
    });
});

Mock Object(模擬對象

設定產品代碼中耦合的類的指望的行爲,而後驗證指望的行爲是否發生,從而達到測試產品代碼行爲的目的。適用於驗證一些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是替換整個被Mock的類,這個類能夠存在也能夠不存在。而Spy是使用一個已經存在的類,嗅探其中的部分方法。
  • 從流程中來講,Mock是先設定被Mock的類的指望行爲,而後驗證指望的行爲是否發生。Spy是記錄下樁的方法的使用記錄(如傳入的參數,調用的次數等),而後再對記錄進行驗證。

Mock退化爲Stub

在現實使用中,咱們常常將mock作不一樣程度的退化,從而使得mock對象在某些程度上如stub同樣工做。
使用Mock的示例:
//產品代碼
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。

參考資料

軟件測試- 3 - Mock 和Stub的區別

淺談mock和stub

《xUnit測試模式--測試碼重構》

相關文章
相關標籤/搜索