w3ctech 2011 JavaScript专题会议(上海站)最近在张江畅星大厦召开,参会者200多人,来自国内技术社区的四位知名专家高博、权一、杜欢、贺师俊分别做了精彩的演讲,涉及的内容包括测试驱动开发、ES5新特性、iOS上的Web应用、Javascript框架API设计思想等。InfoQ中文站整理了大会的精彩内容,供读者参考。
测试驱动的JavaScript开发入门
来自盛大创新院的高博以自己翻译的书“测试驱动的Javascript开发”为背景,结合在测试工作中的实践经验,介绍了测试驱动在Javascript开发中的应用。
什么是测试驱动开发?高博指出,在传统的开发过程中,大家遵循着先开发后测试的阶段模式,按部就班地需求分析、设计、开发、测试、发布。而在测试驱动开发过程中,测试环节位于开发环节之前,甚至取代了设计环节。测试用例代码完成在先,实际产品代码完成在后,这意味着第一行代码是测试用例代码。测试的结果是开发进度的指挥棒,测试推进到哪里,开发才能跟进到哪里。测试的结果是“失败“,才能继续进行开发工作,直至测试的结果成为”通过“为止。
高博举了一个有关Javascript的测试驱动例子,需求是”开发一个JavaScript字符串函数,实现去除字符串开始和结尾的空白字符的功能”。如何编写第一个测试用例?他认为首先要对需求分解,在这个例子中,可以把”开头“和”结尾“当成两个小的需求,另外不能忽略隐含的需求,比如假定被测试的函数名为trim,那么第一个测试用例可能是判断其在作用域中是一个函数:typeof "".trim == "function"。回到刚才分解的两个需求上来,针对每一个需求写一个测试用来,第一个看起来像这样:
"test trim should remove leading white-space": function () { assert("should remove leading white-space", "a string" === " a string".trim()); }
即测试去掉开头的空白字符。接下来,运行该测试用例,因为没有还没有定义trim,所以测试结果是失败的,不过在测试驱动的开发中,测试用例的失败结果才是开发能够跟进的唯一动力。可是,为什么明知道测试会得到失败结果,还是要运行一次测试用例?高博指出,这样做的一个原因是测试在当前作用域下存在函数冲突。
接下来实现第一个小需求:
String.prototype.trim = function () { return this.replace(/^\s+/, ""); };
高博特别强调,在开发产品代码时,要暂时忘掉原始的需求,现在的唯一目的是让当前测试用例的结果从失败变成通过。在通过了第一个小需求之后,编写第二个测试用例,并修改产品代码使其通过第二个测试。在每一步撰写了更多产品代码后,高博提醒开发人员要做两件事:
最终的函数定义为:
String.prototype.trim = function() { return this.replace(/(^\s+)|(\s+$)/, ""); };
在测试驱动开发中,存在着一个循环,即”撰写引起新的失败结果的测试用例“——>”撰写新的产品代码以使之前的测试用例通过“——>”重构以得到更好的等价产品代码“——>”撰写引起新的失败结果的测试用例”——>......在这里,测试用例是循环的第一推动力,测试用例在每轮循环的起始代表着需求分析的分解结果,开发过程表现为满足测试用例的活动,测试用例在每轮循环的结束代表着开发规格。高博总结说,测试驱动将难以估算的抽象需求转换为一系列明确的、无二义性的规格。由测试驱动的开发过程是增量式的,由测试驱动的开发无须等到全部的需求明确即可开始,遵循不做无用功原则。测试驱动的本质是将测试用例分别作为需求和规格加入开发过程首末两端的持续集成技术。
为什么要将测试驱动的开发方法引入JavaScript开发呢?高博分析了Javascript语言的特点:极其灵活,书写风格几乎无穷无尽;操作对象复杂,需要面对各种物理和逻辑对象;可以和各种动态语言混合,作为它们的就地(in-place)输入或输出;虽然有标准可循(ECMA-262),但是各种环境对标准支持的程序区别很大;解释型语言,调试的工具和手段比较缺乏。由此导致开发人员容易防御性地过度设计,在开发过程中对产品代码的信心不足。而这些不足恰好是测试驱动开发的用武之地:科学地设计测试用例及其自动化,逐步覆盖需求;每通过一个或一组测试,开发者信心就增强一分。
对于单元测试,高博建议:积累测试用例集,形成回归测试的规模和覆盖率;欲修改产品代码,首先更新测试用例;测试用例彼此独立,避免耦合;只测试 JavaScript,不测试与外部依赖项目的耦合部分(数据库、文件系统、用户界面等);测试用例的建立要多快好省;产品代码要避免形成测试障碍和阻塞(如弹出对话框、Ajax异步调用等)。
对于性能测试,高博认为通过Javascript性能测试,开发人员能够在问题的多种解决方案中,比较优劣。性能测试结果使JavaScript的开发避免了盲目“优化”性能,以及无限制地“改进”性能。
对于当前火热的Node.js技术,高博介绍了NodeUnit工具,并提供了测试示例。除此之外,他还介绍了Stub和Mock,Stub能够像真实的被测试环境一样提供根据输入给出对应输出的模拟对象。通常实现比真实对象要简单,但是从给定的测试用例集合的输出结果看不出它与真实对象的区别。Mock是一种输出结果预知的测试对象。Stub仍然是先执行测试,再得到输出(该结果可能是受控的),而Mock则是直接给出测试输出。Mock对象常用于测试的输出结果不常见的场景,以求得更高的测试覆盖率。
ECMAScript Edition5 尝试
来自盛大在线的Web前端工程师权一为大家讲解了ECMAScript 5引入的重要新特性,虽然该版本早在2009年12月份已经发布,但是由于浏览器的兼容性问题,一直让大家对ECMAScript 5敬而远之。随着浏览器版本的更新,特别是最近Node.js的兴起(其依赖的V8引擎支持ECMAScript 5),开发人员开始尝试应用于实践中。权一目前正在对ECMAScript 5规范进行中文注解,所以他的讲座内容很全面。
目前,ECMAScript 5的支持情况较好的浏览器包括Firefox4+、Chrome11、IE10PP2,还有后端JS运行时NodeJS(V8)。
权一首先分析了ECMAScript 5引入的新API,包括:
在这些API中,经常会遇到一个概念,即属性描述符。它是用于解释某一个被命名的属性具体操作规则的特性集。 属性描述符中的对应每个字段名都会有一个值,其中任何一个字段都可以缺省或显式的设置。 属性描述符还会被进一步以字段的实际用途来分类成数据属性描述符访问器属性描述符。比如,在Object.create和 Object.defineProperty方法的参数中会涉及到属性描述符。
数据属性可以可以存储和获取值,其描述符包括:
数据属性的代码示例如下:
var obj = {}; Object.defineProperty(obj, "newDataProperty", { value: 101, writable: true, enumerable: true, configurable: true });
访问器属性(accessor)在每次存储或者获取属性值时都会调用用户提供的函数,描述符包括:
访问器属性的示例代码如下:
var obj = {}; Object.defineProperty(obj, "newAccessorProperty", { set: function (x) { document.write("in property set accessor" + newLine); this.newaccpropvalue = x; }, get: function () { document.write("in property get accessor" + newLine); return this.newaccpropvalue; }, enumerable: true, configurable: true });
权一指出,新增加的Object.create、defineProperty、freeze、seal、preventExtensions等方法会让开发人员在使用Javascript进行面向对象编程时更加方便。
ECMAScript 5还引入了严格模式(use strict),能够更好地执行错误检查。use strict可以声明一个文件、程序或者函数,这种声明方式称之为指令序言,它的作用域可以是整个程序,也可以是某个函数体内。
严格模式限制的内容主要包括:
Build Web Apps for iOS
Cisco CSG前端架构师杜欢的演讲题目是《Build Web Apps for iOS》,不过从其讲座内容来看,是以iOS为例,讲述了Web应用在CSS3、HTML5、UI设计思想中的经验。
对于设计原则,杜欢强调了以下几点:
在实现设计风格时,我们需要重点考虑的地方是这个应用的目的是什么,所有的设计是否为此而存在,所有呈现给用户的功能是否是用户期望的,不走“混搭风”。保持UI的一致性能够降低用户的学习成本、增强应用的整体性。在渐进增强处理时,需要考虑设备的输入输出和特性支持,以及不同浏览器的差异。
杜欢以iOS设备为例,使用条件式CSS来指定特定样式:
<link media="only screen and (max-device-width: 480px)" href="small-device.css" type= "text/css" rel="stylesheet">
对于页面的视窗(Viewport)设定,可以参照设备的显示宽度:
<meta name ="viewport" content = "width = device-width">
杜欢还提到了在wifi不工作的离线状态下,如何开发离线应用,即利用缓存清单MANIFEST:
MIME type = text/cache-manifest <html manifest=“resource.manifest”>
如果需要存储离线数据,则可以通过离线存储,如本地存储WEB Storage、WEB SQL、IndexedDB。
在演讲最后,杜欢总结了iOS开发中需要注意的一些限制:
关于JavaScript库和框架的API设计的思考两三例
资深Web开发专家贺师俊在讲座中对API的设计和评判标准做了深入的探讨。他首先借用了《软件框架设计的艺术》中的“无绪”(Cluelessness)概念,软件工程中的无绪是指:程序员无需深入了解很多内容,也可写出好的代码找到一种编程实践方法,让开发人员不用深入了解所有事情,即选择他们所需的知识。API是对所需知识的抽象,它将系统的复杂性隐藏起来。
评价API好坏的标准包括:
贺师俊以jQuery为例,其中的Selector体现了可理解性,$体现了可见性,而相比Dojo、YUI,jQuery在兼容性方面表现出色。接着,他又举了Pyhon框架、Grails、Prototype.js、短命名等例子来分析API的设计问题,让读者有了更深的理解。他用较长的篇幅分析了 Lazy function模式在API设计方面的缺陷,并强调不能以防御式编程和提供文档为借口来降低API的质量。
最后,贺师俊总结API设计的一些经验:
没有参会的朋友可以通过主办方w3ctech的相关页面下载幻灯片和观看演讲视频。InfoQ中文站将继续关注国内Web技术社区的发展。