单元测试—kissy1.2最佳实践探索(上)
离上次写kissy1.2最佳实践探索时间有些长,先来回归下前面的内容。
《使用Kissy Pie快速构建—kissy1.2最佳实践探索》
明河总结了项目构建打包、模块组织方面的知识,接下来明河继续总结kissy1.2中的单元测试该如何编写。
javascript的单元测试,一直是javascript的老大难问题,当编写针对业务代码的测试用例时,总会遇到各式各样的问题。
明河先抛出突出的几个问题,后面会一一进行论述,提出解决方案:
- 我该使用什么单元测试框架?
- 为什么我的js代码这么难以测试?
- 在kissy的生态圈中如何进行测试呢?
- 如何测试异步的js代码?
- 如何测试ajax?如何mock ajax的数据?
- 如果我的业务js代码依赖于dom,我该如何处理?
- 要回归的浏览器这么多,我该如何快速回归?
单元测试框架的选择
javascript单元测试框架五花八门,明河比较推荐的选择是: jasmine/ mocha/ QUnit。
如果你的js代码是基于jQuery,那么QUnit可以作为第一选择,QUnit和jQuery具有天然的适配性,当然jasmine和mocha也是非常不错的选择;
如果是基于KISSY的,那么明河推荐jasmine/mocha;
如果是在Node环境中,无疑是mocha了。
jasmine和mocha非常相似,mocha比jasmine来的强大和便利,jasmine起步早,用户更多些。
综合考虑,基于kissy的js代码,推荐使用 jasmine进行测试。当然想要使用jasmine服务于kissy,还要经过一番“调教”(有些邪恶的样子~\(≧▽≦)/~)。
关于 jasmine的使用请看官方文档,已经足够详细,后面明河会结合kissy讲解异步API部分的处理。
在kissy1.2的生态圈中如何进行测试呢?
这个标题通俗一点的表述是如何让基于kissy1.2的代码更为可测。
在跟同事交流中,最大的抱怨声就是维护的旧代码,可测性太差,导致难以编写测试用例。
那么代码的哪些部分影响到可测试性呢?
- 大量闭包,代码过于封闭,测试用例覆盖率过低
- 代码欠缺组织,一个函数包含大量逻辑,违背单一性原则,导致测试用例覆盖不到位
- 遇到异步代码,而且是混在业务逻辑内的,难以编写测试用例
- DOM的依赖过深,导致用例运行错误
如何提高代码的可测试呢?明河的建议如下:
1.采用模块==类的方式组织业务代码
明河曾讲解 《说说模块组织—kissy1.2最佳实践探索》时提到模块组织的原则,这些原则其实都有利于提高代码的可测试。
这里再提一个:采用模块==类的方式组织业务代码。 模块==类指的是,一个js业务模块就是一个js的类,外部使用new关键字来实例化这个类,初始化业务模块逻辑。
为什么这种方式有利于提高代码的可测试性呢?
因为可以暴露出属性和接口供用例使用,保证用例的覆盖率。
这里肯定有同学提异议了:不是说封闭的js代码才好吗?暴露出去的属性和接口被误改了,咋办?
第二点接着讲。
2.合理使用闭包
每本讲解javascript的书上都会告诉你闭包的重要性,在kissy的模块系统中,kissy的每个模块都是独立的,拥有自己的作用域,闭包的特性已经自带,而且给私有属性或私有方法加“_”已经成为前端宗约定俗成的事,暴露出去,供编写用例时使用,未尝不可。
现在明河写的代码很少是外部无法调用到的,也没发现有什么问题,比如明河写的一个 业务模块,所有的方法都是暴露出去的,只是私有的方法加上了“_”,这样我在编写用例的时候,就可以覆盖到业务模块所有的函数逻辑。
it('_setUrl测试',function(){ var url2 = howDisponse._setUrl(); expect(url2).toBe(url); expect(howDisponse.get('url')).toBe(url); htmlMock.clean(vm); })
(PS:howDisponse为业务模块的实例)
当然这里是明河的一家之言,欢迎大家讨论。
过于封闭的模块代码,也缺失了可配置性,适用性上会变差。明河在实际编码中发现,很多业务模块也是公用却局部变化的,所以一切在于权衡和设计。
3.模块单一性原则,函数尽量职责单一
什么样的js代码最难写用例?
单模块js包含大量功能逻辑,通俗点就是js文件很长,直接失去编写用例的兴趣,你挑战的是一个个未知的地雷,写出了用例,心里没底,说不定跑通过了,代码发布上线却触雷了,粉身碎骨,惊呼,“单测坑我!”。
庞大的函数也是如此,翻翻淘宝的旧代码,大量的代码都是这种情况,一个函数,包含了所有业务功能逻辑的都有,就像一个内部错综复杂的炸弹,你准备拆弹打开外壳一看,吓尿了,动弹不得。
4.避免过多的回调,回调提取
举个常见的例子,ajax请求:
function Test(){ Test.superclass.constructor.call(this); this._init(); } S.extend(Test,Base,{ _init:function(){ S.io({ type:'post', url:ajaxUrl.getQATree, data:{ reasonId:target.val() }, dataType:'json', success:function (d) { if (d.status == true) { self.questionTree = d.data; parent.attr('data-value', target.val()); self.formBox.one('.J_RightTreeId').val(self.questionTree.treeId); self._recursionRemoveChildByTrack([0]); if (self.questionTree && self.questionTree.root) { var track = S.JSON.parse(parent.attr('data-tree-idx')); track.push(0); self._renderQuestionDetail(self.questionTree, self.questionTree.root, parent, 0, track); } } else { } }, error:function () { // alert('请求错误,请稍后重试!'); } }) } } })
(PS:嘿嘿偷偷抄了一份同事的代码做反例,希望他看到后别拿刀砍我,O(∩_∩)O哈!)
这样的书写见得太多了,ajax请求成功后执行 success
回调。success回调函数内,包含了各种逻辑处理。
为什么明河建议将success回调提取出来呢?比如写成下面的代码:
function Test(){ Test.superclass.constructor.call(this); this._init(); } S.extend(Test,Base,{ _init:function(){ var self = this; S.io({ type:'post', url:ajaxUrl.getQATree, data:{ reasonId:target.val() }, dataType:'json', success:function (d) { self._successCallback(d); }, error:function () { // alert('请求错误,请稍后重试!'); } }) } }, _successCallback:function(data){ //这里是逻辑 } })
所有的逻辑都在 _successCallback
中,这样做的好处,你的用例如果想要覆盖回调函数中所有逻辑,就不需要每次触发io,发送请求,你可以伪造ajax返回的数据测试successCallback这个回调的所有与逻辑。
也就说可以通过提取,让用例跳过ajax的异步环节,简化测试环节,同时保证用例的覆盖率。
而且 避免回调,以及多层回调是编写js代码的好习惯,可以让代码更易懂,同时提高可维护性。
同样的,监听DOM节点的事件,明河也建议提取,比如下面的代码:
$('#J_Tip').on("click",this._btnClickHandler,this);
5.分离DOM和逻辑
不依赖于DOM的js代码最好测,所以NodeJs的代码比前端js容易测很多,mocha跟NodeJs更是天作之合。
为了达到分离DOM的目的,可以在代码中引入前端模版引擎。
前端模版引擎现在很火,单是kissy就有好几套,比如 template(kissy1.3重构成xTemplate)、 juicer、 velocity。它们存在的理由就是希望能够将DOM从业务逻辑中剥离出来。而对于我们进行单测好处不言而喻,不再考虑烦人的DOM的问题(事实上很难做到这点,因为在web中DOM既是view又负责事件的分发,起到controller的作用)。
说到分离,直接使用MVC框架呢?
使用前端MVC框架的业务代码更好测,但明河严重不推荐盲目使用,为什么呢?因为现在web页面的形态,都不是单页面应用形态,通俗点,比如像webQQ或淘宝下单页面,单页面包含大量逻辑,修改数据/视图都在单页面解决,就很适合MVC的方式,很遗憾,这种形态的页面毕竟少数,所以要根据业务实际场景选择。
相对于MVC,明河更推荐MVVM模式,比如google的 angularjs和 knockoutjs,angularjs更是一个完整的基于MVVM模式的完整解决方案,将单测集成为一个环节。
不展开论述了,有兴趣的朋友可以看下winter的 《Web前端开发:为何选择MVVM而非MVC》。
总结
一坨文字,如果亲友耐心看到这里,明河赶紧不尽,当然没有切糕奉上,不过后面将奉上单测实践的内容,感谢大家继续关注。