单元测试—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中的单元测试该如何编写.

深入理解单元测试:技巧与最佳实践

- - crossoverJie's Blog
之前分享过如何快速上手开源项目以及如何在开源项目里做集成测试,但还没有讲过具体的实操. 今天来详细讲讲如何写单元测试. 这个大家应该是有共识的,对于一些功能单一、核心逻辑、同时变化不频繁的公开函数才有必要做单元测试. 对于业务复杂、链路繁琐但也是核心流程的功能通常建议做 e2e 测试,这样可以保证最终测试结果的一致性.

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、测试和其他.