javascript 单元测试覆盖率工具,及Grunt集成

js单元测试Unit Test的工具包括Jasmine, Mocha, NodeUnit, JsTestDriver等。

Jasmine最为流行,因为其良好的语义和丰富的Mocking机制。

无论使用Grunt.js或是Maven来作为项目构建工具,Jasmine都有良好的插件支持。

js单元测试其实并不为前端开发者重视,近年开始node.js以及单页面web app流行起来后,越来越多的js项目也开始采用了单元测试,至于单元测试覆盖率,对于很多js开发者还很新鲜。

笔者所在项目采用grunt.js作为项目工具。之前使用了grunt.js v0.3,采用grunt-jasmine-runner插件来运行测试,但一直苦于没有合适的覆盖率插件,后来发现了grunt-istanbul,然而两者不能够直接集成,需要对源代码进行改动,以使运行测试的浏览器(比如phantomjs)将生成的覆盖率数据,从内存中传递到grunt的上下文中。

于是,笔者在github上fork了项目,自行将两者整合成为完整的一步式插件grunt-jasmine-coverage,并发布到NPM上。

近期,grunt升级到v0.4, 官方的jasmine插件也换成了grunt-contrib-jasmine。经过笔者一番探索和讨论,发现该插件支持模板系统用于扩展功能,而且已有热心人制作了用于覆盖率的模板grunt-template-jasmine-istanbul。经过与其作者的讨论,现在该模板已经可以很好的支持同时生成多份覆盖率报告。比如,cobertura格式的报告,可以用于Jenkins集成,而lcov格式的报告,可以用于Sonar集成(需要sonar-javascript插件)

有一个小技巧,项目中除了包括自己的源代码,肯定也会包含其他第三方的库,比如jquery。而对于计算覆盖率,我们可能希望将第三方代码踢除出去。这时,在grunt-contrib-jamine的配置中,记得将第三方代码置入vender一节中,这样grunt-template-jasmine-istanbul在做instrument时会忽略掉这些代码。

此外,grunt.js对于nodeunit也有很好的支持

手工安装 grunt.js/npm/phantomJs 一些经验

grunt.js是node.js的一个组件,本身还带有很多plug-in,用于javascript应用程序的构建管理及依赖管理,诸如lint, copy, concat, minify, unit test等,有点象Maven。

有连接外网的机器,可以很方便的利用其本身的依赖管理NPM安装。但是企业内网就有一些限制,下面就如何在无外网连接的linux主机安装grunt.js等进行说明。

 

主机是centOS,之前已经安装了RPM包的gcc。

下载node.js源码,拷贝到目标机,然后make,这个还好。

下载grunt.js,同样拷贝到目标机。注意grunt.js应该使用(npm -g install grunt)作为全局安装。在linux系统下,结果是在/usr/lib/node_modules下。在winXP下,是在 C:\Documents and Settings\<username>\Application Data\npm\node_modules 下。其他grunt的plugin,如果不是全局module,一般都是安装在项目下,即<project>/node_modules。

 

我们希望grunt的这些module能被目标机的多个项目共享,毕竟没有网络,也不想拷来拷去。在https://npmjs.org/doc/link.html 提供了一种npm link的方法,不太好使。其实不用它这么麻烦。将所需的grunt module都拷到目标机的某个目录下的node_modules,然后直接在每个项目下新建个指向该目录的符号链接:

cd <project>

ln -s <somewhere>/node_modules/ node_modules

 

ok,在项目下运行grunt吧!

(要是使用了jasmine进行单元测试,还需要安装grunt-jasmine-runner模块及PhantomJS)

  1. Grunt.js/node.js should be quite easy toonce-click install if this centOS5 server has a internet connection, but, forthis server, I have to copy the .tar manually, then copy all modules andplugins by finding the right place to put

  2. Grunt-jasmine-runner require phantomJs to rununit test. At first, I tried to run the complied version for linux, but itrequires libgc v2.9+ , which finally I realize centOS5 can’t afford such highversion (even forcely compile that lib with low version gcc compiler, possiblywill make the low version linux core crash).

  3. Then I tried to compile phantomJs 1.7 manually,which depends on 30+ libs. Those can be download fromhttp://mirror.centos.org/centos/5/os/x86_64/CentOS/then copy to server, and run with rpm command in turn. Fortunately, it works

  4. The last tricky one, the passed unit test failson that server by run ‘grunt jasmine’.

<pre>Running “jasmine” task

Testing jasmine specs via phantom

[D] [“phantomjs”,”onResourceReceived”,”GET http://127.0.0.1:8888/test/spec/CommonTest.js"]
[D] [“phantomjs”,”onError”,”ReferenceError: Can’t find variable: describe”,[{“file”:”http://127.0.0.1:8888/test/spec/CommonTest.js","line":31,"function":""}]]
ReferenceError: Can’t find variable: describe
…</pre>

At last, it’s caused by that: I createda symbolic link for “node_modules” to re-use common module, but it preventgrunt-jasmine-runner to fetch sth. (not sure yet). To resolve it, need to copythe “node_modules” files directly under project folder.

基于HTML5的EasyXDM组件实现浏览器兼容的跨域资源请求

同源策略(http://baike.baidu.com/view/1580195.htm)指浏览器客户端的javascript脚本,只被允许访问来自同一站点的资源,阻止来自其它站点可能怀有恶意的资源。这种策略在保证安全的同时,也带来了站点间交互的诸多限制。一直以来,存在多种解决方案来实现跨域资源请求,比如基于flash或基于iFrame等等,web开发者需要选择不同的方案来兼容各种浏览器。

HTML5 XDM

近年不断发展的HTML5规范对XDM-跨文档消息传送机制进行了规范,利用postMessage()(http://www.whatwg.org/specs/web-apps/current-work/multipage/web-messaging.html#dom-window-postmessage)函数可以简单稳妥的实现对其他窗口及站点的跨文档消息传送XDM(cross-document messaging)。

postMessage(message, targetOrigin, transfer)函数有三个参数。第一个message表示要发送的消息,第二个参数表示消息接收方所在域。第三个参数是可选的,用于渠道信息发送。

比如下面的例子简单演示了使用postMessage()向内嵌iframe发送一条消息,并指定来源自www.infoq.com,防止其中文档来源被悄悄改变。

var iframWindow = document.getElementById(“theFrame”).contentWindow;

iframWindow.postMessage(“private info”, “http://www.infoq.com“);


 

接收方www.html5.org收到XDM消息会异步触发window对象的message事件。首先验证消息来源是已知的域www.infoq.com,在处理完消息后,向发送方回复一条消息。

window.addEventListener(“message”,function(event) {

if (event.origin == “http://www.infoq.com“) {

processMessage(event.data);

event.source.postMessage(“Heard You”, “http://www.html5.org“);

}

});


easyXDM

easyXDM (https://github.com/oyvindkinsey/easyXDM)是一款轻量级开源javascript组件,很好的实现了各种跨域解决方案的封装,来消除同源策略带来的限制,方便应用程序通过javascript API进行跨域通讯。easyXDM借助传输栈允许两个窗口双向交换基于字符串的消息,支持双向通讯、可靠性、队列和发送方验证等。对于支持HTML5的浏览器如IE 8+, Opera 9+,Firefox 3+, Safari 4+, Chrome 2+等直接采用HTML5的postMessage(),对于其他非HTML5浏览器,则根据当前浏览器优先选择最有效的方法实现跨文档消息传送,比如加载swf文件组件,创建iFrame等。

 

easyXDM包含两个基本对象。

easyXDM.Socket对象包装了传输栈,允许在通讯的消费者(客户端)和提供者(服务端)之间通讯,并定义一系列属性用于异步事件。

服务端首先建立Socket:

var socket = new easyXDM.Socket({

onMessage:function(message, origin) {

//do something with message

}

});


客户端创建Socket并进行连接:

**var** socket **= new** easyXDM.Socket({

remote:http://path.to/provider/“, // the path to the provider

onMessage:function(message, origin) {

//do something with message

}

});

socket.postMessage(“hello world”);


 

easyXDM.Rpc对象允许创建带有代理对象进行远程方法调用。Rpc与Socket使用相同的传输栈和配置属性。

在服务端配置被调用的远程对象:

var rpc = new easyXDM.Rpc({},

{

local: {

helloWorld: function(one, two, three, successFn, errorFn){

// here we expose a simple method with three arguments

// that returns an object

return {

this_is: “an object”

};

}

},

remote: {

helloWorld:{

// here we tell the Rpc object to stub a method helloWorld for us

}

}

});


在客户端可以进行基于JSON的远程方法调用:

rpc.helloWorld(1,2,3, function(response){

// here we can do something with the return value from helloWorld

}, function(errorObj){

// here we can react to a possible error

};


 

跨域资源请求

在web开发中跨域资源请求相当常用。借助XDM,可以将资源内容作为消息内容来传送。

下面举例如何使用easyXDM来跨站点文件请求:站点client.com/index.html获取remote.com/list.xml 这种典型场景。首先在remote.com放置代理,注意这个代理是纯HTML文件,例如remote.com/agent.html

 

在客户端client.com/index.html引用easyxdm.js后,添加以下代码,发起远程连接并准备好回调函数来处理XML文件内容。

 

var socket = new easyXDM.Socket({

remote: “http://remote.com/agent.html,

onMessage:function(message, origin) {

var data = $.parseXML(message);

//do something with message data

}

});


 

在被请求的远程服务端remote.com/agent.html,添加以下代码:list.xml的内容将被缓存并发送

$.ajax({

type: “GET”,

url: “list.xml”,

dataType: “text”,

success: function (data) {

var socket = new easyXDM.Socket(

{

onReady: function(message, origin){ }

}

);

socket.postMessage(‘’ + data);

}

});


 

 

小结:easyXDM开源组件封装和支持HTML5的postMessage()和其他传统跨文档消息传送XDM方案,允许web开发人员可以方便安全的实现跨域资源请求,并方便的兼容不同的浏览器。

Calatrava:自由构建UI的跨平台移动框架

移动是未来计算的趋势,越来越多的人使用移动设备来访问互联网。但是目前至少三大平台:iOS、Android、移动Web。相比桌面Web,移动用户需要更好的体验、界面和设计。然而移动设备受限于电池、不可靠的网络连接和小尺寸屏幕。

几个月前,Martin Fowler写过一篇关于开发跨设备移动应用的文章。他指出,要获得最佳用户体验,最直接的办法就是针对每个移动平台开发一个本地应用,但这却将带来极大的成本。或者选用跨平台工具箱,“一次编写随处运行”?然而没有几个产品真正获得成功。Web应用程序倒是可以以较低成本运行在跨平台之上,但是用户界面又受到限制。因此,不可避免地要在用户体验和成本之间做出选择。

或者,折中的办法就是开发混合式(hybrid)应用程序:结合Web和原生应用。

ThoughtWorks 探索这条路并取得一些进展,最近发布了Calatrava开源框架。Calatrava的思路是开发者用跨平台的JavaScript编写客户端逻辑,这部分代码完全相同,运行在iOS、Android、移动Web的JavaScript解释器中。Calatrava提供本地桥接,允许逻辑来驱动原生UI。

它将移动应用程序分成两部分来考虑:UI和客户端逻辑主体(headless-body)。开发者使用JavaScript代码开发通用的控制器逻辑,而原生代码处理UI部分生成原生外观(native veneer),可移植的JavaScript代码和原生代码之间能够互操作,进而开发出混合移动应用。

Calatrava本身并不提供任何UI框架或建立UI抽象层。在iOS上,就用Objective-C和Cocoa Touch框架来构建UI;在Android上,就用Java和Android库;在移动Web上,就用HTML5、CSS3和你喜欢的JavaScript库。跨平台逻辑层与代表着应用表现层Page对象进行交互,该对象提供了从显示和交互机制中分离出的API接口。很多时候,对于移动应用来说,HTML5 UI已经足够好,所以Calatrava也允许在iOS和Android上桥接HTML5,但是应用绝不会绑定在HTML UI上。

这样,每个平台都有具有了自己独有的UI设计,避免了“恐怖谷效应”(uncanny valley effect)。应用开发可以从HTML UI开始,当你觉得某些地方不够好的时候,就用原生UI替换掉,且只对部分平台进行替换,而不影响其他部分。同时你也可以享受某些平台特有的UI特性。

Calatrava适合包含较多复杂的客户端逻辑的应用,且当应用偏重于成为现有产品的新渠道(Channel),而非产品本身。如果应用的大部分代码是UI部分(如游戏),或是已经提供非常好的用户体验的Web应用,Calatrava就不太适合了。 Calatrava编写的核心JavaScript逻辑支持使用Jasmine进行单元测试,以及使用cucumer.js进行功能测试。

想试试吗?从github的Caltrava主页可以找到说明,简单来说就是下面几步:

  1. 安装依赖:Node.js, Ruby, Xcode, Android.
  2. 安装Ruby插件gem install calatrava
  3. 使用Calatrava工具创建项目calatrava create sample
  4. 编译并运行项目
    Calatrava还处在早期开发阶段,估计还是会有很多bug,另外插件、模块和文档也需要完善。

Martin Fowler分析了用户体验和可负担能力之间的动态平衡,他认为混合式(hybrid)解决方案介于纯本地应用和纯Web应用之间,更适合增量式发布。即首次发布(release)采用纯Web UI,之后的发布(release)逐步将Web UI转为本地UI特性,或逐步地增加本地UI特性的比例。

MartinFowler认为Caltrava最有价值的地方就在于它适合增量式发布策略。比如cover-your-bases策略,即当你已经有大量用户基础,而移动应用定位为现有产品的新渠道(channel)。由于现有的用户,最重要的事情是将新渠道尽快地推到尽可能多的用户面前。很明显,平台覆盖率最重要。然而在移动平台上,体验也非常重要,所以应该用最小功能集提供简化的体验,而不是提供退化的体验。

它将移动应用程序分成两部分来考虑:UI和客户端逻辑主体(headless-body)。开发者使用JavaScript代码开发通用的控制器逻辑,而原生代码处理UI部分生成原生外观(native veneer),可移植的JavaScript代码和原生代码之间能够互操作,进而开发出混合移动应用。

Calatrava本身并不提供任何UI框架或建立UI抽象层。在iOS上,就用Objective-C和Cocoa Touch框架来构建UI;在Android上,就用Java和Android库;在移动Web上,就用HTML5、CSS3和你喜欢的JavaScript库。跨平台逻辑层与代表着应用表现层Page对象进行交互,该对象提供了从显示和交互机制中分离出的API接口。很多时候,对于移动应用来说,HTML5 UI已经足够好,所以Calatrava也允许在iOS和Android上桥接HTML5,但是应用绝不会绑定在HTML UI上。

这样,每个平台都有具有了自己独有的UI设计,避免了“恐怖谷效应”(uncanny valley effect)。应用开发可以从HTML UI开始,当你觉得某些地方不够好的时候,就用原生UI替换掉,且只对部分平台进行替换,而不影响其他部分。同时你也可以享受某些平台特有的UI特性。

Calatrava适合包含较多复杂的客户端逻辑的应用,且当应用偏重于成为现有产品的新渠道(Channel),而非产品本身。如果应用的大部分代码是UI部分(如游戏),或是已经提供非常好的用户体验的Web应用,Calatrava就不太适合了。 Calatrava编写的核心JavaScript逻辑支持使用Jasmine进行单元测试,以及使用cucumer.js进行功能测试。

想试试吗?从github的Caltrava主页可以找到说明,简单来说就是下面几步:

  1. 安装依赖:Node.js, Ruby, Xcode, Android.
  2. 安装Ruby插件gem install calatrava
  3. 使用Calatrava工具创建项目calatrava create sample
  4. 编译并运行项目
    Calatrava还处在早期开发阶段,估计还是会有很多bug,另外插件、模块和文档也需要完善。

Martin Fowler分析了用户体验和可负担能力之间的动态平衡,他认为混合式(hybrid)解决方案介于纯本地应用和纯Web应用之间,更适合增量式发布。即首次发布(release)采用纯Web UI,之后的发布(release)逐步将Web UI转为本地UI特性,或逐步地增加本地UI特性的比例。

MartinFowler认为Caltrava最有价值的地方就在于它适合增量式发布策略。比如cover-your-bases策略,即当你已经有大量用户基础,而移动应用定位为现有产品的新渠道(channel)。由于现有的用户,最重要的事情是将新渠道尽快地推到尽可能多的用户面前。很明显,平台覆盖率最重要。然而在移动平台上,体验也非常重要,所以应该用最小功能集提供简化的体验,而不是提供退化的体验。

原文:http://www.infoq.com/cn/articles/calatrava

JavaScript创始人Brendan Eich:我们不需要Native Client

Native Client 是Google支持的开源技术,用来在浏览器中像桌面程序一样运行编译过的代码,满足人们对web应用程序的可移植性和安全性的期望,提供更好的富客户端用户体验,允许开发者编写更强大的移动Web应用程序。
JavaScript创始人Brendan Eich上个月在旧金山召开的O’Reilly Fluent ConfereNative Cliente大会上解释说JavaScript足以满足Google对Native Client的设计目的,并怀疑Native Client是否能够像JavaScript一样,得到浏览器厂商的广泛支持

继续阅读 More