编写可测试的JavaScript代码

978-7-115-37337-3
作者: 【美】Mark Ethan Trostler
译者: 徐涛
编辑: 陈冀康

图书目录:

详情

本书介绍如何为已有的JavaScript代码进行良好的测试,设计实用的测试计划,JavaScript程序员和开发者将由此提升代码的可维护性,从而受益。本书试图在良好开发实践和JavaScript之间建立一个桥梁。本书试图结合测试和质量保证(QA)方面几十年的研究和经验教训,将这些经验教训应用于JavaScript。

图书摘要

版权信息

书名:编写可测试的JavaScript代码

ISBN:978-7-115-37337-3

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [美] Mark Ethan Trostler

  译    徐 涛

  责任编辑 陈冀康

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Simplified Chinese Edition, jointly published by O’Reilly Media, Inc. and Posts & Telecom Press, 2015. Authorized translation of the English edition, 2013 O’Reilly Media, Inc., the owner of all rights to publish and sell the same.

Copyright© 2013 by O’Reilly Media, Inc.

All rights reserved including the rights of reproduction in whole or in part in any form.

本书中文简体版由O’Reilly Media, Inc.授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式复制或抄袭。

版权所有,侵权必究。


JavaScript专业开发人员必须具备的一个技能是能够编写可测试的代码。

本书的目标是教授如何为客户端和服务器编写和维护可测试的JavaScript代码。全书共8章,首先介绍了什么是可测试的代码,然后分别从代码复杂度、架构、单元测试、代码覆盖率、集成测试、性能测试、负载测试、调试和自动化等角度详细介绍。书中丰富的示例能够帮助读者从头开始养成好的代码编写习惯,提升测试效率及代码的可维护性。

本书的作者曾先后在YouTube、Yahoo!以及Google等公司从事前端开发及测试相关工作,具有丰富的经验。本书主要针对那些想成为JavaScript专业开发人员的读者,也适合初、中级或者专家级的开发人员参考阅读。


O’Reilly Media通过图书、杂志、在线服务、调查研究和会议等方式传播创新知识。自1978年开始,O’Reilly一直都是前沿发展的见证者和推动者。超级极客们正在开创着未来,而我们关注真正重要的技术趋势——通过放大那些“细微的信号”来刺激社会对新科技的应用。作为技术社区中活跃的参与者,O’Reilly的发展充满了对创新的倡导、创造和发扬光大。

O’Reilly为软件开发人员带来革命性的“动物书”;创建第一个商业网站(GNN);组织了影响深远的开放源代码峰会,以至于开源软件运动以此命名;创立了《Make》杂志,从而成为DIY革命的主要先锋;公司一如既往地通过多种形式缔结信息与人的纽带。O’Reilly的会议和峰会集聚了众多超级极客和高瞻远瞩的商业领袖,共同描绘出开创新产业的革命性思想。作为技术人士获取信息的选择,O’Reilly现在还将先锋专家的知识传递给普通的计算机用户。无论是通过书籍出版、在线服务或者面授课程,每一项O’Reilly的产品都反映了公司不可动摇的理念——信息是激发创新的力量。

“O’Reilly Radar博客有口皆碑。”

——Wired

“O’Reilly凭借一系列(真希望当初我也想到了)非凡想法建立了数百万美元的业务。”

——Business 2.0

“O’Reilly Conference是聚集关键思想领袖的绝对典范。”

——CRN

“一本O’Reilly的书就代表一个有用、有前途、需要学习的主题。”

——Irish Times

“Tim是位特立独行的商人,他不光放眼于最长远、最广阔的视野并且切实地按照Yogi Berra的建议去做了:‘如果你在路上遇到岔路口,走小路(岔路)。’回顾过去Tim似乎每一次都选择了小路,而且有几次都是一闪即逝的机会,尽管大路也不错。”

——Linux Journal


Mark Ethan Trostler有超过20年编写和测试代码的经验。从在加利福利亚大学圣迭戈分校主修计算机科学/哲学两个专业到就职于创业公司(IPivot)和高通、英特尔、Redback Networks、Juniper Networks、Yahoo等大公司,目前在谷歌就职,Mark一直致力于提高代码质量,之前在Yahoo! Mail团队任职高级首席前端工程师,他为Yahoo! Mail最新改版中的先锋测试和质量控制提供了帮助。

他目前受雇于谷歌,在Ads团队任职软件测试工程师,帮助确保实现优秀的客户体验。他在一些内部和外部会议(Yahoo! TechPulse 2010和2011、Yahoo!’s Front End Summit、以及YUIConf 2011)上多次讲过关于可测试JavaScript方面的内容,并且也是Open Source Javascript Unit Test Environmenthttps://github.com/zzo/JUTE)的作者。转战JavaScript之前,Mark广泛参与过Perl、Java和C语言的开发工作,但现在,他已经痴迷于JavaScript并做了全职转换。使用NodeJS和PhantomJS,便携式语言的未来就在这里!

《编写可测试的JavaScript》封面上的动物是一条小带刺尾鱼(英文:Doctor fish;学名:Acanthurus chirurgus),属于热带鱼,在整个大西洋的浅珊瑚礁都可见其踪影,尤其是在马萨诸塞州、加勒比地区、巴西以及非洲的西海岸。它是其种群中分布最广泛的鱼类。刺尾鱼科鱼种的特点是其两侧尾鳍都带有尖刺,Acanthurus来源于希腊语“带刺的尾巴”。小带刺尾鱼的通用名或许源于其柳叶刀形状的附属肢体,它们通过快速摆动尾巴来防御捕食者或其他的刺尾鱼。

这种鱼类最大体长40厘米,体呈蓝色或棕色。尽管颜色不同,但所有小带刺尾鱼的体侧都有10~12条纵纹。它们的牙齿形状特殊,可以从岩石上刮食藻类和其他植物,它们白天大部分时间都是以这种方式进食。有时,小带刺尾鱼也会吃沙,这有助于在消化过程中磨碎食物。

小带刺尾鱼受精后,会在24小时内产卵,鱼卵在短时间内看起来像浮游生物。这种鱼类长到13毫米长之前不会长脊椎,9个月后达到性成熟。

封面图片版画,来源未知。


既然要对代码进行测试,那么为什么不让这一过程变得尽可能简单和轻松呢?JavaScript客户端代码测试之所以尤其困难,是因为我们几乎无法控制代码运行的环境。多种类型的操作系统、多个版本的操作系统、多种类型的浏览器、多个版本的浏览器,更不用说插件、扩展、多语言版本和缩放大小了,还有一些未知内容,所有这些因素交织在一起,阻碍着应用程序的性能。这些因素会导致程序变慢、中断、崩溃,最终覆灭。这里面的内容纷繁复杂!服务端JavaScript给了我们更多的控制权,以便我们能够从总体上控制执行环境。然而,Rhino和Node.js应用程序不像其他语言一样有完整的成熟工具、测试程序以及生态系统。此外,Node.js的异步特性也使得测试变得更加复杂。有趣的是,这样一种与异步执行密切相关的语言,竟然没有设置与该执行模式相配的内置支持。

无论如何,测试——尤其是JavaScript测试——是很复杂的。克服这种复杂性的最好办法是完全控制自己实际所控制的东西:代码。代码是连续存在的,一方面是从别人的代码到自己的代码,另一方面是从遗留代码到非遗留代码。

什么是遗留代码(legacy code)?我比较推崇Michael Feathers在他的优秀作品Working Effectively with Legacy Code(Prentice Hall出版社)中的定义:遗留代码是没有测试过的代码,这段代码将无法存活或永远不会被任何人接触到。再次接触遗留代码时,就是要重写它了。看一下当前的项目,任何没有被测试的代码都有可能会被重写。重写的人可能不是原作者,而是负责处理这个任务(增强代码或修补漏洞)的人。除非这些代码经过测试,否则它们就是必须要重写的无用代码。这段代码可能很惊人,但它唯一能存活的方法就是永远不会产生Bug,并且没有人要求对它进行增强或者添加新特性。即便如此,你愿意将这些未经测试的产品代码推到市场上吗?即使代码之前“能用”,之后你还能继续满意吗?拥有该代码的公司也是同样满意吗?因此,通常的结果都是付费重写。公司不得不再次付费进行代码重写,这真是太糟糕了,但这就是遗留代码的情况。

从图P-1所示的矩阵图中可以看到,遗留代码非常容易被别人重写。相比对别人的遗留代码进行测试,这种方式通常要好些。由于代码时常会易主,归你管或不归你管的情况都极容易发生,所以在矩阵中左右移动是很容易的。“向下”移动是编码中最难的一步,没有人愿意为已有的代码编写测试,相反,绝大多数人都会强力避免它——通常的结果是进行全部重写。

图P-1 当前代码与遗留代码

可惜的是,在这个矩阵中“向上”移动需要遵守一些规则。原先经过测试的代码如果被低手接管就会变成遗留代码。由于越来越多的增强和新功能需要引入,所以要时刻保持对代码测试的更新,但这一过程比为完全没有测试(或非常少的测试)的代码编写测试简单多了。

本书的目标是通过采用一个整体的开发方法让编写的JavaScript代码处于图P-1的右下象限。这不仅是“测试编写”或“测试先行”,而是让大家了解到,在编码时所做出的选择,不管是好是坏,都将影响你后续的工作(代码和使用)。

先从养成在语法和语义上构建代码以实现可测试性的良好习惯开始,在正确的时间编写正确的测试,定期运行这些测试并查看测试结果,这样会让代码时刻保持在矩阵的右下象限。

本书试图在良好开发实践和JavaScript之间建立一个桥梁。JavaScript是一种古怪的小众语言,从其毫无意义的名称就可以看出,JavaScript最初用于非编程人员向Web页面添加一些互动。在语言建立之初,即使是“真正”的程序员使用,也得花一些时间才能适应其语言、DOM和浏览器环境之间的动态性。

随着越来越多的专业程序员开始使用JavaScript语言,这种语言的最佳实践开始被理解和编纂。JavaScript调试器、测试工具、IDE等更是开始不断涌现。该语言本身也是历经了多年的修改。JavaScript开始发展并茁壮成长,但是依然有很多怪异之处,而且也还没有出现更强大的工具。

随着Node.js、PhantomJS这类服务器端JavaScript的出现,现在一个应用程序可以全部用JavaScript来编写了。而在这之前,这不仅是不可能的,而且也被认为是疯狂的。但现在没有人再笑话这种做法了!

本书试图结合测试和质量保证(QA)两个方面几十年的研究和经验教训,将这些经验教训应用于JavaScript。本书中几乎所有的示例和代码片段都是用JavaScript编写(偶尔有一些Perl)。

本书主要目标受众是那些想成为JavaScript专业开发人员的人,初、中级或者专家级的开发人员都适合阅读本书,因为每个人都可以从本书获取有用的知识。

JavaScript可能不是你所使用的唯一语言,但你在编写或测试程序时要用到大量JavaScript。如果有人付费(希望是)让你编写JavaScript代码在浏览器里运行,或者幸运的话,在服务器端运行,如果你每天都用JavaScript编写不同大小项目的话,本书正是你的不二选择。

如果你加入了一个必须要测试JavaScript的QA或工具团队,那么本书的第3章到第7章非常值得一读。本书的目的是使测试尽可能变容易,进而全部自动化。希望这本书能使大家的工作更轻松,这就是我要达到的目的。

如果你编写JavaScript不太多,这本书仍会为你提供很多有用的信息——特别是复杂度(第2章)、基于事件的架构(第3章),以及调试(第7章)这几章。注意,其余各章也有很多有用的信息,但它们可能不会直接解决你的问题。我遇到的众多难题促使我撰写了这本书——我从之前的错误和努力工作中学到了很多,所以你也应该这样!从头开始养成好习惯,将会让你更富有成效并感到快乐。

遗憾的是,本书并不是适合所有的人。如果你有兴趣学习JavaScript,建议先从其他地方学习一些该语言的基本知识,然后再回到本书。如果你已经能够编写整洁、零Bug的代码,且这些代码有充分的文档和注释,能够自动化构建,且连续运行所有单元测试和集成测试,并能够生成完整的代码覆盖率(code coverage)报告,自动部署到生成环境,这样的话,本书对你可能就没多大用途了。如果不得不进行代码调试的话,可以快速看一下第7章,或者可以看一下第6章,了解一些小技巧。

如果你不经常用JavaScript,现在就可以合上本书了。

我转向JavaScript开发的时间不算长,之前做了很长一段时间的Perl开发。当然,我使用JavaScript也有十年以上了,但从未真正把它当回事儿。以前认为做出“炫酷”的效果和展示其他看似无用的UI技巧仿佛都是很有意义的事情。

Douglas Crockford在Crockford on JavaScript中做出的精彩论述真正打开了我在语言深度和复杂性方面的视野,该视频可以在YouTube的YUI库频道上找到。如果你需要进一步确信,或者你的一些朋友让你对JavaScript感到悲观,看一下这些视频会影响你的心态变化。

我作为前端开发人员在Yahoo! Mail工作了两年半。在我任职期间将它的代码整个重写了。我确信我们团队在重写JavaScript客户端的时候,经历了所有可能遇到的问题、争议、麻烦、灾难,最后取得了胜利。这本书的大部分内容都来自这段经历。

目前,我是Google的一名软件开发测试工程师,我会好好利用这些来之不易的经验教训,希望你也可以。

从这本书中可以学到两点:正确的方法和错误的方法!当然,还有“正确”和“错误”之间的联系。读完本书之后,我希望,不管是自己编还是看别人的代码,你都能知道为什么觉得代码好还是不好。如果你已经可以找出哪些是错误的代码,那么这对你来说是件好事。当我第一眼看到代码的时候,不管是我写的还是别人写的,我都能很快判断出代码是好还是坏:要么是立即理解代码(好感觉),要么是目光呆滞(坏感觉)。能够表达作者代码的问题是非常好的,希望第2章不仅能在好与坏上提供更具体的解释,也能提供与程序作者进行交流的词汇。

为客户端JavaScript编写单元测试可谓是令人怯步。这意味着有很多人都不会编写。这样就不太妙了,本书(以及其他书籍)中详细说明了各种原因。本书提供了编写单元测试入门所需的工具和代码,而不是蜻蜓点水式的介绍。入门是最困难的部分,本书将指导你为客户端JavaScript编写单元测试。

很显然,仅仅编写测试还是不够的,还要运行它们。理想情况下,任何时候都可以在开发环境中运行这些代码,而且它们也可以作为自动构建过程的一部分。那这些测试的代码覆盖率呢?集成测试、性能测试和负载测试呢?所有这些测试的代码覆盖率呢?负责所有这些测试的持续构建环境是怎样的?最后,如何组织代码,以便让所有的测试和自动化变得更加容易而不是更难?如何既在客户端JavaScript又在服务器的JavaScript上运行测试呢?

这些主题以及更多内容(调试及其他)在本书中都会覆盖到,所以准备好开启JavaScript开发世界的狂野之旅吧。本书的重要主题是编写和维护“可测试”的代码。

本书将在几个步骤内解决如何编写可测试的代码。首先,我们将研究复杂度(complexity)。接着看架构选择,尝试着限制复杂度和耦合度(coupling)。以此作为基础,在功能层面和应用程序层面上继续测试方面的内容。我们将全面了解代码覆盖率和调试(debugging),然后完成自动化相关的所有内容。在本书最后,大家将更全面地理解“什么是”以及“如何进行”可测试的JavaScript。

第1章 可测试的JavaScript

本书的最重要主题是编写和维护“可测试”的代码。可测试的代码是什么?为什么要努力编写它?如何进行编写?我们将从研究这些问题开始,并且了解一些流行的开发理论以及它们和可测试代码之间有何联系。最后,不管是否跟着进行实战,编写可测试代码的关键都在于让代码保持短小、整洁、简单和松耦合。

第2章 复杂度

复杂度是很多问题的根源,不仅仅是可测试性。这些问题包括可理解性和可维护性,这两个因素是代码质量的关键指标。一些系统和应用程序本质上是复杂的,事实上,大多数应用程序都是很复杂的,但在处理和表达这些复杂性时,有正确的方式也有错误的方式。很显然,将复杂的部分分解成一个个更小、更简单的小块是首要步骤。降低耦合度和扇出(fan-out)是管理复杂度的另外两种方式。在探索可测试的JavaScript时,我们会研究所有这些方法,甚至更多内容。

第3章 基于事件的架构

讨论复杂度之后,我们将深入研究基于事件的架构。该应用程序架构可以极大地降低复杂度和耦合度,同时提供简单的方式将应用程序分解成更小、更自足的片段。不管应用程序是用于服务器端还是客户端,或者(很可能)用于两者,基于事件的架构均可以解决第2章中列举的很多问题。即便该架构不适合作为所有应用程序的总体架构,在整体架构中肯定也会有用到基于事件架构的概念和实践的地方。

第4章 单元测试

关于单元测试有很多争论,测试到底有多重要?单元测试并不能发现所有的错误。像其他工具一样,单元测试是可测试性的一部分。描述代码为“可测试”的,并不意味着这些代码的测试用例是可用的;而是说为这些代码编写测试用例比较简单而已。单元测试之所以特殊,是因为通常这是测试开发人员唯一要编写的。它们还具有侵入性,要求测试代码和程序代码隔离,并且可以独立于应用程序运行。这可能会使单元测试变得有难度,因为在隔离环境下独立运行测试代码是非常困难的。本书很大一部分章节都是讲解如何确保代码能够隔离运行,从而使编写单元测试变得更简单。单元测试无法发现所有的Bug(甚至大多数Bug),但它们所找到的Bug验证了运行单元测试确实是值得的。同样重要的是,测试代码要遵循和即将测试的应用程序代码一样的高标准和原则。

第5章 代码覆盖率

代码覆盖率通常与单元测试有关。代码覆盖率是单元测试的一个很好的衡量标准;然而,我们会发现并非总是如此。代码覆盖率不仅仅适用于单元测试,所有类型的测试,包括集成测试、手工测试、性能测试,都可以受益于代码覆盖率。我们将研究代码覆盖率的优势和劣势,以及如何生成、查看代码覆盖率,并使其变得有意义。

第6章 集成测试、性能测试、负载测试

当然,除了单元测试以外,还有很多其他类型的测试。集成测试、手工测试、性能测试、功能测试以及其他类型的测试,在寻找和挖掘Bug的工作中,都发挥着很重要的作用。不管谁做这些测试工作——开发人员、QA团队,亦或是不知情的用户,不管你喜欢不喜欢,都要完成这些类型的测试。将应用程序作为一个整体进行轻松测试的能力也是至关重要的。模块化功能使测试代码能够与实现的功能更密切相关,这有助于开发人员更快地修复Bug。在这些测试中使用代码覆盖可以快速显示黑盒测试期间执行的代码。大量的基于JavaScript的工具可以让开发人员用于集成测试和性能测试,我们将深入研究其中一些工具,给大家一个直观的展现。

第7章 调试

我们编写的代码,第一次编写时不管看起来多完美,都是不完美的。我们的代码肯定会产生Bug,可能有很多的Bug。我们想到的和意想不到的Bug都有可能会破坏代码。我们的测试、其他人的测试或者用户使用程序时都有可能发现Bug。测试时发现的Bug是最容易解决的,这也是最大化测试的一个很好的理由。用户运行程序时发现的Bug更难以追踪,其结果是,不仅要调试自己的代码,还得调试别人的代码。针对Node.js和浏览器代码这两方面,我将分享一些调试的技巧和窍门。要准备一个好用的调试环境,因为我们要经常用到。

第8章 自动化

最后,对于测试,一遍遍地手工操作不仅不可持续,而且非常无趣。软件编程是世界上手工处理过程最多的工作之一,但测试和软件维护却不一定。运行测试、生成代码覆盖率报告、执行静态分析、精简和压缩代码,以及向生产环境或其他环境部署或回滚代码都应该是自动化过程的一部分。自动化可以确保不管发生什么情况,无论是成功还是失败,它会很快地进行处理,更重要的是在某种程度上可以重复操作。程序代码错了,自动化测试就会失败,生产环境运行也会失败,其他事情也会出错,但这些绝对不会关联到我们的程序代码。这就是现实。关键是要从这些失败(连同你造成的故障)中尽快且不着痕迹地恢复回来。

不管你喜欢还是不喜欢本书,都请务必让大家知道。在亚马逊上评论是一种流行的方式,可以分享你的满意度,或者可以在本书的网站(http://oreil.ly/Testable-Java Script)上留下你的评论。

该网站还提供了勘误表链接,请告知我们本书中的文字错误、内容错误以及其他问题。这些勘误提交后将立即可见,我们检查后会给予确认。O’Reilly可以将这些勘误增补在未来的印刷版本和Safari在线图书中,以便给读者提供更好的阅读体验。

编写可测试的代码会让我们自己和效仿我们的人的生活变得更简单。从更少的Bug到更多容易修复的Bug,从容易测试到简单调试,编写可测试的JavaScript是明智之选。

本书将展示通往睿智道路的途径。阅读整本书之后,大家将对编写和维护可测试的JavaScript实际需要方面有非常清楚的了解。但这仅仅是一个开始。我们作为开发人员,必须将这些实践和模式应用到日常工作中。必须抵制住“懒惰”且不编写测试的诱惑,避免走回头路,防止自己或别人来收拾我们的烂摊子。可测试的JavaScript代码将会延续。如果你现在正在编写遗留代码,帮你自己和老板一个忙,开始编写可测试的代码。希望你会发现,这样做并不是很难,而且非常有益,甚至非常有趣!

如果您对本书有意见或想提出问题,请联系出版社:

美国:

O’Reilly Media, Inc.

1005 Gravenstein Highway North

Sebastopol, CA 95472

中国:

北京市西城区西直门南大街2号成铭大厦C座807室(100035)

奥莱利技术咨询(北京)有限公司

我们已将本书做成一个网页,我们在那里列出了勘误表、示例和额外信息。可以打开http://oreil.ly/Testable-JavaScript访问这个页面。

如需对本书发表评论或提出技术问题,请发送电子邮件至:bookquestions@oreilly.com。

欲获得有关本书、课程、会议及新闻的更多信息,请访问我们的网站:http://www.oreilly.com

在Facebook上找到我们:http://facebook.com/oreilly

在Twitter上追踪我们:http://twitter.com/oreillymedia

在YouTube上关注我们:http://www.youtube.com/oreillymedia

本书使用下列排版约定:

斜体Italic

表示专业词汇、文件名和文件扩展名。

等宽字体(Constant width

表示广义上的计算机编码,包括变量或函数名、数据库、数据类型、环境变量、语句和关键字。

等宽粗体(Constant width bold

表示应该由用户按照字面引入的命令或其他文本。

等宽斜体(Constant width italic

表示应该由用户替换或取决于上下文的值。

这个图标表示提示、建议或一般说明。

这个图标表示警告或提醒。

这本书是为了帮助你顺利完成工作的,可以在程序和文档中使用本书的代码。只要不是大规模复制本书中的代码,就不需要联系我们获取许可。例如,使用本书中的几段代码写一个程序不需要许可;出售和发行O’Reilly书中用例的光盘(CD-ROM)是需要许可的;通过引用本书用例和代码来回答问题不需要许可;把本书中大量的用例代码并入到你的产品文档中则需要许可。

我们赞赏但不强求注明信息来源。一条信息来源通常包括标题、作者、出版者和国际标准书号(ISBN)。例如:Testable JavaScript by Mark Ethan Trostler (O’Reilly). Copyright 2013 ZZO Associates, 978-1-449-32339-4。

如果你感到对示例代码的使用超出了正当引用或这里给出的许可范围,请随时通过permissions@oreilly.com联系我们。

Safari在线图书(Safari Books Online)是一家按需服务的数字图书馆,提供来自领先出版商的技术类和商业类专业参考书目和视频。

专业技术人员、软件开发人员、Web设计师、商业和创意专家将Safari Books Online作为他们研究、解决问题、学习和认证培训的主要资源。

Safari Books Online为组织、政府机构和个人提供一系列的产品组合和定价计划。用户可以在一个来自各个出版社的完全可搜索的数据库中访问成千上万的书籍、培训视频和正式出版前的手稿,这些出版社包括:O’Reilly Media、Prentice Hall Professional、Addison-Wesley Professional、Microsoft Press、Sams、Que、Peachpit Press、Focal Press、Cisco Press、John Wiley & Sons、Syngress、Morgan Kaufmann、IBM Redbooks、Packt、Adobe Press、FT Press、Apress、Manning、New Riders、McGraw-Hill、Jones & Bartlett、Course Technology等。欲获得有关Safari Books Online的更多信息,请在线访问我们。

非常感谢所有帮我完成这本书的人,我的前雇主Yahoo !,允许我在工作时间写书。感谢Julia和Randy!还要特别感谢Yahoo! Mail优秀的前端团队,尤其是在勃南度农庄工作的伙伴——列举一下:Brian、Jeff、Hung、Dan、Mily、Steve以及Scott。感谢我在Google的经理Matt Evans和团队的其他人,他们支持我继续向开发健全软件的目标前进。

特别感谢开源技术的贡献者。令人意外的是,使用商业软件的理由越来越少了,这对于从事商业软件业务的人来说相当讽刺。很明显,金钱不是最重要的动力,我每次都深有感悟,激情胜于金钱。老板们,员工的激情和产品有了碰撞才能真正完成高质量的工作!

感谢Doug Crockford和他的一系列优秀作品,启发我认真研究JavaScript。

感谢Shelley Powers和Davis Frank在审稿方面的大力支持,并提供了大量能提高本书质量的反馈。当然,我责无旁贷,如果本书有任何错误都是我的原因。JavaScript在快速变化,语言和工具都在不断发展,但我希望本书提出的概念(可能不是所有的工具)会保持很长一段时间。可怜的Audrey Doyle承担了全书的编辑工作,她非常用心!现在我敢保证,至少有一个人从本书的第一页阅读到了最后一页——多谢Audrey!

最后,感谢我深爱的家庭成员——Walter、Inslee,尤其是Michelle在本书漫长的酝酿过程中给予了我极力的支持。现在,让我们往下继续吧。


每个人的想法都是独一无二的,但代码不是。几乎每个行业都完成了机器革命,然而奇怪的是,计算机科学行业并没有。程序员基本上是在做已经做了40年的同样的事情。手工编写代码,接着这些代码被编译或解释,然后执行。看看输出结果,再确定是否需要再改代码。计算机科学的黎明到来之前,这种开发周期就一直这样保持不变。我们的机器在几何级变快,内存和二级存储大小是无限制的,且这样发展下去软件越来越复杂。但我们仍然一个字母一个键地手工编写着代码。我们依然滥用着“print”语句输出运行时发生的内容。我们的开发工具确实越来越强大,但每个热门的新语言出现时,又会出现新的工具。归根结底,在一个令人难以置信的自动化世界(并且大部分的自动化都是软件编程的成果),软件编程却依然是一个几乎完全手工的过程。一次敲一个字符的软件编程做法仍停滞不前。

虽然我们编写的大部分代码之前都已经编写过,可能是目前正在使用的语言,或者之前用过的语言,但每个应用程序都是独一无二的,即使我们和竞争对手都在做同样的事情。不管是否独特,成功的应用程序是必须能够使用的。它不需要漂亮,也不需要绝对得快,或者也不需要最丰富的功能,但它必须得能用才行。

应用程序的核心,只是拥有输入输出的消息传递系统。而在此之上构建的复杂性越来越大。由于JavaScript的出现,我们不仅要吸取其他语言的经验教训,还要从JavaScript自身吸取教训以便编写可测试的代码。在客户端和服务器端,JavaScript的应用程序都越来越大,我们必须非常谨慎地应用前辈们提供的最佳实践和经验教训,加以调整以很好地适应JavaScript代码。

图1-1显示了过去三十年每个晶体管周期的微处理器成本[1]。这幅怪诞的图表示了每个CPU的周期成本,此图遵循摩尔定律,有不可避免的下降趋势。硬件刷新率确实远远超出任何软件层面。

图1-1 每个晶体管周期的微处理器成本

通过编程机已经将对象变得更快更小以获得巨大的利益。为了达到不可思议的全球规模,大批的机器通过实施标准化组装于工厂中。然而软件工程师却仍然要在他们的个人电脑面前坐着,在键盘上不停地敲打着每个字符。

编写软件虽然是一个手动过程,但已经有很多人尝试编纂和规范开发人员应该如何做事,以便为构建“良好”代码创建一个可重复的过程。当然这些过程,希望引导固执的开发人员编写“整洁”和“零Bug”的代码。然而在大多数情况下,本书下面几节中采用的过程或方法,其结果直接取决于开发人员在系统里引入的意愿。本书内容不关注技术的选择或使用,而是关注编程实战中应该考虑做什么。让我们通过如下一些内容思考一下。

敏捷开发是大量实践中一个比较大的方法。敏捷方法主要是应对软件应用程序开发的“瀑布”模型,瀑布模型开发使用离散性的序列化过程。例如,瀑布模型首先是需求规范编写,接着程序员编码、测试人员测试、应用程序部署,然后回头重新更新新的需求。该过程的每一步都是连续且独立的。因此,编写需求规范时,程序员和测试人员都要等待,程序员编码时,测试人员再等待,而测试人员在测试的时候,所有人都在等待,等等。

敏捷开发尝试以更加灵活的方式让每个阶段都并行发生。软件能用是首要任务,与其花大量的时间等着上一步的完善交接,不如每个团队进行短周期的迭代,以便时刻都有事做。大量的工作被分解成可以预估的小块。敏捷开发尝试打破开发周期内每个小组的壁垒,这样他们可以一起工作,从而减少相互之间交接的时间。与客户沟通有助于确定最终的交付成果。

注意,使用敏捷开发方法,并不一定意味着应用程序完成得更快且质量更高。敏捷开发的最大优势是它处理需求变更的方式。在瀑布模型中,任何改变都需要贯穿整个过程。敏捷开发的短周期允许变更,以便将这些变更整合到最终的产品中。如果你听说过fail fast、release often、backlog、standup或者和continuous相关的词语,你可能已经在使用敏捷开发了。某种程度上,大多数现代开发都使用敏捷开发。图1-2显示了敏捷开发过程的典范图。

图1-2 敏捷开发过程(Dbenson and VersionOne科技提供)

图1-2中有很多内容,但基本思想是:快速迭代和持续交互可以加快高质量软件的交付。

敏捷开发本身没有提到如何编写软件,相反,它建议了一些适合敏捷哲学的方法。例如,“用户故事(user story)”是由“用户(user)”描述一个应用程序该有的特性的句子。这些故事是用户想在应用程序中体现的产品特性。使用你的应用程序或API的用户,可以是普通的用户,也可以是能够帮你定义最终产品的另外一组开发人员。结对编程是一种经常与敏捷开发一起使用的开发方法。结对编程最纯粹的形式就是两个程序员坐在同一张桌子旁,看同一个屏幕、键盘以及鼠标,一起进行编写软件。其中一个开发人员敲代码,而另一个开发人员则积极调试和思考代码。两个大脑通常比一个更聪明,与两个人单独进行编码相比,结对编程能够更快地发现并解决问题。

测试驱动开发(TDD)是敏捷软件开发的推荐做法。TDD希望在编写代码之前先编写测试。这些测试提供了必须遵循预期功能的代码。编写测试失败后(刚开始还没编写代码肯定会失败),接着开始编写代码,以便确保测试能够通过。保持测试领先于开发,永远不会有未被测试的代码,至少这是TDD的理论。在现实中,往往开始时,开发人员会沿着这条路走,并开始编写测试,但很快功能代码就会超过测试代码。呵呵,至少你得到了一些测试!

在新建项目或模块时,使用TDD的效果显然是最好的。如果只是需要单元测试,那它也是最成功的。在编写代码之前,编写完整的集成测试是令人望而生畏的!TDD也为重写现有遗留代码提供了一个很好的理由或借口。如果开发人员在为“已存在的代码编写一大堆测试”和“在编写自己新代码的同时进行测试”中做选择,大多数开发人员可能会选择后者。当然,开发人员不一定只有一个选择,如果对现有已存在代码编写测试是下一步的话,那就不要指望他们能有开心的笑容以及很高的效率。

无论如何,TDD不是一件坏事;事实上,它可以是一件非常好的事情。TDD是一个伟大的开始,不管是整个应用程序还是一个单一模块—所有人都喜欢编写新代码,如果编写新代码之前的“成本”仅仅是先编写测试的话,那就随它去吧。因为开始时没有代码,所以编写测试的“成本”是最小的。

在2005年针对加拿大的大学生开展的一项有趣的研究发现,TDD使程序员更有效率,因为他们写了更多的测试。虽然这很有争议,但更有趣的是,研究人员也观察到,随着编写测试数量的增加,软件质量很少呈线性提高,这与采用的开发战略无关。最好是要知道,测试的数量和更高的代码质量是成正比的。从中能得出的结论是,任何能让开发人员在编码前、编码中、编码后进行编写测试的方法都是非常好的方法。

行为驱动开发(BDD)是在TDD的基础上发展而来的,它为开发人员和非开发人员提供了一种通用语言,用于描述正确的应用程序行为和模块行为,该通用语言是日常语言。例如,提供用于定义被测试模块的行为的描述,比如“如果购物车是空的就不能进行结账”,而不是写一个名为testEmptyCart的测试。使用通用语言定义的测试或预期,可以让任何人都能更容易地了解被测试的内容,并且有助于定义测试和期望应该是什么样的[2]

相对于代码,BDD是利用敏捷用户故事来定义测试。用户故事可以直接转化为测试。用户故事通常必须遵循特定的模板形式:作为一个[人/角色],我需要[某些功能或权利],以便能[得到相应利益或达到相应的结果]。

正确填写每个空白项,如作为一位Yahoo!Mail用户,我想将照片附加到电子邮件中,以便我的收件人都可以看到它。这个用户故事可以转化为Yahoo!Mail产品的一组功能需求和测试。

BDD对于获得非项目团队成员(技术人员或非技术人员)的正式反馈很有用,从而帮助你理解系统应该如何操作。用户故事通常可以直接转化成测试—或者任何可以促进集中测试(更多测试)的内容,这是一件非常好的事情!

本书既不提倡也不解释任何一种开发方法,从这一点上讲,我认为它是成功的。瀑布(Waterfall)、螺旋(Spiral)、敏捷(Agile)以及其他方法都很好用,但都没有产生可测试的代码,更不用说可测试的JavaScript了。同样,TDD、BDD以及其他形式的开发方式也不一定确保有可测试的JavaScript。怎样才可以确保生成可测试的JavaScript?确保编写出整洁、松耦合并有足够注释,且确保别人能够接手维护的代码,才能够编写可测试的JavaScript。编写、阅读和维护可测试的JavaScript并不一定需要测试驱动、行为驱动或任何其他“驱动”型的开发方式。然而,遵循任何强调与编码一起测试的实践是一件有益的事情。要谨记的最重要的事情是我们编写的代码并不是凭空存在的。前面编写的任何专业代码,都将被我们自己或者别人查阅、编译、调试,并最终使用。最后,我们编写出代码,让别人来维护、研究和使用。

最近这一理念已经深入人心,我们不会弱化这一理念。我们编写的代码不是让电脑用的,而是让人用的。编写软件是一种亲身实践的业务。电脑只是接收比特数据、JavaScript、C+、Java、Per、Lisp或任何其他语言,都是将其编译到CPU极其有限的指令集中。CPU不知道它运行的是“编译”的语言还是“解释”的语言。CPU不在乎注释、分号或空格。CPU对人们使用的各种计算机编程语言的结构、语法、语义都是兼容的。JavaScript程序看起来就像是C++程序,而对CPU来说,C++程序又像Perl程序一样。

在最底层,项目的成败与否,与CPU执行的机器语言代码有关。但我们很少看到这一点。我们只看到原始代码。CPU不在乎我们想做什么,那是人要关注的事情。

软件始于意图。你想要做什么?这段代码要完成的是什么?为什么要编写它?这些都是你或你的同事每天都必须要问的重要问题。你将最初的意图(“什么(what)”和“为什么(why)”)转换到实际代码中,即“如何(how)”。本书主要关心的是“how”。首先弄清楚你想做什么,以及为什么要这么做,这是“如何”做工作的第一步,但实际处理“how”工作时,却发现其间关联不断。编程语言书籍可以在最底层的“how”上帮助你;而软件模式书籍可以在更高层次上帮助你。而本书则是希望在顶层能够清楚地解释“how”:如何编写可测试的代码,以及如何测试它。

但我们在了解“how”之前,理解软件开发中的“what”和“why”是非常重要的。

为什么要编写可测试的代码?可测试的代码是什么?

Douglas Crockford说过:“编写软件是人类做的最难的事情”,也就是说,软件要尽可能人性化是极其重要的。可测试的代码更加容易测试,意味着它更加容易维护,易维护则意味着它能让人(包括自己)更加容易理解,更加容易维护,从而又使得测试变得更加容易。

我们绕了一大圈,但这是一个良性循环。没有柏拉图式的完美代码,可测试性、可维护性和可理解性是互相关联的,对于这些内容是什么以及如在软件里应用它们,大家有很多的看法。然而,在可测试的、可维护的、可理解的代码上发现Bug并修复Bug则会简单得多。作为程序员,平时在自己的代码或别人的代码中查找并修复Bug,至少要花一半的时间,所以要善待自己,并让自己在这个过程里尽量轻松。

为什么要可测试的

测试代码,不管使用哪一种类型的测试,都是一件必须要做的事情。测试工作会由你或其他人来完成,即使是使用你的程序的最终用户。没有任何代码在第一次或后来的编写中是完美的,即便是最简单的JavaScript程序。

x=x+1;

这种程序也可能会有问题。如果x是一个字符串?或者x是无穷大?以及x是一个对象?程序会变得越来越复杂,我们只能期望测试最常用的路径。穷举测试是不可能的。编写容易测试的代码,或“可测试的”,至少可以让测试人员有机会完成最基本的且能永远保持先进的测试。

为什么要可维护的

程序员并不总是从零开始编写代码。有时候,我们的任务是调试和维护别人的代码。别人可能已经离开很久了,就像我们离开以前公司编写的代码已经很远了一样。在维护别人的代码时,别人可能也在维护我们的代码。除了让一切变得更美好外,可测试的代码也更易于维护。通常,我们对于代码变更带来的影响没有总体的了解,编写由可测试代码组成的测试可以帮助我们了解看似很小的变更所带来的影响。一旦知道代码如何工作—尤其是做修改以后会发生什么事情—我们就拥有了可维护的代码。这样的代码我们不必担忧。我们可以更容易地与团队成员分享这些代码,我们不需要重写代码也能完全理解它。程序规模越来越大,而完全了解代码知识的人在迅速减少。即便看似无关紧要的变更影响了其他功能,也会让这些人感到惊讶。

为什么要可理解的

高质量软件的第三个特点与其他两个特点交织在一起,就是阅读并理解代码的能力。理解一段代码的用途时,需要花多长时间?显然,能够对代码进行测试(以及为其准备测试)可以在很大程度上帮助理解代码是在做什么。但是,通过看一下方法或函数,能否不仅理解作者的意图,也能理解“如何”吗?如果不能理解自己6个月前编写的代码或者别人写的代码,那么代码就有严重的问题了。阅读一段代码后要了解,它不仅要令人满意,而且还要适当地满足工作需求。相反,如果我们写的代码自己都不再明白了,那么我们就失败了。代码是让人理解、维护和测试的。我们为别人写代码,这意味着我们的同事必须能够相对快速地理解代码。如果不理解代码,就无法进行测试或维护。在这种情况下,代码通常会被全部重写。我们可能扔掉垃圾代码,或者可能扔掉99%没有Bug的代码,但谁又能知道呢?如果没有可测试的、可维护的以及可理解的代码,那它就是垃圾。

所以究竟“什么”才是“可测试的”代码?“可维护的”代码看起来是什么样子?什么样的代码又是“可理解的”的代码?让我们深入挖掘一下这些问题。通过本书,我们会看到很多例子,展示这些内容是什么样子。

什么是可测试

可测试的代码就是容易被测试的代码。我猜大家会很惊讶!但是怎样让代码更易于测试呢?一般来说,使代码易于测试的特性,同样使代码易于维护,以及更容易理解:短小但也不太复杂的代码、完整的注释,以及松耦合。这些特性更让代码具有“可测试性”。利用可测试性特性和测试工具,可以让代码更具有可测试性。

什么是可维护

可维护的代码是一种可以移交给其他团队成员和同事,并便于接手和过渡的代码。具有良好测试并容易理解的可测试代码,比复杂的、不加注释的且没有测试的代码更容易维护。可维护的代码可以存在于一个完整的产品生命周期:产品从一个人转到另外一个人手里时,不需要部分或全部重写。可以修复和更改代码,而不必完全理解所有的代码,要有信心自己所做的修改不会影响别的功能,这样的代码就是可维护的代码。

什么是可理解的

看一段代码—不管是一个函数还是一段更小的代码,要花多长时间去理解?需要原作者一步一步解释一下吗?或者能在合理的时间内“理解”它吗?简单的、小型的且有注释的代码往往更加容易理解。通过单独运行来测试关于代码的假设,也能大大有助于理解。最重要的是代码可理解;否则,它将会被丢弃并重写。

理解“为什么”和“什么”才能得知“如何”。这本书主要是关于如何编写、测试并维护可测试的、可维护的以及可理解的代码。显然,对已经具有可测试性、可维护性以及可理解性的代码进行测试和维护,可以让工作变得更简单。有了这个坚实的基础,可以让我们更富有成效,富有成效的人是快乐的,快乐的人也是富有成效的!这是一个良性循环,我喜欢良性循环。

如何编写可测试的代码

从头开始编写可测试的代码更容易一些(所有事情从头开始做都更加简单,不是吗?)。一些开发方法,比如TDD和BDD,可以产生可测试的代码,但也不是必然的。这是因为给代码编写测试不会自动让代码变得可测试。然而,从一开始的时候就关注测试,写出的代码比从头到尾都没有测试的代码更具可测试性。我不会要求大家在编写代码的时候使用TDD和BDD;但是,我认为从头就开始测试是一个很好的想法,我相信编写可测试的代码,有必要不断地进行测试。先编写测试或编写UI并非编写可测试代码的必要条件,但先编写单元测试(TDD)或集成测试(BDD)是编写可测试代码的一个基本概念:代码执行和测试,越早越好。在TDD和BDD之间,我要提出第三种方法:测试循环开发(test-while-driven,TWDD)。测试和代码是鸡生蛋和蛋生鸡的问题:有第一个,另外一个接着才会有—也就是说,不要一点代码都不写就编写大量的测试,也不要编写了大量的代码而一点测试都不编写。而是要编写一段代码后,就开始编写一段测试;或者编写一段测试后,就开始编写一段代码。

再说一下,这些方法并不一定能够编写出可测试的代码,我们仍然可以编写大量没用的测试。在一点一点编写代码和测试的时候,记住要考虑到大局:编写短小、最小依赖和最低复杂度的可隔离的代码块,这种思想是本书的精髓。

如何编写可维护的代码

可以以实现可测试代码的相同方式,来实现编写可维护的代码:编写短小简单的、可隔离的代码—短小,是因为代码行数越少,错误就越少;简单,是因为简单的代码更容易维护;可隔离的,这样在代码修改时,对其他代码的影响才会最小。在本书中,我们将探讨几种能让代码短小且可隔离的方法。

如何编写可理解的代码

不足为奇,要编写可理解的代码需要遵循相同的原则。代码越简单越容易理解。有测试的代码使我们能够进一步了解代码的意图和内部运作机理,注释可以提高可理解性。

编写代码类似于创作小说章节:多个小章节比几个大章节更容易理解。冗长的注释(方法前插入的注释块)和代码(选择有意义的变量名、遵循最佳实践、遵循一致的编码风格,等等)可以提高可理解性。负责维护代码的同事也不是白痴,给他们一些标示帮助他们理解代码,这样我们的代码才不会被丢弃和重写。

编写短小、可测试的代码后工作并没有结束,还需要测试它!编写可测试的代码,可以让测试以及找Bug变得更容易。没有开发人员愿意尝试去调试一段巨大的代码,特别是如果这些代码不是他编写的。

单元测试是开发者的第一道防线。单元测试不仅能强迫开发人员理解我们的代码,也能帮助我们记录和调试代码。除了单元测试以外,集成测试也有助于确保一切都能按预期集成在一起—尤其是客户端JavaScript,它运行在更多平台(台式机、平板电脑和手机)上的不同浏览器上。最后,性能测试和负载测试有助于确保应用程序能够按指标执行。每一步的测试,都可以让我们在不同的抽象级别上练习代码。每个测试在不同的使用场景中都可以发现Bug。要完整地测试代码,需要在所有的抽象级别上都进行测试才行。生产环境中仍可能有Bug,并且没有解决这种问题的万能药。

不管进行的测试有多少,对于软件开发人员来说,调试(debug)都是必须要做的事情。幸运的是,JavaScript有一些非常好的工具可以不断改进调试工作。利用这些工具,将有助于让调试工作变得更容易,这是非常好的,因为如果不用,调试代码的时间可能比编写代码的时间还长。

无论是在本地调试还是远程调试,很多强大的工具都可以进行逐步调试以及资源管理。

编写可测试的JavaScript代码,并不能自动从敏捷、瀑布、TDD、BDD或任何其他软件开发方式中产生结果。可测试的JavaScript是编写短小、松耦合、独立的简单小块代码的一个保证。如何编写这样的代码取决于我们自己。希望本书能够帮助大家了解编写这种代码的方式。

编写可测试的代码会让我们的工作以及后续者的工作变得更加容易。从更少的Bug到更容易修复的Bug,从容易测试到简单调试,编写可测试的JavaScript是让我们保持清醒的方式。

最重要的是,不要忘记我们是为人编写代码,而不是为编译器。人,包括我们自己,以后必须要维护代码,所以为了让大家(包括我们自己)的工作更轻松,就要编写可测试的JavaScript。

[1] Wgsimon提供【署名-相同方式共享(CC BY-SA 3.0)协议】

[2] Erdogmus, Hakan, Macro Torchiano, Maurizio Marisio. On The Effective of the Test-First Approach to Programming. 2005, Proceeding of the IEEE Transactions on Software Engineering.


相关图书

深入浅出Spring Boot 3.x
深入浅出Spring Boot 3.x
JavaScript核心原理:规范、逻辑与设计
JavaScript核心原理:规范、逻辑与设计
JavaScript入门经典(第7版)
JavaScript入门经典(第7版)
JavaScript函数式编程指南
JavaScript函数式编程指南
PHP、MySQL和JavaScript入门经典(第6版)
PHP、MySQL和JavaScript入门经典(第6版)
JavaScript学习指南(第3版)
JavaScript学习指南(第3版)

相关文章

相关课程