我對Stub和Mock的理解


介紹

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

今天查閱了相關資料,覺得寫得很不錯,所以我試圖在博文中對資料進行整理一下,再加上一些自己的觀點。

本文是目前我對Stub和Mock的認識,難免有偏差,歡迎大家拍磚。

分析

Stub和Mock都是屬於測試替身,對類型細分的話可以分為:

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

前四項屬於Stub,最后的Mock Object屬於Mock。

類型分析

Dummy Object(啞對象)

測試代碼僅僅是需要使用它來通過編譯,實際上用不到它。如測試A類的run方法,需要在創建A類的實例時需要傳入B類實例,但run方法並沒有用到B類實例。在測試時需要傳入B類的啞對象new NullB()(如“new A(new NullB())”),讓其通過編譯。這里的NullB是一個空類,沒有具體實現。

Fake Object(假對象)

假對象相對於啞對象來說,要對耦合的組件有一些簡單的實現,實現我們在測試中要用到的方法,指定期望的行為(如返回期望的值)。假對象適用於替換產品代碼中使用的全局對象,或者創建的類。這里注意的是要先對被替換的全局對象或類進行備份,然后在測試完成后進行恢復。

示例1(替換全局對象):

//產品代碼
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測試模式--測試碼重構》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM