单元测试—kissy1.2最佳实践探索(上)

标签: kissy kissy1.2最佳实践探索 | 发表时间:2012-12-20 16:38 | 作者:明河
出处:http://www.36ria.com

离上次写kissy1.2最佳实践探索时间有些长,先来回归下前面的内容。

《start篇—kissy1.2最佳实践探索》

《使用Kissy Pie快速构建—kissy1.2最佳实践探索》

《说说模块组织—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)、 juicervelocity。它们存在的理由就是希望能够将DOM从业务逻辑中剥离出来。而对于我们进行单测好处不言而喻,不再考虑烦人的DOM的问题(事实上很难做到这点,因为在web中DOM既是view又负责事件的分发,起到controller的作用)。

说到分离,直接使用MVC框架呢?

使用前端MVC框架的业务代码更好测,但明河严重不推荐盲目使用,为什么呢?因为现在web页面的形态,都不是单页面应用形态,通俗点,比如像webQQ或淘宝下单页面,单页面包含大量逻辑,修改数据/视图都在单页面解决,就很适合MVC的方式,很遗憾,这种形态的页面毕竟少数,所以要根据业务实际场景选择。

相对于MVC,明河更推荐MVVM模式,比如google的 angularjsknockoutjs,angularjs更是一个完整的基于MVVM模式的完整解决方案,将单测集成为一个环节。

不展开论述了,有兴趣的朋友可以看下winter的 《Web前端开发:为何选择MVVM而非MVC》

总结

一坨文字,如果亲友耐心看到这里,明河赶紧不尽,当然没有切糕奉上,不过后面将奉上单测实践的内容,感谢大家继续关注。

相关 [单元测试 kissy1 最佳实践] 推荐:

单元测试—kissy1.2最佳实践探索(上)

- - ria之家--RIA三部曲:jquery、ext、flex
离上次写kissy1.2最佳实践探索时间有些长,先来回归下前面的内容. 《start篇—kissy1.2最佳实践探索》. 《使用Kissy Pie快速构建—kissy1.2最佳实践探索》. 《说说模块组织—kissy1.2最佳实践探索》. 明河总结了项目构建打包、模块组织方面的知识,接下来明河继续总结kissy1.2中的单元测试该如何编写.

Android单元测试

- - CSDN博客推荐文章
    单元测试不管对于初学编程还是已经工作了很久的开发者来说,都不乐意花时间去写认为没用的代码进行测试,只要交给测试人员就行了,虽然这样也能把软件改出来,但也许你要花上几倍的时间去修改问题,如果在开发的过程中花点时间去写单元测试代码,把尽可能出问题的地方都测试一遍,把问题扼杀在最开始的地方,这样你就不必为后来找问题出处而烦恼.

jQuery最佳实践

- andi - 阮一峰的网络日志
上周,我整理了《jQuery设计思想》. 那篇文章是一篇入门教程,从设计思想的角度,讲解"怎么使用jQuery". 今天的文章则是更进一步,讲解"如何用好jQuery". 我主要参考了Addy Osmani的PPT《提高jQuery性能的诀窍》(jQuery Proven Performance Tips And Tricks).

PHP最佳实践

- xiangqian - 阮一峰的网络日志
虽然名字叫《PHP最佳实践》,但是它主要谈的不是编程规则,而是PHP应用程序的合理架构. 它提供了一种逻辑和数据分离的架构模式,属于MVC模式的一种实践. 我觉得,这是很有参考价值的学习资料,类似的文章网上并不多,所以一边学习,一边就把它翻译了出来. 根据自己的理解,我总结了它的MVC模式的实现方式(详细解释见译文):.

MongoDB最佳实践

- - NoSQLFan
将 MongoDB加入到我们的服务支持列表中,是整个团队年初工作计划中的首要任务. 但我们感觉如果先添加一项对NoSQL存储的支持,而不是先升级已支持的关系型数据库,可能对用户不太好,毕竟目前的用户都使用关系型数据库. 所以我们决定将引入MongoDB这项工作放到升级MySQL和PostgreSQL之后来做.

Dockerfile 最佳实践

- - DockOne.io
在容器领域,Docker 公司提出的容器镜像已经成为目前容器打包交付的事实标准. 构建镜像需要编写 Dockerfile,如何编写一个优雅的 Dockerfile 呢. 在 Docker 公司的官方文档中给出了一篇:《 Best practices for writing Dockerfiles》.

Hadoop之MapReduce单元测试

- - ITeye博客
通常情况下,我们需要用小数据集来单元测试我们写好的map函数和reduce函数. 而一般我们可以使用Mockito框架来模拟OutputCollector对象(Hadoop版本号小于0.20.0)和Context对象(大于等于0.20.0). 下面是一个简单的WordCount例子:(使用的是新API).

springboot单元测试技术

- - 海思
整个软件交付过程中,单元测试阶段是一个能够最早发现问题,并且可以重复回归问题的阶段,在单元测试阶段做的测试越充分,软件质量就越能得到保证. 具体的代码参照 示例项目 https://github.com/qihaiyan/springcamp/tree/master/spring-unit-test.

文章: Grails最佳实践

- - InfoQ cn
我在IntelliGrape工作,这是一家专门使用Groovy & Grails进行开发的公司. 本文是我们Grails项目遵循的最佳实践的基本清单,收集自邮件列表、Stack Overflow、博文, 播客和 IntelliGrape的内部讨论. 它们分为控制器、服务、Domain、视图、TagLib、测试和其他.

PHP最佳实践(译)

- - CSDN博客Web前端推荐文章
原文:  PHP Best Practices-A short, practical guide for common and confusing PHP tasks. 译者: youngsterxyf. 本文档最后审阅于2013年3月8日. 由我, Alex Cabal,维护该文档. 我编写PHP程序已有很长一段时间了,当前我 经营着 Scribophile,由认真作家组成的一个在线写作团体,  Writerfolio,为自由职业者提供的一个易用写作工具集,以及  Standard Ebooks,一个图文并茂、无数字版权管理的公共领域电子书出版商.