Closed baixiaoji closed 4 years ago
当前进度
有趣的例子: 在正式开始今天的话题之前,我先给你分享一个工厂生产电视机的例子。
工厂首先会将各种电子元器件按照图纸组装在一起构成各个功能电路板,比如供电板、音视频解码板、射频接收板等,然后再将这些电路板组装起来构成一个完整的电视机。
如果一切顺利,接通电源后,你就可以开始观看电视节目了。但是很不幸,大多数情况下组装完成的电视机根本无法开机,这时你就需要把电视机拆开,然后逐个模块排查问题。
假设你发现是供电板的供电电压不足,那你就要继续逐级排查组成供电板的各个电子元器件,最终你可能发现罪魁祸首是一个电容的故障。这时,为了定位到这个问题,你已经花费了大量的时间和精力。
那在后续的生产中,如何才能避免类似的问题呢?
你可能立即就会想到,为什么不在组装前,就先测试每个要用到的电子元器件呢?这样你就可以先排除有问题的元器件,最大程度地防止组装完成后逐级排查问题的事情发生。
实践也证明,这的确是一个行之有效的好办法。
如何做到TDD,可以遵循契约测试 https://insights.thoughtworks.cn/about-contract-test/
单元测试期间,主要测试单元存在一些外部依赖,但是单测期间我们并不需要关注外部依赖的情况,因此采用mock对象或是stub对象来代替外部依赖,从而模拟真实场景对被测试单元进行测试工作。
在测试,尤其是单元测试中,我们通常关注的是主要测试对象的功能和行为,对于主要测试对象涉及到的次要对象尤其是一些依赖,我们仅仅关注主要测试对象和次要测试对象的交互,比如是否调用,何时调用,调用的参数,调用的次数和顺序等,以及返回的结果或发生的异常。但次要对象是如何执行这次调用的具体细节,我们并不关注,因此常见的技巧就是用mock对象或者stub对象来替代真实的次要对象,模拟真实场景来进行对主要测试对象的测试工作。
相同点
都是模拟外部依赖对象,从而让测试能够正常进行
不同点
Test lifecycle with stubs: Setup - Prepare object that is being tested and its stubs collaborators. Exercise - Test the functionality. Verify state - Use asserts to check object's state. Teardown - Clean up resources.
Test lifecycle with mocks: Setup data - Prepare object that is being tested. Setup expectations - Prepare expectations in mock that is being used by primary object. Exercise - Test the functionality. Verify expectations - Verify that correct methods has been invoked in mock. Verify state - Use asserts to check object's state. Teardown - Clean up resources.
测试生命周期不同,stub 是帮助测试被测单元的功能,检验单元是否符合预期;mock 更多是记录并校验被测单元的行为是否触发,而且自身到测试结束在进行验证。主要检验被测单元功能是否触发,并行为和状态是否符合mock数据 stubs 的例子
//产品代码
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);
});
});
//产品代码
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
});
});
总结,stub和mock对象都是s协助被测单元进行完成单测。 stub对象协助测试后,断言期望与被测单元相关。 mock对象协助测试后,断言期望与mock对象相关。
现实使用中,我们通常使用的mock是退化的mock,从而使得mock和stub在测试使用上行为表示一样。 把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);
});
});
因为日常使用中,对两者的概念比较模糊,所以在Jest 社区中存在一个命名的讨论。Rename jest.mock to jest.stub
官网一些测试建议:
shallow
和shallowMount
,区别 shallow
不会渲染子组件只渲染自身,shallowMount
渲染子组件会进行存根(及挂载一个组件而不渲染其子组件)createLocalVue
终极三问回复。
好玩的是一件事情,事后补录单测,发现函数名称叫做unique,然后根据下意识去写单测,发现根本就是不是和自己理解的是同一回事
这是什么东西?(明白该想project的定义)
单元测试
为什么要做这个东西?(想想做这个东西的动机)
为了让自己书写的代码出现的问题,可以及早发现 逐渐养成TDD习惯
如何做这个东西?(拆解细节)
回顾(项目过程中的执行、以及心态的变化)
2019.04.13 完善了文章前言的例子
2019.04.17 mock v.s stub 区分
2019-04-18 终极三问&一些实行建议 ![](http://progressed.io/bar/50)