介绍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测试模式--测试码重构》