Google软件测试之道

978-7-115-33024-6
作者: 【美】James Whittaker Jason Arbon Jeff Carollo
译者: 黄利李中杰薛明
编辑: 张涛

图书目录:

详情

本书通过讲解测试谷歌语音,工具栏,浏览器和Chrome OS的测试技术。告诉读者谷歌开发团队内部是如何进行测试的,他们是如何提高初始代码质量的。本书通过讲解测试谷歌语音,工具栏,浏览器和Chrome OS的测试技术。告诉读者谷歌开发团队内部是如何进行测试的,他们是如何提高初始代码质量的。

图书摘要

版权信息

书名:Google软件测试之道

ISBN:978-7-115-33024-6

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

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

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

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

• 著      [美]Whittaker Arbon Carollo

  译    黄 利  李中杰  薛 明

  责任编辑 张 涛

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

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

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

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

  反盗版热线:(010)81055315


Authorized translation from the English language edition, entitled: How Google Tests Software, 9780321803023 by James Whittaker,Jason Arbon,Jeff Carollo, published by Pearson Education, Inc., copyright © 2012. Pearson Education,Inc.

All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc.

CHINESE SIMPLIFIED language edition published by PEARSON EDUCATION ASIA LTD. and POSTS & TELECOM PRESS Copyright © 2013.

本书中文简体字版由Pearson Education Asia Ltd.授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

本书封面贴有Pearson Education(培生教育出版集团)激光防伪标签,无标签者不得销售。

版权所有,侵权必究。


每天,Google都要测试和发布数百万个源文件、亿万行的代码。数以亿计的构建动作会触发几百万次的自动化测试,并在好几十万个浏览器实例上执行。面对这些看似不可能完成的任务,谷歌是如何测试的呢?

本书从内部视角告诉你这个世界上知名的互联网公司是如何应对21世纪软件测试的独特挑战的。本书抓住了Google做测试的本质,抓住了Google测试这个时代最复杂软件的精华。本书描述了测试解决方案,揭示了测试架构是如何设计、实现和运行的,介绍了软件测试工程师的角色;讲解了技术测试人员应该具有的技术技能;阐述了测试工程师在产品生命周期中的职责;讲述了测试管理及在Google的测试历史或在主要产品上发挥了重要作用的工程师的访谈,这对那些试图建立类似Google的测试流程或团队的人受益很大。最后,本书还介绍了作者对于Google测试如何继续演进的见解、Google乃至整个业界的测试方向的一些预言,相信很多读者都会感受到其中的洞察力,甚至感到震惊。本书可以作为任何从事软件测试人员到达目标的指南。

本书适合开发人员、测试人员、测试管理人员使用,也适合大中专院校相关专业师生的学习用书,以及培训学校的教材。


“James Whittaker长期以来一直都能准确把握测试领域的发展脉搏,在这个云计算变革浪潮汹涌的时代,不论对Google员工,还是对其他任何测试人员来说,这本书都是紧跟时代、保持竞争力的必读书籍。”

—— Sam Guckenheimer,微软Visual Studio产品及战略负责人

“Google一贯是测试领域的创新者——无论是对手工测试与自动化测试的结合、本地团队与外包资源的融合,还是近来开创性地用真实场景测试补充实验室场景测试等方面。这种对创新的渴望帮助Google解决了很多新问题,更好地发布了产品应用。这本书中,James Whittaker系统地描绘了Google是如何在快速发展的软件测试领域取得成功的。”

—— Doron Reuveni,uTest CEO及联合创始人

“这本书改变了游戏规则,从版本的每日发布到平视显示器(译注:平视显示器是一种飞行辅助仪器。飞行员透过座舱正前方组合玻璃上的光电显示装置观察舱外景物时,可以同时看到叠加在外景上的字符、图像等信息,方便随时察看飞行参数。这里指软件系统参数的集中显示面板)。James Whittaker把计算机科学的方法应用到软件测试领域,这将成为未来软件企业的标准。本书以平实而饶有趣味的语言风格描述了Google在流程和技术上的创新。对每个做软件开发的人来说,这都是一本不可多得的好书。”

—— Michael Bachman,Google AdSense/Display 部门高级工程经理

“通过记录Google测试工程实践中的大量奇思妙想,作者已经把本书打造成了现代软件测试领域的圣经。”

—— Alberto Savoia,Google工程总监

“如果你要在云端发布代码并尝试建立一套保证产品质量和用户满意度的策略,你必须仔细研究和思考本书中的方法。”

—— Phil Waligora,Salesforce.com

“James Whittaker在测试领域是很多人的导师和灵感源泉。如果没有他的贡献,我们在测试领域不可能拥有今天这样的人才和技术。我一直敬畏他的魄力和激情。作为业界巨擘,他的作品绝对值得每位IT行业的人阅读。”

—— Stewart Noakes,英国TCL集团总裁

“当James Whittaker在微软工作的时候我曾与他共事。虽然我怀念与他一起在微软的日子,但我知道他在Google会从事伟大的工作。这本书包含了各种创新的测试理念、实践案例及对Google测试体系的深刻洞察。任何对Google测试和质量技术稍感好奇的人,或有意发现一些崭新测试思路的人,都能从这本书中有所收获。”

—— Alan Page,微软XBox,《微软的软件测试之道》的作者


It brings me great pleasure that the demand for this book was strong enough in China to make this translation possible. China is a major player in the software industry ,and I am pleased that some of my work is available to the millions of software professionals in this country. May your code have few bugs and many adoring users!

James Whittaker

“看到这本书在中国的需求如此旺盛,以及它的中译版最终付梓,真让我喜出望外,难以言表。在整个软件产业版图中,中国占据着非常重要的位置,如果说我的一些工作能给中国的软件同仁带来些许帮助,幸甚至哉。祝愿你们的代码少一些bug,多一些挚爱的用户。”

James Whittaker


毫无疑问,在当前这个时代,处于浪潮之巅的伟大公司非Google莫属。很长一段时间以来,Google的技术一直被外界所觊觎,其所宣扬的工程师文化氛围也成为了许多工程师梦寐以求的技术殿堂,其内部的工程实践更是技术分享大会中最热门的话题之一。但迄今为止,没有一本书系统地介绍Google内部产品的研发流程与模式,包括开发、测试、发布、团队成员如何分工协作等细节,直到《How Google Tests Software》的出现,才使得我们有机会管中窥豹,了解Google技术神秘之处。这也是我们翻译这本书的第一个原因。

正如本书中所提及的那样,互联网的出现改变了许多软件研发的模式。许多曾经红极一时的传统测试书籍里提及的最佳测试实践,在当前的环境下,效率会大大下降,在一些极端的情况下甚至会适得其反。我自己就是一名测试工程师,从事互联网方面的测试工作,对此深有体会,也经常焦虑如何在制约质量和快速发布之间寻找平衡,所以,也特别想从一些主流互联网公司的测试模式中得到启发和借鉴,特别是想看一下这个世界上最成功、增长速度最快的互联网公司——Google,是如何应对互联网测试挑战的。通过翻译这本书,自己学到了更多感兴趣的知识。这也是我们翻译这本书的第二个原因。

James Whittaker在正式撰写本书英文版之前,于2011年1月在Google Testing Blog上尝试发表了“How Google Test Software”系列文章。当看到第一篇时我就被深深地吸引住了,第一感觉就是,太棒了!Google测试团队居然是这样组织的!之后,随着这个系列文章的逐一公开,Google也逐渐揭开了其神秘面纱,让我对其测试实践也有了越来越多的了解,但了解的越多,疑惑也就越多。不得不承认,这几篇文章就像正餐前的开胃小菜,它完全勾起了大家的食欲,仅仅依赖这几篇文章完全不能满足窥探Google测试体系的需求。在2011年11月的GTAC(Google Test Automation Conference)大会上,我见到了James本人,便聊起了《How Google Test Software》这本书,James一听到又有人在打探这本书的下落,乐呵得嘴都合不拢了,却卖起了关子来,只是说书快出版了。大约在2012年9月,这本书的英文版终于问世之后,突然接到李中杰(本书的合译者之一)的电话,问我为什么不去翻译一下这本书呢。之前虽然是兴趣使然,做过那几篇文章的翻译,但与翻译一本书相比,还是有些微不足道的。但几经转辗,还是机缘巧合地去做了这件事情,这也是翻译这本书的第三个原因吧。

最后要说的,也是最重要的一个原因。我原本根本没有这么大的勇气来完成这件事情。众所周知,James不仅是测试领域的泰山北斗,而且他颇具文学功底,语言诙谐幽默,妙笔生花,翻译他的书籍,让我诚惶诚恐,以至于焦虑得昼夜不安。但两位合译者,李中杰博士和薛明,他们的乐观与自信让本书的翻译得以完成。与他们两位的合作,幸福之感难以言表,所收获的也不仅仅是长知识那么简单,更有许多惊喜深藏内心。翻译别人的书,像是在反刍,再精彩也是在讲别人的故事,还是期待有朝一日,能够也有机会讲讲自己的故事。

最后祝愿国内的读者能够从这本书中有所借鉴,找到适合自己现状的开发测试模式。由于译者水平有限,错漏之处在所难免,若有欠妥之处,欢迎指正。


“Google的测试理念有什么与众不同?Google的快速开发、快速发布的秘密又是什么?《Google软件测试之道》将Google的测试、产品的发布变得没有那么神秘,本书系统地介绍了Google的测试理念、自动化测试技术、产品发布流程,以及测试团队的组成和测试工程师的招聘,是一本真心做技术分享的好书!”

—— 张南,Google中国测试经理

“读完本书,Google测试就像一副完美的测试画卷展现在我的面前。没错,我说的是‘完美’!测试领域一直倡导的诸多测试理念,如尽早测试、注重早期测试和评审、注重测试人员技能等,对于很多测试团队而言,是那么的理想化,以至于实施起来困难重重,而在Google都已化作种种测试实践,自然又现实。感谢译者的工作,让更多中国的测试人员可以从中借鉴Google测试的优秀实践。”

—— 邰晓梅,独立软件测试培训与咨询顾问、首届ChinaTest大会执行主席

“我2007年刚加入Google中国时,就被这家企业具有的测试文化深深吸引。Google内将测试推到上游的实践、内建质量的意识,以及优秀的自动化测试实践,无一不让我觉得兴奋。在担任Google中国区的测试负责人期间,我也多次向外界介绍Google的测试实践,希望Google的实践经验能够更好地帮助到更多人。James的这本书详尽地介绍了Google的测试体系与测试实践,是一本即系统又非常‘接地气’的书。很高兴看到人民邮电出版社组织将这本好书翻译成中文,相信每位读者都能从本书中受益匪浅。”

—— 段念,豆瓣工程副总裁,曾任Google中国测试经理

“这本介绍Google软件工程生产力的好书值得每一位软件测试人员和研发管理者拥有,我个人甚至认为这是软件行业十年难得一遇的好书,书中所描述的观点、测试人员的价值拓展和测试技术创新实践不仅对互联网行业的软件测试从业人员有着非常好的借鉴意义,而且也为其他行业的软件工程人员提供了‘新的翅膀’,让大家都能飞得更快、更高。正确的认知是一切成功的源头,也许你能很容易找到十个拒绝了解不同观点的理由,但你依然可以找到十个理由去接受不同的新观点,兼听则明会让你的工作更高效,自己做得更开心,过得更充实。”

—— 董杰,百度在线网络技术有限公司测试架构师

“软件测试方法会产生颠覆性的变化吗?未来还需要测试工程师吗?最近一年这样的话题被持续地讨论,我没有结论,但是我觉得与其喋喋不休地争论,不如让我们看看世界级的IT企业Google是如何做测试的。通过本书让我们理解了Google的测试理念,理解了Google的工程师文化,从中你能发现更适合你的测试方法!”

—— 贺炘,领测国际创始人

“这本书是我推荐读者了解敏捷测试思想和技术的第一读物,没有之一。这本书的内容全部来自一线实际经验,而非理论空谈。更为重要的是,它传递了一种非常重要的理性质量观,同时还对如何将这种理性质量观落地给出了非常具体的建议。”

—— 吴穹,敏捷咨询师(在敏捷测试、自动化测试方面有深入研究)

“对于互联网公司,在快速前进中保持高质量是一个永恒的难题,在去哪儿网内部,开发工程师、产品经理都需要参加测试,以此来提醒——质量是所有人的事情而不只是测试团队的事情,但是,依然有太多的质量问题和实施中的难题没办法解决。本书可以给那些关注如何在此困境中突围的人们很多启发。”

—— 吴永强,去哪儿网CTO

“感谢译者翻译了这本测试业内的经典之作,让国内的测试团队能够快速理解国际测试的发展并跟上国际节奏。我有幸先阅读了本书的部分内容,对Patrick Copeland在序中描述的测试变革的心路历程深有共鸣:招聘具备开发能力的测试人员难,找到懂测试的开发人员更难;团队的变革开发团队不接受,测试团队也不买账。同时,我们面临的挑战比Google更大,我们不仅要做好自动化,做好持续集成,做好测试工具,做好研发生产力,我们还要将测试技术与产品和业务结合,促进集团内产品和业务的发展。因此,与Google的测试人员相比,我们不仅要具备开发能力、测试思维,还要具备业务思维,能深刻理解业务所服务的客户需求及客户价值。做好工程,更要做好业务!加油!”

—— 夏林娜,阿里巴巴集团测试总监

“互联网快速响应变化的需求彻底颠覆了传统的软件开发和测试模式,敏捷、持续构建和开发自测等成为测试行业的热点话题。Google无疑走在测试变革的最前沿,并已经在互联网领域产生广泛的影响并拥有大批拥趸。Google的全新测试理念和组织形式非常值得国内的同行借鉴。”

—— 刘立川,阿里巴巴集团测试总监

“或许有人会质疑,互联网公司也可以有很好的测试吗?此书可能会改变他们的观点。第一,本书第一作者James Whittaker是一个在微软接受了最正统测试理念的人,又从互联网的视角解读测试,这让他的观点全面而具有说服力;第二,这本书的中文翻译非常出色,读起来像测试行家如数家珍。所以,我强烈推荐本书,Google的测试不一定是最出色的,但这本书是。”

—— 柴阿峰,测试圈儿里那个说相声的

“我和本书的三位作者在西雅图有很多交流,并曾经共事。James Whittaker 是软件测试界强有力的执行者、探索者和思考者。本书是他和另外两位作者在Google工作的全面、详细总结和提炼。他们从软件测试开发工程师、软件测试工程师以及测试经理三个不同角色出发,详细阐述了Google软件测试之道,给企业,特别是互联网企业在如何测试、如何保证产品质量等方面提供了很好的参考。同时开阔了我们的视野,让我们对软件测试的职责、手段和未来发展有所思考。”

—— Bill Liu,Software Design Engineer in Test, Amazon


在Patrick Copeland最初建议我写这本书的时候,我有些犹豫,犹豫的理由后来也被逐一证实它们确实值得思考。人们会质疑我是否是写这本书的最佳候选Googler(他们也的确怀疑过)。有着太多的人想参与到这本书的撰写之中(后来也证实的确如此)。但更重要的是,我之前出版的一些书籍多数是给初学新手看的,像“How to Break”系列和《Exploratory Testing》,都是在从头到尾讲一个完整的故事。这本书并不是这样。读者可能坐着一口气读完,但其实它更适合作为一本参考书,一本介绍Google是如何完成大小规模不一的测试任务的参考书。我希望本书的读者是一些已经在公司从事测试工作的人,而不是一些初学者,他们会有一些基础,并会比较Google的流程与他们所使用的流程之间的区别,这样他们的收获更大。我憧憬着经验丰富的测试人员、测试经理、管理者能够随手拿起这本书,找一些感兴趣的话题,看一下在某些方面Google是如何做的。这可真不是我惯用的写作风格。

在此之前从没有写过书的两位工程师,为了这本书,加入进来共同努力。这两位都是优秀的工程师,他们在Google的工作年限都比我长。Jason Arbon的职位是TE(测试工程师),但他内心深处有着创业情怀,在本书“测试工程师”这一章中出现的许多工具和想法,都深受他的影响。我们有幸一起共事,并彼此从对方身上受益良多。Jeff Carollo也是一名测试人员,但后来转做开发了。Jeff Carollo是我见过的最优秀的那一类SET(软件测试开发工程师),也是少数几个我认识的那种可以写出“自动化之后就不用再参与”的代码的人之一,他的测试代码写得非常棒,可以独立运行不需要任何干预。我与这两位才华横溢的人共同写作,并在风格上尽可能地达成一致。

有许多Googler提供了资料。当资料中的文字和标题是同一个人的工作时,我们会在标题中把这个人标记一下。还有许多对Google测试发挥了深刻影响的人,我们针对这些人做了一些采访。这是我们能想到的最好的、让尽可能多的曾经定义了Google测试的人参与进来的方法,而不是搞一本由30个人合著而成的书。不一定所有的读者对这些访谈都感兴趣,但在书中可以很清晰地找到这些访谈的起止位置,以便选择跳过这一部分,或者专门找到这部分来阅读。我们同样感谢为数众多的贡献者,但如果有不到之处,也愿意接受任何批评。英语实在是一门贫乏的语言,无法用它描述出这些工作是多么地卓越和辉煌。

快乐阅读,快乐测试,祝愿你总能发现(并修复)bug。

James Whittaker

Jason Arbon

Jeff Carollo


献给Google、Microsoft和全世界给我启发的测试人员。

——James Whittaker

献给我的妻子Heather和我的孩子们Luca、Mateo、Dante和Odessa,他们一直认为这段时间我在星巴克工作。

——Jason Arbon

献给我的妈妈、爸爸、Lauren和Alex。

——Jeff Carollo


我们想感谢那些不知疲倦地、致力于质量改进的Google工程师们。同样,也非常感谢Google开放的工程和管理文化,在对待测试方法与实践方面与Google打造其他产品如出一辙,允许不断创新以及天马行空般自由思维的存在。

在这里要特别向那些投入巨大精力并勇于承担风险将测试推向云端的人们致敬,他们是Alexis O. Torres, Joe Muharksy, Danielle Drew, Richard Bustamante, Po Hu, Jim Reardon, Tejas Shah, Julie Ralph, Eriel Thomas, Joe Mikhail, Ibrahim El Far。还要感谢我们的编辑,Chris Guzikowski和Chris Zahn,他们一直非常有礼貌地在容忍我们这些工程师的唠叨。感谢那些受访者在书中分享他们的观点与经验,他们是Ankit Mehta, Joel Hynoski, Lindsay Webster, Apple Chow, Mark Striebeck, Neal Norwitz, Tracy Bialik, Russ Rufer, Ted Mao, Shelton Mar, Ashish Kumar, Sujay Sahni, Brad Green, Simon Stewart, Hung Dang。特别要感谢一下Alberto Savoia,他在原型及快速迭代方面的灵感成就了今日Google快速发布的文化。感谢Google餐厅的工作人员,他们提供了美味的餐饮。感谢Phil Waligora, Alan Page, Michael Bachman,他们为本书提供了率直坦诚的反馈。最后,要特别感谢Pat Copeland,是他将来自五湖四海且充满激情的各路精英汇集于此,并投身于质量方面的不断改进工作。


Alberto Savoia

谷歌工程总监

为一本你曾经想自己去撰写的书去做序,是一种尴尬的荣誉,这种感觉有点像你被邀请去为好友做伴郎,但新娘却是你曾经心爱的姑娘。但是James Whittaker却是一个聪明的家伙,在他问我是否愿意为这本书写序之前,先请我吃了一顿我非常喜欢的墨西哥晚餐,并让我喝了几杯墨西哥Dos Equis啤酒。当我还沉浸在牛油果酱带来的愉悦时,他终于提出了这个请求,在当时那种气氛下,我只能强作欢颜并答应了他:“没有问题。”他的诡计“得逞”了,他和他的“新娘”——这本书,一起站在一边,而我却不得不在这里为他们的婚礼做致辞。

正如我说过的,他是一个聪明的家伙。

让我继续写这篇序吧,为这本我曾想自己写的书。

这个世界上真的还需要另外一本关于软件测试的书吗?特别是James Whittaker,这个高产的家伙,一个我曾经不止一次公开地称其为测试书籍出版界高产的“八胞胎妈妈”(译注:不知道“八胞胎妈妈(Octomom)”是什么意思?Google一下你就知道),还需要他的这么一本软件测试书吗?那种讲述陈旧得令人厌烦的测试方法学和宣扬一些可疑、过时的建议的书还少吗?是的,这样的书已经足够多了,但我认为这本书绝非如此。这也是我想自己去写它的原因,这个世界很需要这样一本独特的测试书。

互联网的出现急剧地改变了许多软件设计、开发和发布的方式。很多曾经红极一时的测试书籍里提及的最佳测试实践,在当前的环境下效率会大大下降,或者毫无效果,甚而在某些情况下会事与愿违地起反作用。在互联网和软件产业,一切变化都如此迅速,以至于许多最近几年才出版的软件测试方面的书籍都已陈腐过时,打个比方,它们就像讲述水蛭吸血和开颅驱赶恶鬼的外科手术书一样。对付这种书,最好的办法就是直接把它们扔掉,或者做些有益的事情,例如,循环再利用,做出纸尿裤来,以防止流落到容易上当受骗的人之手。

考虑到软件产业的发展速度如此之快,如果说十年后这本书也过气了,那一点儿也不奇怪。但在下次浪潮来临之前,这本书可以既适时又适用地从内部视角告诉你这个世界上最成功、增长速度最快的互联网公司之一,是如何应对21世纪软件测试的独特挑战的。James Whittaker和他的伙伴们,抓住了Google如何做测试的本质,抓住了Google如何测试我们这个时代最复杂和流行软件的精华。我之所以了解这些,是因为我从头到尾经历了这个伟大的转变。

我于2001年以工程总监的身份加入Google。当时,Google大概有200名开发人员,但只有区区3位测试人员!那个时候,开发人员已经开始做自己代码的测试了,但由于测试驱动开发的模式才刚刚开始,而且像JUnit这样的测试框架也没有大规模使用。当时的测试主要是在做一些随机测试(ad-hoc testing),其好坏取决于编写代码的开发者的责任心。但即使那样也是可以接受的,因为,当时正处在创业阶段,必须快速前进并勇于冒险,否则就无法和那个时代已经非常强大的对手竞争。

然而,当Google逐渐成长变大,Google的一些产品对于最终用户和客户来说开始变得至关重要(例如,竞价广告产品,我曾经负责的产品,很快变成许多网站的主要收入来源),我们清晰地认识到必须加大对测试的关注和投入。但只有3个测试工程师,别无选择,只能让开发来做更多的测试。与其他的几个Googler(译注:Google员工,本书中一般指Google工程师)一起,我们介绍、培训、推行单元测试,我们鼓励开发人员把测试作为优先级较高的事去做,并建议使用一些工具,如JUnit,把测试做成自动化的。但是进展缓慢,并非所有的人都接受、认同开发人员去做测试这件事情。为了继续保持这个势头,在每周五下午公司的啤酒狂欢时(译注:TGIF,Thank God It’s Friday,Google在每周五下午举行全员聚会),我们为一些做测试的开发人员颁发奖品来激励大家。但这种感觉不是很好,有点像杂技训兽师在小狗完成某个动作后给一些奖励一样,但这样至少还是把大家的注意力吸引到测试上了。会如此幸运吗?如此简单就可以让开发做测试了?

很不幸的是这招根本不管用。开发人员发现,为了测试充分,他们不得不针对每一行功能代码,写两到三行的单元测试代码,而且这些测试代码和功能代码一样都需要维护,且有着相同的出错概率。而且大家也意识到,仅做单元测试是不够的,仍然需要集成测试、系统测试、用户界面等方面的测试。当真正开始要去做测试的时候,会发现测试工作量变得非常大(且需要很多知识的学习),并要求在很短的时间内完成测试,要以“迅雷不及掩耳”之势完成。

我们为什么要在很短的时间内迅速地完成测试呢? 我一直这么认为,对于一个坏点子或考虑欠周的产品,即便再多的测试,也无法把它变成一个成功的产品。但如果测试方法不当,却会扼杀一个本来有机会成功的产品或公司,至少会拖慢这个产品的速度,让竞争对手有机可乘。Google当时正处于这样的紧要关头,测试已经成为Google持续成功道路上的最大障碍。此时,我们需要正确的测试策略来满足产品、用户和员工快速增长的需要,不拖慢公司前进的步伐。这样的测试策略会涉及大量的创新性方法、非常规的解决方案和独特的工具。当然,并非所有的策略都生效了,但在这个过程中,我们得到了宝贵的经验和教训,这对于其他像Google一样快速成长的公司来说也是非常有帮助的。我们学会了如何在保持正常的开发速度的同时,让大家充分意识到质量的重要性。本书将要讲述的,正是这个过程中我们的所作所为、所思所想。如果你想要理解Google是如何面对21世纪最新的互联网、移动和客户端应用等方面的测试挑战的,读这本书就对了。我本想自己为大家讲述整个故事,但James Whittaker和他的伙伴却抢先了一步,他们已经摸索出了Google测试的精髓。

关于这本书,最后要说明一点:是James Whittaker造就了这本书。他加入Google,深入了解了Google的文化,参与了重要的项目,并发布了Chrome、Chrome OS和其他许多产品。有一段时间,他变成了Google测试的代言人。但是,这本书与他曾经出版过的其他书籍略有不同,里面的很多素材都不是来自于他个人。他本人在Google测试演化过程中的角色,更像是一个描绘记录者,而不是一个参与贡献者。在你阅读这本书时,一定要把这一点铭记于心,因为James Whittaker很有可能会把所有的功劳都归功于他自己。

在Google由200人变成2万人的过程中,有许多人在我们的测试战略的形成和实施中做出了杰出贡献。James Whittaker肯定了他们的贡献,在本书中以访谈的形式将他们引入书中。然而没有一个人,包括我、James Whittaker,或者本书中提到的其他任何人的影响力,比得上Patrick Copeland,他是我们今天组织结构的架构师和Google工程生产力部门的负责人,所有Google的测试人员最终都会汇报给Patrick。作为执行官,他以自己的想象力创造了James Whittaker在本书中描述和贡献的一切。如果非要把Google今天的测试成就归功于某人,那这个人一定是Patrick。我这么说的原因不仅在于他是我的老板,而且还在于,作为我的老板,是他命令我这么说的!

Alberto Savoia是Google的工程总监,同时也是一位创新鼓动者。Alberto于2001年加入Google,那个时候主要负责AdWords产品的发布,同时他也是Google“开发者/单元测试”这一文化的主要缔造者。他还是《The Way of Testivus》的作者,以及O’Reilly出版的《Beautiful Code》一书中“Beautiful Tests”一章的作者。

来自James Whittaker的说明:我完全同意Alberto所说的一切。作为这一过程的描绘记录者,绝大多数的材料都归功于Patrick创建的这个测试团队。还有,我这么说并不仅是因为Patrick授权我写这本书。作为我的老板,是Patrick命令我写了这本书。


Patrick Copeland

谷歌测试和部署技术的架构师

我在Google的旅程始于2005年3月。Alberto在前面的序中也介绍了一些当时Google的状况:虽然公司规模还比较小,但已开始感受到成长带来的烦恼。当时适逢快速的技术变革之际,Web世界正在迎接动态内容的到来,而云计算也正在逐渐成为一种新的选择,取代当时还占统治地位的客户机-服务器架构。

在加入Google的第一周里,我和其他Nooglers(译注:New Googler,新加入Google的员工)一起,戴着三色的螺旋桨帽,参加了称为TGIF的公司每周例会,听创始人介绍公司战略。我对彼时的工作情形还知之甚少,有些兴奋,也有些害怕。在我之前10年的研发模式经历中,一个典型的交付周期可长达5年,这种经历在Google的速度和规模面前显得毫无价值。更糟糕的是,我觉得自己是所有戴着Noogler帽子的人中唯一的测试人员。当然,其他地方一定还有更多的测试人员!

我加入Google的时候,工程团队还不足1000人。测试团队大概有50名全职人员和一些临时工,具体数量我一直没搞清楚。测试团队当时的称谓是“测试服务”,工作重点在UI的验证上,随时响应不同项目的测试需求。可以想象,这并不是Google最闪耀的团队。

但这在当时已经足够了。Google当时的主要业务是搜索和广告,规模要比今天小得多,一次彻底的探索式测试足以发现绝大多数的质量问题。然而,世界在变,Web点击量开始史无前例地爆发性增长,文档化的Web正在让位于应用化的Web。你可以感觉到势不可挡的成长和变化,在这种情况下,规模化和快速进入市场的能力变得至关重要和生死攸关。

在Google内部,规模和问题的复杂性给测试服务团队带来了巨大的压力。在之前小型的、类同的项目里的一些可行做法,现在却让优秀的测试人员感到筋疲力尽,疲于奔命在多个急需救火的项目之间。更加火上浇油的是,Google在项目快速发布方面的坚持。是时候采取措施了,我面临两个选择,要么沿用这种劳动密集型的流程增加更多的人手,要么改变整个游戏规则。为了适应业界和Google发生的巨变,测试服务团队需要根本性的变革。

我也很想说自己是借助于丰富的经验构思出了完美的测试组织模型,但实事求是地讲,我从过去的经历中,学到的只不过是一些过时的做法。我所工作或领导过的每个测试组织都有这样或那样的问题。有问题是常态,代码质量很糟糕,测试用例很差劲,团队也问题多多。我完全清楚那种被技术质量债压得喘不过气来的感受,在那种状态下,一切创新性的想法都会被遏制,以免不小心破坏了脆弱的产品。如果说我在以往的经历中有所收获的话,那就是经历了各种错误的测试实践。

那个时候,以我对Google的了解,有一件事情是确定无疑的,那就是Google对于计算机科学和编程能力非常重视。从根本上说,如果测试人员想加入这个俱乐部,就必须具备良好的计算机科学基础和编程能力。

变革Google测试的首要问题是重新定位身为测试人员的意义所在。我过去经常在头脑中想象理想团队的模型,想象这样的团队是如何肩负起质量重任的,每次我都会得到相同的结论:一个团队能编写出高质量软件的唯一途径是全体成员共同对质量负责,包括产品经理、开发人员、测试人员等所有人。我认为,达到此目标的最好方式是使测试人员有能力将测试变成代码库的一个实际功能,而测试功能的地位应该与真实客户看到的任何其他功能同等重要。我所需要的能够实现测试功能的技能,也正是开发人员需要具备的技能。

招聘具备开发能力的测试人员很难,找到懂测试的开发人员就更难,但是维持现状更要命,我只能往前走。我希望测试人员能为他们的产品做更多的事情,同时,我希望演变测试工作的性质和从属,要求开发团队更大地投入。这种组织结构在当时的业界尚未实现,但我坚信它非常适合Google,我相信在这家公司,时机到了。

不幸的是,这种如此深刻、根本性的变革在公司里极度缺乏认同,极少有人能分享我的激情。当我开始推销这种关于软件测试角色的地位平等而作用不同的愿景时,我发现竟然难以找到一个人一起共享午餐!开发工程师们好像被他们将要在测试上发挥更大的作用这个想法吓着了,他们指出“这是测试人员的职责”。而测试人员也不买账,因为很多人已经习惯了当前的角色,维持现状的惯性导致任何变革都变得非常困难。

我毫不松懈地继续努力着,主要是出于对Google的研发过程深陷技术和质量债的困境的恐惧,一旦如此,长达5年的开发周期又会成为现实,而我本来已经很高兴地把它们留在客户机-服务器的世界里了。Google是一家由天才组成的公司,以创新为灵魂,这种企业文化与冗长的开发周期是不相容的。这是一场值得打的战斗,我说服自己,一旦这些天才理解了这种旨在打造一个生产线式的、可重复的“技术工厂”的开发和测试实践 ,他们就会改变看法。他们就会理解我们不再是一个初创公司,快速成长的用户群、不断累积的bug和糟糕结构的代码形成的技术债将会导致开发过程的崩溃。

我逐个接触各产品团队,寻找优秀的案例,试图为我的立论找到比较容易的切入点。在开发人员面前,我描绘了一个持续构建、快速部署的蓝图,一个行动敏捷、省下更多时间用于创新的开发过程;在测试人员面前,我激发他们对于成为同等技能、同等贡献和同等薪酬的完全的工程合作伙伴的渴望。

开发人员的态度是,如果我们招聘到有能力做功能开发的人,那么,我们应当让他们做功能开发。其中一些人对我的想法非常反感,甚至发信给我的主管,非常直率地建议如何来处理我的疯狂之举,这些信塞满了我的主管的邮箱。幸运的是,我的主管并没有采纳那些建议。

令我吃惊的是,测试人员的反应竟然与开发人员类似。他们沉湎于老的做事方式,抱怨自己在开发面前的地位,但又不想去改变。

我的主管对这些抱怨只有一句话:“这里是Google,如果你有想法,尽管去做就是。”

于是我开始付诸行动。我召集了一批志同道合的骨干分子,组成了一个面试团队,开始招聘。事情进行得比较艰难,我们寻找的人要兼具开发人员的技能和测试人员的思维,他们必须会编程,能实现工具、平台和测试自动化。我们必须对招聘和面试的标准与流程做出一些调整,并向已经习惯了既有模式的招聘委员会做出合理解释。

最初的几个季度进行得异常艰难。好的候选人经常在面试过程中失利,也许是因为他们没能很快地解决一些奇怪的编程问题,或是在某些人认为很重要的方面表现得不够好(然而这些方面其实与测试技能毫不相干)。我预料到了招聘过程的困难,每周都要抽出大量时间写辩词。这些辩词最终会到达Google联合创始人Larry Page手里(他一直是招聘的最终批准者)。他批准了足够多的候选人,我的团队开始稳步增长。直到现在,我猜每次Larry听到我的名字时想到的一定是:“招聘测试的!”

当然,到这个时候,我已经做了大量的宣传和鼓动工作,来说服大家这是唯一的选择。整个公司都在看着我们,一旦失败,后果将是灾难性的。对于一个混合了很多不断变化的外包人员和临时人员的小测试团队而言,期望显得如此之高。然而,即使是在我们艰难的招聘进行中同时减少了临时人员的数量时,我已经注意到了变化在发生。测试资源越稀缺,给开发人员留下的测试工作就越多。很多团队都勇敢地接受了挑战。我感觉,如果技术保持不变的话,这个时候的状态已经在接近我们的目标了。

然而,技术不是静止不动的,开发和测试实践处于飞速的变化之中。静态Web应用的时代已经成为过去,浏览器还在努力追赶之中,围绕浏览器的自动化技术比已经迟缓的浏览器还要落后一年。开发人员正面临着巨大的技术变革,在这个时候,把测试交给开发人员,这看上去是徒劳的。我们甚至还不太会手工测试这些应用,更不用提自动化测试了。

开发团队身上的压力也同样巨大。当时Google 开始收购拥有富含动态Web应用的公司。YouTube、Google Docs等后继产品的融入,延展了我们内部的基础设施。开发团队在编写功能代码的过程中,要面临很多问题,与我们测试人员在测试过程中要面临的问题一样,令人生畏!测试人员面对的测试问题无法孤立地解决。把测试和开发割裂开来,看成两个单独的环节,甚至是两类截然不同的问题,这种做法是错误的,沿着这条路走下去意味着什么问题也解决不了。解决测试团队的问题,只是我们前进路上的其中一步而已。

进展在继续。雇佣优秀的人是一件很有意思的事情,他们会推动进展的发生!到了2007年,测试团队有了更好的定位。我们能够很好地处理发布周期的最后环节。开发团队已经视我们为顺利上线的可靠合作伙伴。不过我们仍然是在发布过程的后期才介入的支持团队,局限于传统QA模型。尽管有了优秀的执行能力,我们还没达到我设想的目标。我解决了招聘方面的问题,测试也向着正确的方向发展,但是我们还是在整个流程中介入太晚。

我们在一个被称作“测试认证”(本书后面的章节会详细介绍)的事情上取得了不少进展。我们向开发团队提供咨询,帮助他们改善代码质量并尽早进行单元测试。我们开发工具并指导团队进行持续集成,使产品一直保持可测试的状态。我们进行了无数的改进和调整,从而消除了之前的很多质疑,本书详细介绍了其中的很多方法。但是,在那个时候,还是感觉缺乏整体感,开发依旧是开发,测试依旧是测试。虽然很多文化变革的因素已经存在,但是,我们还需要一个催化剂把它们聚合成一体。

自从根据我的想法开始招聘担当测试角色的开发人员以来,测试组织在不断壮大。基于对这个团队的思考,我意识到测试仅仅是我们所负责的工作的一部分。我们的工具团队开发了从源代码库到编译框架,再到缺陷数据库的各种工具。我们是测试工程师、发布工程师、工具开发工程师和咨询师。触动我的是,我们所做的非测试的工作对生产力的提升产生了巨大的影响力。我们的名称是测试服务,但是我们的职责已经远大于此。

因此,我决定正式把团队名称改为工程生产力(Engineering Productivity)团队。伴随着称谓的改变,随之而来的是文化的革新。人们开始更多地谈论生产力而不是测试和质量。生产力是我们的工作,测试和质量是开发过程里每个人都要承担的工作。这意味着开发人员负责测试,开发人员负责质量。生产力团队负责帮助开发团队搞定这两项任务。

开始的时候,这个观点还只是一种梦想和志向,我们提出的“给Google加速”的口号听起来也很空洞,但是,随着时间的推移和我们的努力,我们实现了这些诺言。我们的工具让开发的动作更快,我们帮助开发人员扫清了一个又一个障碍,消除了一个又一个瓶颈。我们的工具还使开发人员能够编写测试用例,并在每次构建时看到这些测试的结果反馈。测试用例不再只是隔离地运行在某些测试人员的机器上。测试结果会在仪表盘上显示,并把成功的版本积累下来,作为应用发布健康性的公开数据。我们并不是仅仅要求开发人员对测试和质量负责,我们还提供帮助让他们可以轻松地达到这些要求。生产力和测试的区别最终变成了现实——Google的创新能够更为顺畅,技术债也不会累积了。

最终结果如何呢?我可不愿这么早就交了底,因为这本书就是要详细讲述这个问题的。作者们花费了巨大精力,根据自身和其他Googler的经历,把我们的秘诀浓缩成了一套核心实践。但其实,我们的成功有很多方面,从将构建次数以数量级式地降低,到“跑完即忘”式的测试自动化,再到开源一些非常新颖的测试工具。在我写这篇序的时候,生产力团队已经拥有1200名工程师,这个数量比我在2005年加入Google时整个工程部门的工程师的数量还要多。生产力品牌的影响力已经相当大,我们加速Google的使命已经作为工程文化的一部分,被广泛接受。从我困惑、迷茫地坐在TGIF会议上的第一天到现在,这个团队已经走过了漫长的征途。这期间唯一没变的是我那顶三色螺旋桨帽,我把它放在我的桌上,作为我们一路走来的见证。

Patrick Copeland是Google工程生产力部门的高级总监,处于Google整个测试链的最顶端。公司里所有的测试人员都最终汇报给Patrick(而他恰好跨级汇报给Larry Page,Google的联合创始人和CEO)。Patrick加入Google之前是微软的测试总监,并在那里工作了近10年。他经常公开演讲,在Google内部被公认为Google软件快速开发、测试和部署技术的架构师。


软件开发并不简单,测试也一样。谈及整个Web规模的开发和测试,一定会提到Google。如果你对这家互联网上最有名气的公司是如何进行如此大规模的测试感兴趣的话,那么这本书将非常适合你。

每天,Google测试和发布数百万个源文件、亿万行的代码。数以亿计的构建动作会触发几百万自动化测试在几十万个浏览器实例上执行。操作系统按年构建、测试和发布,浏览器的构建每天都在进行,Web应用基本达到持续发布。2011年,Google+在100天之内发布了100个功能。

这就是Google规模和Google速度——正是Web本身的规模——这就是本书描述的测试解决方案。我们会揭示这个架构是如何设计、实现和运行的,介绍在概念和实现阶段都发挥了重大作用的许多人士,解释使之成功的基础架构。

但之前也并非如此。Google走到今天的路线与我们的测试技术一样有趣。回到6年以前,Google的情况与我们之前工作的那些公司非常类似,测试是主流之外的领域,测试人员不受重视、加班加点,测试主要是一个手工的过程,那些善于自动化的人很快就被开发拉走了,因为做开发影响力会更大。在Google被称为“工程生产力”部门的奠基者们必须克服对测试的偏见,以及那种推崇个人英雄主义而轻视工程严谨性的公司文化。今天,Google的测试人员与开发人员同工同酬,奖金、晋升待遇完全一样。测试人员取得成功,以及这种文化能够经受公司巨大成长(产品、多样性和营收)和结构重组带来的实际考验,对于那些跟随Google足迹的公司来说,是非常振奋人心的。测试是在做正确的事情,是可以被产品团队和公司的管理层认可的。

随着越来越多的公司在Web领域淘金,本书介绍的测试技术和组织结构可能会变得更加普及。果真如此的话,请考虑将这本书作为到达目标的指南。

这本Google测试指南按照所涉及的角色组织。第一部分介绍了Google质量流程的所有角色、概念、流程和细节,这一部分建议必读。

本书前面几章可以按任何顺序阅读。首先介绍了SET(Software Engineer in Test,即软件测试开发工程师)这个角色,因为这是现代化的Google测试的起点。SET是技术测试人员,该章内容有适度的技术性,但抽象程度足够能让任何人理解其主要概念。之后的一章涵盖了另一个主要的测试角色——TE(Test Engineer,即测试工程师)。该章内容较多,因为TE的工作非常宽泛,Google的TE在产品生命周期中的职责很广。这个角色同样为许多传统的测试人员所熟知,我们猜测这会是读者最多的一章,因为它的受众面最大。

本书还讲述了测试管理,以及与Google的测试历史或在主要产品上发挥过重要作用的人士的访谈。那些试图建立类似Google的测试流程或团队的人,可能会对这些访谈感兴趣。

任何一位读者都千万不要错过最后一章。James Whittaker介绍了他对于Google测试如何继续演进的见解,并对Google乃至整个业界的测试方向做了一些预言。我们相信很多读者会感受到其中的洞察力,甚至感到震惊。


在许多场合下,不管是在国外访问还是出席会议期间,我总是毫无例外地被问及一个问题。甚至是刚刚加入公司的新员工也会问到同样的问题:“Google是如何测试的?”

虽然我已经不太确定曾经多少次回答过这个问题,以及给出了多少个不同版本的答案,但可以确定的是,随着我在Google工作的时间越来越长,发现Google的各种测试实践的不同之处也越来越多,答案也一直在变化。这些测试实践总是浮现在脑海里,并幻想着有朝一日能够将它们整理成书。直到有一天,Alberto(译注:Alberto Savoia,Google的测试总监,详细介绍参见本书序言中的Alberto部分),这个一贯认为所有测试相关的书籍都要为自己的存在找一个理由,否则就应该被扔掉做成纸尿裤的人,当他建议我应该写这样一本书的时候,我觉得时机已经成熟,是时候开始考虑写这样一本书了。

然而,我依旧还在等待。第一,我并非是写这样一本书的最佳人选。在Google,有很多我的前辈,我想先把机会让给他们来写;第二,我只是Chrome和Chrome OS产品的测试总监(现在这个职位被我之前的一个下属担任着),我看到的也只是Google所有测试实践中很小的一部分,我还需要去了解很多其他Google产品的测试方法。

在Google,软件测试团队归属于一个被称为“工程生产力”(译注:Engineering Productivity,也译为工程效率或工程生产率)的中心组织部门,这个部门的职责横跨开发测试人员使用工具的研发、产品发布和各种级别的测试,从单元级别的测试到探索性级别的测试。Google拥有大量针对互联网产品的共享工具与测试基础框架,服务于包括搜索、广告、Apps、YouTube视频和其他我们在Web上提供的产品。Google已经成功解决了许多有关速度和扩展性方面的问题,使得Google作为一个大公司,却依然能以创业公司的速度来发布产品。正如Patrick Copeland在本书的序言中所说的那样,拥有如此的魔力,Google的测试团队功不可没。

注意


在Google,软件测试团队归属于一个被称为工程生产力部门的中心组织的部门。

Chrome OS在2010年12月发布以后,我把团队顺利地交接给我的一个直接汇报者,然后开始把自己的工作重点慢慢转移到其他产品上。在这本书刚开始准备的阶段,我使用博客的方式做了一些尝试,发布了第一篇“Google是如何测试的”的系列文章(注:参见http://googletesting.blogspot.com/2011/01/how-google-tests-software.html)。6个月之后,本书终于完成,希望没有拖太长的时间。在这六个月的时间里,我了解到的Google测试实践比我过去两年在Google学到的都要多。现在有了这本书,Google的新员工们也可以通过阅读此书来熟悉Google的环境。

这并不是第一本介绍关于大公司是如何做测试的书籍。当我还在Microsoft的时候,Alan Page, BJ Rollison和Ken Johnston合著了《微软的软件测试之道》(译注:How We Test Software at Microsoft),我当时亲身经历了他们书中写的许多事情。Microsoft在测试领域独步全球,也是一个测试精英云集的圣地。Microsoft的测试工程师在各种技术大会中也是广受欢迎的演讲嘉宾。Microsoft的第一任测试总监——Roger Sherman,吸引了来自全球的测试精英加入华盛顿的雷德蒙德(译注:微软总部所在地)。那是一个软件测试的黄金时代。

因此,Microsoft写了这样一本书来记录其发生的一切。

我没能赶上参与《微软的软件测试之道》的编写,但是在Google却有幸得到这样的机会。我来Google的时候,其测试正处于一个蓬勃发展的上升期。工程生产力团队的员工数量正以火箭喷发般的速度增长,从几百人迅猛发展到今天的1200人。正如Patrick在本书序言中所说的那样,这种增速随之而来的是成长的烦恼,这也是他们最后的阵痛,此后这个组织开始了前所未有的井喷式增长。Google的测试博客每月吸引了成千上万的人来浏览阅读,GTAC(注:GTAC是Google Test Automation Conference的缩写,即Google测试自动化大会,参见http://www.gtac.biz)大会也已经成了测试行业的旗帜性会议。在我来到Google不久之后,Patrick也得到了晋升,手下有十几个总监和工程经理直接汇报给他。如果你认为软件测试又进入到新的文艺复兴时期,那么Google一定就是位于中心的罗马。

这意味着Google背后的测试故事其实可以写成一本很厚的书。但问题是,我并不想这样做。Google之所以闻名于世,在于其实现软件的方法:简单和直截了当。或许这本书也可以保持这样的风格。

《Google软件测试之道》这本书的核心内容包括:详细讲述了作为一个Google的测试人员究竟意味着什么,同时也包含Google是如何解决软件在扩展性、复杂性和大并发方面的问题。如果想知道这些,阅读本书将是你的最佳获取途径。如果书中的内容还是不能满足你想要充分了解Google是如何测试的需求,互联网上还有更多的信息,你只需要“Google一下”。

关于本书由来的故事,不得不说的大概就是这些了。我也终于做好了准备来讲述Google是如何进行测试的。随着越来越多的软件公司从桌面应用转向网络应用,Google测试软件的方法也很有可能成为其他公司的榜样。如果你已经读了《微软测试之道》,那么千万不要试图在这本书中找一些共同点。除了两本书的作者都是三个人,且都是在讲述大型软件公司的测试实践之外,这两本书中所描述的测试方法可谓大相径庭。

注意


书中关于Google的测试方法,很有可能成为其他公司竞相模仿的榜样,特别是那些从桌面应用转向网络应用的公司。

Patrick Copeland在本书的序言中解释了Google测试方法演变的历史,随着公司的不断成长,它也在不停地、有组织地进化着。Google是个大熔炉,许多来自其他公司的工程师被抛进来熔炼。在前雇主公司使用的技术,如果被证明效率低下,该技术要么被遗弃,要么通过Google的创新文化再进行改良。随着测试工程师队伍的不断膨胀,就有了许多新的想法和实践的尝试,那些在实践中被证明很有用的技术会被Google保留下来,并成为Google的一部分;另外一些被证明是负担的,则会被抛弃掉。Google的测试者很愿意去尝试新技术,但有些技术一旦被发现并不实用,就会立刻被抛弃。

Google是一家以创新和速度为基础的公司,快速地发布有用的代码(如果失败,也只有少数早期用户会失望)、迭代地增加早期用户希望使用的功能(最大化用户反馈)。在这样的环境下,测试不得不变的异常灵活,并且在技能上要做许多前期的规划,只是不停地简单维护并不能真正解决问题。有时,测试和开发互相交织在一起,达到了无法区分彼此的程度,而在另外一些时候,测试和开发又是完全分离,甚至开发人员都不知道测试在做些什么。

注意


有时,测试和开发互相交织在一起,达到了无法区分彼此的程度,而在另外一些时候,测试和开发又是完全分离的,甚至开发人员都不知道测试在做些什么。

贯穿Google的整个发展史来看,当前Google的发展速度只比创业初期慢了一点点而已。虽然Google创业已是很久以前的历史,但还是可以在一年内就做出一个操作系统、在几周内就发布像Chrome这样的客户端应用、每天都在更新其网络应用程序。在这种环境下,很容易就可以说清楚测试并非“教条式的、强流程、体力密集型、耗时的”——这比定义测试是什么要简单的多,虽然本书一直在尝试解释测试是什么。有一件事是可以确定的,测试不能成为导致创新和开发过程变慢的阻碍。至少,这种情况不能出现两次。

Google在测试上的成功,不能简单地归结为其被测系统规模小且简单。Google软件应用的规模和复杂度与外面其他的公司一样。从客户端的操作系统到网络应用、移动端、企业级应用、商业应用、社交等各个方面,Google几乎无所不包。

Google的软件庞大且复杂,拥有数以亿计的用户,也是黑客们喜爱的攻击目标。绝大多数Google源代码都是开源的,这些代码对外公开,被外界所觊觎。多数代码是历史遗留代码,使用常规的代码审核来做代码评审。Google的代码服务于上百个国家,使用不同的语言,但是用户其实只是期望Google能够提供简单易用且“能够工作”的服务。Google的测试人员每天完成的工作,并非只是解决简单的问题,Google的测试人员每天都在面临不同的测试挑战。

Google的做法是否正确(很有可能是错误的)是一个值得商榷的事情,但有一点是确定的,Google的测试方法与其他我所了解的公司的测试方法有很大的不同。随着软件逐渐由桌面应用迁移到网络云端,Google的测试模式很有可能会逐渐成为测试行业的主流模式。在测试这个行业,如何做测试,从而保证可以开发出可靠的、值得信赖的软件,一直是这个行业值得争议的话题。我和本书的其他作者就希望通过本书可以很好地阐述Google的测试实践,从而可以引起一些讨论,达到抛砖引玉的目的。Google的测试方法或许有它的不足,但我们也乐意去对外公开它们,使之表露在业界和国际测试社区的眼皮之下,在经过外界的严格审查之后,我们才能持续地改进。

Google的测试方法看起来有点违背常理——在整个公司,我们只有非常少的专职测试人员,甚至比我们竞争对手公司的单个产品的测试人员还要少。在通往成功的道路上,Google的测试团队并非雄兵百万,我们更像是小而精的特种部队,我们依靠的是出色的战术和高级武器。由于资源的缺乏,这也是我们向特种部队方向发展的根本原因。没有足够的人手,使得我们不得不去做好优先级的安排,正如Larry Page所说,“少则清晰”。不管是功能方面的技术,还是测试方面的技术,在追求质量方面,我们已经学会了如何运用这些技术,创建高影响力、低阻力的实践活动。测试人员的稀缺会导致测试资源变得非常昂贵,因此,我们的原则就是让这些稀缺且聪明的测试员工保持昂扬的斗志和充沛的精力。当有人来问我,Google成功的关键是什么,我的第一个建议就是,不要招聘太多的测试人员。

注意


当有人来问我,Google成功的关键是什么,我的第一个建议就是,不要招聘太多的测试人员。

Google在测试人员如此缺乏的情况下,是如何应对的呢?简单地说,在Google,写代码的开发人员也承担了质量的重任。质量从来就不仅仅是一些测试人员的问题。在Google,每个写代码的开发者本身就是测试者,质量在名义上也由这样的开发测试组合共同承担,如图1.1所示。在Google,谈论开发测试比(译注:这里指在人员数量上,开发和测试的比率)就像讨论太阳表面的空气质量一样,这本身没有任何意义。如果你是一名工程师,那么你同时也是一名测试人员。如果在你的职位头衔上有测试的字样,你的任务就是怎样使那些头衔上没有测试的人可以更好地去做测试。

Google可以打造出世界级的软件,这也足以证明其对待质量的独特方法值得学习。或许其中的一些经验在其他的公司组织中也能适用。当然里面也有需要改进的地方。接下来所述就是关于Google测试方法的概要介绍。在后面的章节里,我们会深入到细节中,以此来阐述在以开发为中心的文化中Google是如何做测试的。

▲图1.1 与功能相比Google工程师更看重质量

质量不是被测试出来的——这句看似陈词滥调的话却包含着一定的道理。从汽车行业到软件行业,如果在最开始设计创建的时候就是错的,那它永远不会变成正确的。试问一下汽车行业的公司,大量召回事实上有质量问题的产品,代价是多么的昂贵。因此,从最初的创建阶段就要做正确,否则将会陷入混乱的万丈深渊。

然而,这句话也并不像听起来那样的简单和准确。虽然质量不是被测出来的,但同样有证据可以表明,未经测试也不可能开发出有质量的软件。如果连测试都没有做,如何保证你的软件具有很高的质量呢?

有一个简单的办法可以解决这个难题,那就是停止开发与测试的隔离对立。开发和测试应该并肩齐趋。你需要在写完每一段代码后立刻测试这段代码,当完成了更多的代码时就要做更多的测试。测试不是独立隔离的活动,它本身就是开发过程的一部分。质量不等于测试,当你把开发过程和测试放到一起,就像在搅拌机里混合搅拌那样,直到不能区分彼此的时候,你就得到了质量。

注意


质量不等于测试。当你把开发过程和测试放到一起,就像在搅拌机里混合搅拌那样,直到不能区分彼此的时候,你就得到了质量。

在Google,这正是我们的目标,就是把开发过程和测试融合在一起——开发和测试必须同时开展。写一段代码就立刻测试这段代码,完成更多的代码就做更多的测试,但这里的关键是由谁来做这些测试呢?众所周知,在Google,专职测试人员的数量非常稀少,与开发相比根本不成比例,唯一可能的去做这些的就只能是开发人员。还有谁能比实际写代码的人更适合做测试呢?还有谁能比实际写代码的人更适合去寻找bug呢?是谁会为了避免受更大刺激而去想办法避免产生bug呢?Google能用如此少的专职测试人员的原因,就是开发对质量的负责。如果某个产品出了问题,第一个跳出来的肯定是导致这个问题发生的开发人员,而不是遗漏这个bug的测试人员。

这意味着质量更像是一种预防行为,而不是检测。质量是开发过程的问题,而不是测试问题。我们已经成功地将测试实践融入为开发过程的一部分,并创建了一个增量上线的流程。如果一些项目在线上被证实的确是bug重重,它将会被回滚到之前的版本。在确保不出现回滚级别bug发生的前提下,预防了许多客户问题的同时,也很大程度降低了专职测试人员的数量。在Google,测试的目标就是来判断这种预防工作做的怎么样。

把开发过程和测试混合在一起,密不可分,从代码审核问询时的“你的测试在哪儿”,再到在卫生间张贴着的、用来提醒开发人员的最佳测试实践(注3:参见http://googletesting.blogspot.com/2007/01/introducing-testing-on-toilet.html)。测试是开发过程中必不可少的一部分,当开发过程和测试一起携手联姻时,既是质量达成之时。

注意


测试是开发过程中必不可少的一部分,当开发过程和测试一起携手联姻时,即是质量达成之时。

为了保证“解铃还需系铃人”这句名言成为事实(译注:“you build it,you break it”,摘自“you build it,you break it,you fix it”。原意指在构建实验室(Build Lab)的人永远不会去修复构建失败(build break)的问题,只有开发人员自己才能修复。这里的意思是开发人员自己要对自己写的代码负责,比专职的测试人员更适合做测试工作。在传统的开发岗位之外我们又增加了几种角色。我们明确地提出了有一种工程师角色必须存在,他可以让开发人员更加有效且高效地做测试。在Google,我们的确创建了这样的角色,他的职责就是让其他的工程师更有效率和质量意识。这些角色常把他们自己看做是测试者,但实际上他们的使命是提高生产率。测试人员的存在是为了让开发人员的工作更有效率,并且很大一部分体现在避免因马虎粗心而导致的返工,因此,质量也是效率的一部分。在接下来的章节里,会花费较多的内容来详细讲解这些角色,所以在这里只进行简单的介绍。

软件开发工程师(译注:software engineer,后文简称SWE)是一个传统上的开发角色,他们的工作是实现最终用户所使用的功能代码。他们创建设计文档、选择最优的数据结构和整体架构,并且花费大量时间在代码实现与代码审核上。SWE需要编写与测试代码,包括测试驱动的设计、单元测试、参与构建各种大小规模的测试等,这些测试会在本章的后面做详细解释。SWE会对他们编写、修复以及修改的代码承担质量责任。假设一个开发者不得不修改一个函数,如果这次修改导致已有测试用例运行失败,或者需要增加一个新的测试用例,他就必须去实现这个测试用例的代码。开发工程师几乎将所有的时间都花费在了代码编写上。

软件测试开发工程师(译注:software engineer in test,后文简称SET)也是一个开发角色,只是工作重心在可测试性和通用测试基础框架上。他们参与设计评审,非常近距离地观察代码质量与风险。为了增加可测试性,他们甚至会对代码进行重构,并编写单元测试框架和自动化测试框架。SET是SWE在代码库上的合作伙伴,相比较SWE是在增加功能性代码或是提高性能的代码,SET更加关注于质量提升和测试覆盖率的增加。SET同样会花费近百分之百的时间在编写代码上,他们这样做的目的是为质量服务,而SWE则更关注客户使用功能的开发实现上。

注意


SET是SWE在代码库上的合作伙伴,与增加功能性代码或提高性能的代码的SWE相比,SET更加关注于质量的提升和测试覆盖率的增加。SET写代码的目的是可以让SWE测试自己的功能。

测试工程师(译注:test engineer,后文简称TE)是一个和SET关系密切的角色,有自己不同的关注点——把用户放在第一位来思考,代表用户的利益。一些Google的TE会花费大量时间在模拟用户的使用场景和自动化脚本或代码的编写上。同时,他们会把开发工程师和SET编写的测试分门别类地组织起来,分析、解释、测试运行结果,驱动测试执行,特别是在项目的最后阶段,推进产品发布。TE是真正的产品专家、质量顾问和风险分析师。某些TE需要编写大量的代码,而另外一些TE则只用编写少量的代码。

注意


TE把用户放在第一位来思考。TE组织整体质量实践,分析解释测试运行结果,驱动测试执行,构建端到端的自动化测试。

从质量的角度来看,SWE负责功能实现和这些独立功能的质量。他们对容错设计、故障恢复、测试驱动设计、单元测试负责,并和SET一起编写测试代码。

SET也是开发人员,负责提供测试支持。有这样一个测试框架,它可以把新开发的代码隔离,通过模拟一个真实的工作运行环境(一个包含stubs、mock、fake等方法的流程,这些内容会在后面详细讲到)和代码提交队列来管理代码的提交。换句话说,SET编写代码,通过这些代码提供的功能让SWE能够自己测试他们的功能。多数测试代码是由SWE完成,SET存在的目的就是保证这些功能模块具有可测试性,并且相应的SWE还可以积极地参与到测试代码的编写中去。

很明显,SET的主要关注对象就是开发人员。SET的主要职责是让开发者可以很容易地编写测试代码,从而达到独立功能模块的质量要求。专注于用户角度的测试则是TE的职责。考虑到SWE和SET已经做了足够多的模块级别与功能级别的测试,下一步要考虑的就是要验证这些可执行的代码与数据集成在一起之后,是否可以满足最终用户的需求。在这里,TE扮演着一个双重确认的角色,确认开发人员在测试方面的工作是否到位,任何明显的bug都会表明早期开发人员所做的测试工作存在不足或比较马虎。当这些明显的bug变少时,TE会把注意力转移到常见用户使用场景中去,是否满足性能期望,在安全性、国际化、访问权限等方面是否满足用户的要求。TE运行许多测试的同时,也负责和其他团队的TE、合同工编制的测试人员、以众包形式参与的测试者、内部尝鲜者、beta测试者以及早期用户进行合作交流,与各方讨论基本设计带来的风险、功能逻辑复杂性和错误避免的方法。一旦TE参与到项目之中,基本上就会没完没了。

在我过去曾经工作过的多数组织中,开发人员和测试人员都一起隶属于同一个工程产品团队。从组织架构上讲,开发人员和测试人员汇报给同一个产品团队的管理者。这样看起来,同一个产品、同一个团队、所有参与的人都在一起,应该可以做到平等相处、患难与共。

但不幸的是,我还从来没见过有团队能真正做到这样。资深管理者一般都来自产品经理或开发经理,而不是来自于测试团队。在产品发布时,优先考虑的是功能的完备性和易用性方面是否足够简单,却很少考虑质量问题。作为同一个团队,测试总是在为开发让路。为何我们这个行业里总是充斥着各种有缺陷的、早产的产品,或许这就是问题所在。质量不行就再发布一个补丁包。

注意


资深管理者一般都来自产品经理或开发经理,而不是来自于测试团队。在产品发布时,优先考虑的是功能是否完整和易用性方面是否足够简单,却很少考虑质量。作为同一个团队,测试总是在为开发让路。

Google的组织汇报关系被划分为不同的专注领域(Focus Areas)。这些专注领域包括客户端(Chrome、Google工具栏等)、地理(地图、Google Earth等)、广告、Apps、移动,等等。所有的开发工程师都汇报给这些专注领域的管理者、总监或副总裁。

但SET和TE并没有遵循这个模式。测试是独立存在的部门,是与专注领域部门平行的部门(横跨各个产品专注领域),我们称为工程生产力团队。测试人员基本上以租借的方式进入产品团队,去做提高质量相关的事情,寻找一些测试不足的地方,或者公开一些不可接受的缺陷率数据。由于测试人员并不是直接向产品团队进行汇报,因此我们并不是简单地被告之某个项目急需发布就可以通过测试。我们有自己选择决定的优先级,在可靠性、安全性等问题上都不会妥协,除非碰到更重要的事情。如果开发团队想要我们在测试上放他们一马,他们必须事先和我们协商,但一般情况下也都会被拒绝。

这样的组织结构也可以帮助我们保持数量较少的测试人员。一个产品团队不能任意降低测试人员招聘的技术要求,从而雇佣更多的测试人员,然后再让他们做一些简单和琐碎的脏活累活。这些功能相关的脏活累活本应是开发人员的工作,不能简单地扔给倒霉的测试人员。工程生产力团队会根据不同产品团队的优先级、复杂度,并与其他产品实际比较之后,再来分配测试人员。显然,有时候我们可能搞错,实际上也确实出过错,但总体来说,这样会保持实际的需求与不明确的需求之间的某种平衡。

注意


工程生产力团队会根据不同产品团队的优先级、复杂度,并与其他产品实际比较之后,再来分配测试人员。显然,有时候我们可能搞错,实际上也确实出过错,但总体上来说,这样会保持实际的需求与不明确的需求之间的某种平衡。

这种测试人员在不同项目之间的借调模式,可以让SET和TE时刻保持新鲜感并且总是很忙碌,另外还能保证一个好的测试想法可以快速在公司内部蔓延。一个在Geo产品上运用很好的测试技术或工具,很有可能在Chrome产品中也得到使用。推广测试技术方面创新的最佳方式,莫过于把这个创新的发明者直接借调过来。

在Google有一个广泛被接受的做法:对于一个测试人员,如果在某个产品中工作满18个月之后,就可以无理由地自愿转岗到其他产品,当然这个转岗并不是强制的。可以想象一个产品失去优秀测试专家而带来的悲痛,但从整个公司的角度来看,需要保持对各个产品与技术都了解的测试人员的存在。Google的测试工程师在客户端、Web、浏览器、移动技术等领域都有所涉猎,可以高效地使用不同的语言和平台。由于Google的产品和服务很大程度上有比较强的集成关联关系,测试人员可以很容易地保持相关的专业技能,并在公司范围内的产品之间自由穿梭。

在拥有如此少量测试人员的情况下,Google还可以取得不错的成果,核心原因在于Google从来不会在一次产品发布中包含大量的功能。实际上,我们的做法恰恰相反,在一个产品的基本核心功能实现之后,就立刻对外发布使用,然后从用户那里得到真实反馈,再进行迭代开发。这也是我们在Gmail产品上的经验,Gmail带着beta标签在线上运营了四年,这个标签用以警示我们的用户,Gmail仍处于改良之中。对于最终用户,只有该产品达到99.99%的可用性时,我们才会把beta标签去掉。在Android G1这个产品上,我们再次使用了这个方法,让这个非常有用且经过良好设计的产品变得更棒了,功能也更加丰富全面,之后的Nexus手机也采用了相同的策略。有一点需要引起注意,对于初期版本的用户,并不是因为这个产品还处于早期版本就不为之提供足够的功能,早期版本并不意味着是一个不可用的烂版本。

注意


Google经常在最初的版本里只包含最基本的可用功能,然后在后继的快速迭代的过程中得到内部和外部用户的反馈,而且在每次迭代的过程中都非常注重质量。一个产品在发布给用户使用之前,一般都要经历金丝雀版本、开发版本、测试版本、beta或正式发布版本。

Google发布的过程虽然快,但也并不像想象中如牛仔一般的鲁莽与仓促。实际上,为了发布我们称为beta的版本,一个产品要经历一系列的内部版本验证,用以证明它已经具备了一定的质量。例如Chrome,这是我加入Google之后的两年都为之工作的一个产品,根据我们对产品的信心以及来自用户的反馈,我们在整个过程中使用了不同的版本,大致顺序如下。

注意


Android团队在这方面有更勇敢的尝试,所有核心开发团队成员的手机上都安装有每日构建的版本。这样做是为了减少往代码库中提交有问题的代码,一旦安装了错误代码,手机甚至都无法使用其基本功能,例如和家人通话。

这种爬、走、跑的模式,给我们的应用程序尽早地提供了一个测试验证的良好机会。与从自动化测试那里得到的反馈一样,我们每天都能从内部用户那里得到关于这些版本的质量反馈。

Google并没有使用代码测试、集成测试、系统测试等这些命名方式,而是使用小型测试、中型测试、大型测试这样的称谓(不要和敏捷社区发的那些T恤型号混为一谈),着重强调测试的范畴规模而非形式。小型测试意味着涵盖较少量的代码,其他的测试类型以此类推。Google的三类工程师都会去执行其中的任何一种测试,无论是自动化的还是手动的。测试的规模越小,就越有可能被实现成为自动化的测试。

提示


Google并没有使用代码测试、集成测试、系统测试这些命名方式,而是使用小型测试、中型测试、大型测试这样的称谓,着重强调测试的范畴规模而非形式。

小型测试__一般来说(但也并非所有)都是自动化实现的,用于验证一个单独函数或独立功能模块的代码是否按照预期工作,着重于典型功能性问题、数据损坏、错误条件和大小差一错误(译注:大小差一(off-by-one)错误是一类常见的程序设计错误)等方面的验证。小型测试的运行时间一般比较短,通常是在几秒或更短的时间内就可以运行完毕。通常,小型测试是由SWE来实现,也会有少量的SET参与,TE几乎不参与小型测试。小型测试一般需要使用mock和fake(译注:mock对象是指对外面依赖系统的模拟,在运行时刻可以根据假设的需求提供期望的结果。fake对象是一种虚假的实现,内部使用了固定的数据或逻辑,只能返回特定的结果。更多参见http://stackoverflow.com/questions/346372/whats-the-difference-between-faking-mocking-and-stubbing)才能运行。TE几乎不编写小型测试代码,但会参与运行这些测试,来诊断一些特定错误。小型测试主要尝试解决的问题是“这些代码是否按照预期的方式运行”。

中型测试__通常也都是自动化实现的。该测试一般会涉及两个或两个以上,甚至更多模块之间的交互。测试重点在于验证这些“功能近邻区”之间的交互,以及彼此调用时的功能是否正确(我们称功能交互区域为“功能近邻区”)。在产品早期开发过程中,在独立模块功能被开发完毕之后,SET会驱动这些测试的实现及运行,SWE会深度参与,一起编码、调试和维护这些测试。如果一个中型测试运行失败,SWE会自觉地去查看分析原因。在开发过程的后期,TE会通过手动的方式(如果比较难去实现自动化或实现的代价较大时),或者自动化地执行这些用例。中型测试尝试去解决的问题是,一系列临近的模块互相交互的时候,是否如我们预期的那样工作。

大型测试__涵盖三个或以上(通常更多)的功能模块,使用真实用户使用场景和实际用户数据,一般可能需要消耗数个小时或更长的时间才能运行完成。大型测试关注的是所有模块的集成,但更倾向于结果驱动,验证软件是否满足最终用户的需求。所有的三种工程师角色都会参与到大型测试之中,或是通过自动化测试,或是探索式测试。大型测试尝试去解决的问题是,这个产品操作运行方式是否和用户的期望相同,并产生预期的结果。这种端到端的使用场景以及在整体产品或服务之上的操作行为,即是大型测试关注的重点。

注意


小型测试涵盖单一的代码段,一般运行在完全虚假实现(fake)的环境里。中型测试涵盖多个模块且重点关注在模块之间的交互上,一般运行在虚假实现(fake)环境或真实环境中。大型测试涵盖任意多个模块,一般运行在真实的环境中,并使用真正的用户数据与资源。

小型、中型、大型等描述术语是什么并不重要,怎么称呼它们也都可以,只要大家都一致认可。重要的是,在Google测试人员使用统一术语来谈论他们测试的是什么,以及这些测试范围是如何划分的。一些雄心勃勃的测试者有时会说到第四级别的测试,即被称为“超大型测试”,公司里的其他测试同仁会认为这是一个超大级别的系统测试,涵盖所有的功能且运行时间会非常长。对于一些术语,不需要用过多的文字去解释,按照字面意思就可以理解,这样做是最好的。

我们的测试对象以及测试范围的大小是动态变化的,不同产品之间的区别也比较明显。Google喜欢频繁地发布,并快速地从外部用户那里得到产品的真实反馈,然后再迭代开发出新功能。Google积极努力地开发用户非常感兴趣的产品特性,并尽可能早地提供一些功能给用户使用。另外,我们也在避免做一些用户不想要的产品特性,这就要求我们要非常及时地把用户和外部开发者一起拉进来参与,这样可以更有利于判断我们发布的产品是否满足用户的真正需求。

最后,关于自动化测试和手动测试的比例,对于所有的三种类型测试,当然更倾向于前者。如果能够自动化,并不需要人脑的智睿与直觉来判断,那就应该以自动化的方式实现。但在一些情况下需要人类智慧的判断,例如,用户界面是否漂亮、保留的数据是否包含隐私等,这些还是需要手动测试来完成。

注意


对于所有的三种类型测试,当然更倾向于前者。如果能够自动化,并不需要人脑的智睿与直觉来判断,那就应该以自动化的方式实现。

正如上文中提到的,同时也是值得重点关注的一点,Google也有大量的手动测试,有些使用脚本的方式在记录(译注:scripted case,把每一个步骤都记录下来的用例表示方式。注意,这里scripted case,不是指通过脚本实现的自动化用例,这里只是强调一种case的实现方式),而另外一些使用探索式的方法,这些测试都在被密切地关注,以后可能被自动化方式所替代。通过使用定位点击的验证方式、录制技术等可以把一些手动测试转变成自动化测试,这些自动化测试在每次建立之后都会重复地回归运行,而手动测试更倾向于关注于新功能。我们甚至把开bug和日常的手动工作都自动化实现了,例如,如果自动化用例运行失败,系统会自动检查到最后一次代码变更的内容,这些变更极有可能是造成失败的罪魁祸首。系统会自动给代码变更的提交者发送一封邮件,并新开一个bug来记录这个问题。将自动化做到,力争克服“人类智慧的最后一英寸”这也是Google的设计理念与目标,也正是正在构建之中的下一代测试工具的努力方向。


在理想情况下,一个完美的开发过程是怎样进行的呢?测试先行,在一行代码都没有真正编写之前,一个开发人员就会去思考如何测试他即将编写的代码。他会设计一些边界场景的测试用例,数据取值范围从极大到极小、导致循环语句超出限制范围的情况,另外还会考虑很多其他的极端情况。这些测试代码会作为产品代码的一部分,以自检代码或单元测试代码的形式与功能代码存储在一起。对于此种类型的测试,最合适且最有资格去做的人,其实就是编写功能代码的人。

另外一些测试需要的知识在本产品代码之外,通常都依赖于外部基础设施服务。例如,一个测试用例需要从远程数据源(一个数据库或者云端)读取数据,这就需要存在一个真实数据库或模拟的数据库。在过去几年中,工业界使用了各种特定术语来描述这些辅助设施,包括测试框架测试通用设施模拟设施虚拟设施(译注:test harnesses, test infrastructure, mock and fake)。在假想的完美开发过程中,在你做功能测试时,如果需要,这些工具都应该及时出现在你眼前,任由你使用(记住,这是在一个真正理想的软件世界里)。

在理想开发过程中首次需要测试人员的时刻即将来临。对于人的思维方式而言,在编写功能代码的时候与编写测试代码的时候是迥然不同的,这也就需要去区分功能开发人员和测试开发人员(译注:原文是feature developer and test developer)。对于功能代码而言,思维模式是创建,重点在考虑用户、使用场景和数据流程上;而对于测试代码来说,主要思路是去破坏,怎样写测试代码用以扰乱分离用户及其数据。由于我们假设的前提是在一个童话般的理想开发过程里,所以我们或许可以分别雇佣不同的开发工程师:一个写功能代码,而另一个思考如何破坏这些功能(译注:两种开发工程师,分别是功能开发人员和测试开发人员)。

注意


编写功能代码和编写测试代码在思维方式上有着很大的不同。

在这样乌托邦式(译注:乌托邦是一个理想的群体和社会的构想,名字由托马斯·摩尔的《乌托邦》一书中所写的完全理性的共和国“乌托邦”而来,意指理想完美的境界)的理想开发过程中,众多的功能开发人员(译注:feature developer)和测试开发人员(译注:test developer)需要通力合作,共同为打造同一款产品而努力。在我们假想的完美理想情况下,产品的每一个功能都对应一个开发人员,整个产品则配备一定数量的测试开发人员。测试开发人员通过使用测试工具与框架帮助功能开发人员解决特定的单元测试问题,而这些问题如果只是由功能开发人员独自完成,则会消耗掉他们许多的精力。

功能开发人员在编写功能代码的时候,测试开发人员编写测试代码,但我们还需要第三种角色,一个关心真正用户的角色。显然在我们理想化的乌托邦测试世界里,这个工作应该由第三种工程师来完成,既不是功能开发人员,也不是测试开发人员。我们把这个新角色称为用户开发人员(译注:user developer)。他们需要解决的主要问题是面向用户的任务,包括用例(use case)、用户故事、用户场景、探索式测试等。用户开发人员关心这些功能模块如何集成在一起成为一个完整的整体,他们主要考虑系统级别的问题,通常情况下都会从用户角度出发,验证独立模块集成在一起之后是否对最终用户产生价值。

这就是我们眼中软件开发过程的乌托邦理想模式,三种开发角色在可用性和可靠性方面分工合作,达到完美。每个角色专门处理重要的事情,相互之间又可以平等地合作。

谁不想为这样的软件开发公司工作呢?大家全都要报名应聘!

但不幸的是,这样的公司目前还不存在,Google也只是比较接近而已。Google与其他公司一样,都在尽力去尝试成为这样的公司。或许是因为Google起步较晚,我们有机会从前人那里吸取了很多经验教训。当前软件正经历一个巨大的转变,从发布周期需要以年为单位的客户端模式向每周、每天,甚至每小时都会发布的云端模式转变(注:一个有趣的事情需要说明一下,即使是客户端软件,Google也喜欢常去更新,客户端使用一个“自动更新”的功能,几乎所有的客户端应用都有这个功能),而Google也从这次转换浪潮之中受益良多。在这两种原因的促进下,Google的软件开发流程与乌托邦模式也有了几分相似。

Google的SWE就是功能开发人员,负责客户使用的功能模块开发。他们编写功能代码及这些功能的单元测试代码。

Google的SET就是测试开发人员,部分职责是在单元测试方面给予开发人员支持,另外一部分职责是为开发人员提供测试框架,以方便他们编写中小型测试,用以进行更多质量相关的测试工作。

Google的TE就是用户开发人员,负责从用户的角度来思考质量方面各种问题。从开发的角度来看,他们编写用户使用场景方面的自动化用例代码;从产品的角度看,他们评估整体测试覆盖度,并验证其他工程师角色在测试方面合作的有效性。这不是乌托邦,这就是Google实践之路上最好的尝试,前进的道路上充满了不可预料且无路可退。

注意


Google的SWE是功能开发人员;Google的SET是测试开发人员;Google的TE是用户开发人员。

在这本书里,我们将会着重介绍SET和TE这两个角色的工作内容,也会包含少量SWE的工作内容,作为上述两种角色的补充。虽然SWE也重度参与测试工作,但一般情况下都是在头衔中包含“测试”的工程师的指导之下完成的。

在任何软件公司创立的初期阶段,通常都没有专职的测试人员(译注:本节标题“SET的工作”,因为原文为The Life of an SET。“The Life of ”是Google内部系列课程(搜索和广告是如何工作的)中使用的特定术语。针对Nooglers(新Google员工)的课程里,Life of a Query揭秘搜索query是如何实现的,Life of a Dollar揭秘广告系统的工作原理)。当然那时候也没有产品经理、计划人员、发布工程师、系统管理员等其他角色。每位员工都独自完成所有工作。我们也经常想象Larry和Sergey(译注:Google的早期创始人之一)在早期是如何思考用户使用场景和设计单元测试的样子。随着Google的不断成长壮大,出现了第一个融合开发角色和质量意识于一身的角色,即SET(注:Patrick Copeland在本书的序中已经介绍了SET的出现背景)。

在详细讲解SET工作流程之前,我们先来了解一下SET的工作背景,这对理解整个开发过程将十分有益。在新产品的开发过程中,SET和SWE是紧密合作的伙伴,他们达成一致,甚至一些实际工作也会有所重叠。Google其实就是这样设计的,Google认为测试工作是由整个工程团队负责,而不仅仅单独由那些头衔上带着“测试”的工程师来负责。

工程师团队的交付物就是即将发布的代码。代码的组织形式、开发过程、维护是日常工作重点。Google多数代码存放在同一个代码库中,并使用统一的一套工具。这些工具和代码支撑着Google的构建和发布流程。Google所有的工程师无论是什么角色,对如何使用这些工具环境都非常地熟练,团队成员可以毫不费力地完成新代码的入库、提交、执行测试、创建版本等任务(前提是角色有这样的需求)。

注意


工程师团队的交付物就是即将要发布的代码。代码的组织形式、开发过程、维护是日常的工作重点。

这种单一的代码库模式,使得工程师可以很从容地在不同项目之间转换而几乎不需要什么学习成本。这为工程师提供了很大便利,这种单一的代码库模式让工程师从他们进入项目开始的第一天起,其“百分之二十的贡献”(译注:“百分之二十时间”是指Googler称为的“业余项目”。这并不是一个炒作的概念,而是官方真正存在的,允许所有Googler每周投入一天时间在他的日常工作之外的项目上。每周四天工作用来赚取薪水,剩下一天用以试验和创新。这并不是完全强制的,之前有些Googler认为这个想法只是一个传说。根据我们的真实经历,这个概念是真正存在的,我们三个都参与过“百分之二十时间”项目。实际上,本书提及的许多工具都是“百分之二十”项目的结晶。在现实中,许多Goolers选择把“百分之二十时间”投入到新产品之中,特别是一些听起来很酷的产品,很享受这种工作模式)极具效率。这也意味着对于有需求的工程师,所有的源代码对他们都是开放的。Web 应用的开发人员无须申请任何权限,就能查看所有可以简化他们工作的浏览器端代码。他们从有经验的工程师那里学习到在类似场景下如何编写代码,他们可以重用一些通用模块或详细的数据结构,甚至是重用一些程序控制结构。Google在代码库搜索方面也提供了非常便利的功能。

公开的代码库、和谐的工程工具、公司范围内的资源共享,成就了丰富的Google内部共享代码库与公共服务。这些共享的代码运行依赖于Google的基础设施产品,它们在加速项目完成与减少项目失败上发挥了很大作用。

注意


公开的代码库、和谐的工程工具、公司范围内的资源共享,成就了丰富的Google内部共享代码库与公共服务。

工程师们对这些共享的基础代码做了特殊处理,形成了一套不成文但却非常重要的实践规则,工程师在维护修改这些代码的时候都要遵守这些规则。

最小化对平台的依赖。所有工程师都有一台桌面工作机器,且操作系统都尽可能地与Google生产环境的操作系统保持一致。为了减少对平台的依赖,Google对Linux发行版本的管理也十分谨慎,这样开发人员在自己工作机器上测试的结果,与生产系统里的测试结果会保持一致。从桌面到数据中心,CPU和OS的变化尽可能小(注:唯一不在Google通用测试平台里的本地测试实验室,是Android和Chrome OS。这些类目不同的硬件必须在手边进行测试)如果一个bug在测试机器上出现,那么在开发机器上和生产环境的机器上也都应该能够复现。

所有对平台有依赖的代码,都会强制要求使用公共的底层库。维护Linux发行版本的团队同时也在维护这个底层平台相关的公共库。还有一点,对于Google使用的每个编程语言,都要求使用统一的编译器,这个编译器被很好地维护着,针对不同的Linux发行版本都会有持续的测试。这样做本身其实并没有什么神奇之处,但限制运行环境可以节省大量下游的测试工作,也可以避免许多与环境相关且难以调试的问题,能把开发人员的重心转移到新功能开发上。保持简单,也就相对会安全。

注意


Google在平台方面有特定的目标,就是保持简单且统一。开发工作机和生产环境的机器都保持统一的Linux发行版本;一套集中控制的通用核心库;一套统一的通用代码、构建和测试基础设施;每个核心语言只有一个编译器;与语言无关的通用打包规范;文化上对这些共享资源的维护表示尊重且有 激励。

使用统一的运行平台和相同的代码库,持续不断地在构建系统中打包(译注:打包是一个过程,包括将源代码编译成二进制文件,然后再把二进制文件统一封装在一个linux rpm包里面),这可以简化共享代码的维护工作。构建系统要求使用统一的打包规范,这个打包规范与项目特定的编程语言无关,与团队是否使用C++、Python或Java也都无关。大家使用同样的“构建文件”来打包生成二进制文件。

一个版本在构建的时候需要指定构建目标,这个构建目标(可以是公共库、二进制文件或测试套件)由许多源文件编译链接产生。下面是整体流程。

(1)针对某个服务,在一个或多个源代码文件中编写一类或一系列功能函数,并保证所有代码可以编译通过。

(2)把这个新服务的构建目标设定为公共库。

(3)通过调用这个库的方式编写一套单元测试用例,把外部重要依赖通过mock模拟实现。对于需要关注的代码路径,使用最常见的输入参数来验证。

(4)为单元测试创建一个测试构建目标。

(5)构建并运行测试目标,做适当的修改调整,直到所有的测试都运行成功。

(6)按要求运行静态代码分析工具,确保遵守统一的代码风格,且通过一系列常见问题的静态扫描检测。

(7)提交代码申请代码审核(后面对代码审核会做更多详细说明),根据反馈再做适当的修改,然后运行所有的单元测试并保证顺利通过。

产出将是两个配套的构建目标:库构建目标和测试构建目标。库构建目标是需要新发布的公共库、测试构建目标用以验证新发布的公共库是否满足需求。注意:在Google许多开发人员使用“测试驱动开发”的模式,这意味着步骤(3)会在步骤(1)和步骤(2)之前进行。

对于规模更大的服务,通过链接编译持续新增的代码,构建目标也会逐渐变大,直到整个服务全部构建完成。在这个时候,会产生二进制构建目标,其由包含主入口main函数文件和服务库链接在一起构成。现在,你完成了一个Google产品,它由三部分组成:一个经过良好测试的独立库、一个在可读性与可复用性方面都不错的公共服务库(这个服务库中还包含另外一套支持库,可以用来创建其他的服务)、一套覆盖所有重要构建目标的单元测试套件。

一个典型的Google产品由许多服务组成,所有产品团队都希望一个SWE负责对应一个服务。这意味着每个服务都可以并行地构建、打包和测试,一旦所有的服务都完成了,他们会在一个最终的构建目标里一起集成。为了保证单独的服务可以并行地开发,服务之间的接口需要在项目的早期就确定下来。这样,开发者会依赖在协商好的接口上,而不是依赖在需要开发的特定库上。为了不耽搁服务级别之间的早期测试,这些接口一般都不会真正实现,而只是做一个虚假的实现。

SET会参与到许多测试目标的构建之中,并指出哪些地方需要小型测试。在多个构建目标集成在一起,形成规模更大应用程序的构建目标时,SET需要加速他们的工作,开始做一些更大规模的集成测试。在一个单独的库构建目标中,需要运行几乎所有的小型测试(由SWE编写,所有支持这个项目的SET都会给予帮助)。当构建目标日益增大时,SET也会参与到中大型测试的编写之中去。

在构建目标的增长到一定规模时,针对功能集成的小型测试会成为回归测试的一部分。如果一个测试用例,本应该运行通过,但如果运行失败,也会报一个测试用例的bug。这个针对测试用例的bug和针对功能的bug没有任何区别。测试就是功能的一部分,问题较多的测试就是功能性bug,一定要得到修复。这样才可以保证新增的功能不会把已有功能损坏掉,任何代码的修改都不会导致测试本身的失败。

在所有的这些活动中,SET始终是核心参与者。他们在开发人员不知道哪些地方需要单元测试的时候可以明确指出。他们同时编写许多mock和fake工具。他们甚至编写中大型集成测试。好了,现在是展开讨论SET工作的时候了。

SET首先是工程师角色,他使得测试存活于先前讨论的所有Google开发过程之中。SET(software engineer in test)是软件测试开发工程师。最重要的一点,SET是软件工程师,正如我们招聘宣传海报和内部晋升体系中所说的那样,是一个100%的编码角色。这种测试方式的有趣之处在于它使测试人员能尽早介入到开发流程中去,但不是通过“质量模型”和“测试计划”的方式,而是通过参与设计和代码开发的方式。这会使得功能的开发工程师和测试的开发工程师处于相同的地位,SET积极参与各种测试,使测试富有效率,包括手动测试和探索式测试,而这些测试后期会由其他工程师负责。

注意


测试是应用产品的另外一种功能,而SET就是这个功能的负责人。

SET与功能开发人员坐在一起(实际上,让他们物理位置坐在一起是也是我们的设计目标)。这样讲可能更公平一些,测试也是应用产品的一种功能特性,而SET是这个产品功能特性的负责人。SET参与SWE的代码评审,反之亦然。

在面试SET的时候,在代码要求标准上与SWE的招聘要求是一样的,而且增加了一个额外考核——SET需要了解如何去测试他们编写的代码。换句话说,SWE和SET都需要回答代码问题,而且SET还要求去解答测试问题。

正如你想象的那样,找到满足如此条件的人是非常困难的,在Google,SET的数量也相对比较少,这并不是因为Google在生产率方面有什么神奇的开发测试比要求,而是因为招聘到满足SET技能要求的人实在太难了。SWE和SET这两个角色比较相似,在招聘方面这两个群体的要求也类似。假想这样的场景,公司里的开发人员可以做测试,而测试人员可以写代码。Google其实还没有完全做到这一点,或许永远也做不到。这两大群体之间相互交流学习,SWE向SET学习,SET也在学习SWE,正是我们这些最优秀的工程师一起构成了我们最有效率的工程产品团队。

Google没有规定SET何时进入项目,同样也没有规定怎样的项目才算是“真正”的项目。通常情况下,在Google的产品项目初期阶段,工程师只会投入20%的时间。Gmail和Chrome OS也是从一个想法演变而来,初期也并没有任何Google官方资源的投入,这些资源来源于团队开发测试成员的业余时间。事实上也正如我们的朋友Alberto Savoia(本书的序言的作者之一,详细介绍参见序部分)所说的那样,“只有在软件产品变的重要的时候质量才显得重要”。

许多创新的产品都是来源于团队20%的业余时间。这些时间投入的产品有些慢慢地消失了,而另外一些规模会越做越大,有的甚至会成为Google的官方产品。在这些产品的初期,没有一个会得到测试资源。在未来可能失败的项目中投入测试资源来构建测试方面基础设施,这是一种资源浪费。如果项目被取消了,那么这些创建好的测试也会毫无价值。

一个产品如果在概念上还没有完全确定成型时就去关心质量,这就是优先级混乱的表现。许多来源于Google百分之二十努力的产品原型,在其以后的dogfood或beta版本发布时,还要经历重新设计,原始代码保留的概率几乎为零。很明显,在试验初期阶段强调测试是一件非常愚蠢的事情。

当然,物极必反,风险总是相对的。如果一个产品太长时间没有测试的介入,早期在可测试性上的槽糕设计在后期也很难去做改进,这样会导致自动化难以实施且测试工具极不稳定。在这种情况下,不得不以质量的名义来做重构。这样的质量“债”会拖慢产品的发布,甚至长达数年之久。

在项目早期,Google一般不会让测试介入进来。实际上,即使SET在早期参与进来,也不是从事测试工作,而是去做开发。绝非有意忽视测试,当然也不是说早期产品的质量就不重要。这是受Google非正式创新驱动产品的流程所约束。Google很少在项目创建初期就投入一大帮人来做计划(包括质量与测试计划),然后再让一大群开发参与进来。Google项目的诞生从来没有如此正式过。

Chrome OS是一个可以说明问题的典型例子。本书的三个作者都在这个产品上工作过一年以上。但是,在我们正式加入之前,只有几个开发人员做了原型,且多数实现都是脚本与伪件(fake),这样他们可以拿着浏览器应用模型做演示,并通过正式的立项批准。在这些早期原型阶段,主要精力都集中在如何试验并证明这些想法的可行性上。考虑到项目还没有正式批准,且所有的演示脚本最终都会被C++代码重写替换,如果在早期投入大量测试和可测试性方面努力,其实没有太大的实用价值。为了演示而使用脚本搭建的产品,一旦得到正式批准立项,其开发总监就会找到工程生产力团队,寻求测试资源。

Google内部其实也并存着不同的文化。没有项目会认为如果得不到测试资源,他们的产品就将不复存在。开发团队在寻求测试帮助的时候,有义务让测试人员相信他们的产品是令人兴奋且并充满希望的。在Chrome OS的开发总监给我们介绍他们项目、进度和发布计划时,我们也要求提供当前已有的测试状态、期望的单元测试覆盖率水平、以及明确在发布过程中各自承担的责任。在项目还是概念阶段的时候,测试人员不会参与进来,而项目一旦真正立项,我们就要在这些测试是如何执行的方面发挥我们的影响力。

注意


没有项目会认为如果得不到测试资源,他们的产品就将不复存在。开发团队在寻求测试帮助的时候,有义务让测试人员相信他们的产品是令人兴奋且并充满希望的。

SWE会深入他们自己编写的那部分代码之中,通常这部分代码只是某个单一功能的模块甚至更小范围的代码。SWE一般仅在自己的模块领域里提供最优方案,但如果从整个产品的角度来看,视野会显得略微狭窄。一个好的SET正好可以弥补这一点,不仅要具有更宽广的整体产品视野,而且在产品的整个生命周期里对产品及功能特性做充分理解,许多SWE来往穿梭于不同产品,但产品的生命存活期比SWE待在产品里的时间要长久得多。

像Gmail或Chrome这样的产品注定要经历许多版本,并消耗数以百计的开发人员为之工作。如果一个SWE在某个产品的第三个版本研发时加入,这时这个产品已经有良好的文档、不错的可测试性、运行着稳定的自动化测试、清晰的代码提交流程,这些现象都在说明这个产品早期已有出色的SET在为之工作。

在整个项目生命周期里,功能的实现、版本的发布、补丁的创建、为改进而做的重构在不断地发生,你很难说清楚什么时候项目结束或一个项目是否真的已经结束。但所有软件项目都有明确的开始时间。在早期阶段,我们常去改变我们的目标。我们做计划,并尝试把东西做出来。我们尝试去文档化我们将要去做的事情。我们尝试去保证我们早期做的决定长期看来也是正确的。

我们在编码之前做计划、试验、文档,这部分工作量取决于我们对未来产品的信心。我们不想在项目初期做少量的计划,而到项目后期却发现这个计划是值得花费更多精力去做的。同样,我们也不希望在早期计划上投入数周时间,而之后却发现这个世界已经改变了,甚至与之前我们想象的世界完全不同了。某种程度上来说,我们早期在文档结构和过程中的处理方式也是明智的。总而言之,做多少和怎样做比较合适,由创建项目的工程师来做最终决定。

Google产品团队最初是由一个技术负责人(tech lead)和一个或更多的项目发起人组成。在Google,技术负责人这个非正式的岗位一般由工程师担任,负责设定技术方向、开展合作、充当与其他团队沟通的项目接口人。他知道关于项目的任何问题,或者能够指出谁知道这些问题的细节。技术负责人通常是一名SWE,或者由一名具备SWE能力的工程师来担任。

项目的技术负责人和发起人要做的第一件事就是设计文档(后文会做介绍)。随着文档的不断完善,就需要不同专业类型的工程师角色投入到项目中去。许多技术负责人期望SET在早期就能参与项目,即便那时SET资源还相对稀缺。

所有Google项目都有设计文档。这是一个动态的文档,随着项目的演化也在不断地保持更新。最早期的项目设计文档,主要包括项目的目标、背景、团队成员、系统设计。在初期阶段,团队成员一起协同完成设计文档的不同部分。对于一些规模足够大的项目来说,需要针对主要子系统也创建相应的设计文档,并在项目设计文档中增加子系统设计文档的链接。在初期版本完成后,里面会囊括所有将来需要完成的工作清单,这也可以作为项目前进的路标。从这一点上讲,设计文档必须要经过相关技术负责人的审核。在项目设计文档得到足够的评审与反馈之后,初期版本的设计文档就接近尾声了,接下来项目就正式进入实施阶段。

作为SET,比较幸运的是在初期阶段就加入了项目,会有一些重要且有影响力的工作急需完成。如果能够合理地谋划策略,我们在加速项目进度的同时,也可以做到简化项目相关人员的工作。实际上,作为工程师,SET在团队中有一个巨大的优势,就是拥有产品方面最广阔的视野。一个好的SET会把非常专业的广阔视野转化成影响力,在开发人员所编写的代码上产生深远的影响力。通常来说,代码复用和模块交互方面的设计会由SET来做,而不是SWE。后面会着重介绍SET在项目的初期阶段是如何发挥作用的。

注意


在设计阶段,SET在推进项目的同时也可以简化相关项目成员的工作。

如果有另外一双眼睛来帮助审核你的工作,这是无疑会很有帮助且令人期待。SWE就渴望得到来自SET的这种帮助与反馈。在SWE完成设计文档的各个部分之后,需要发送给更大范围人去做正式审核,在这之前他们希望得到SET的帮助。一个优秀的SET对这样的文档审核也会比较期待,乐意去投入他的时间,在SET审阅过程中,会针对质量和可靠性方面增加一些必要的内容。下面是我们为什么这么做的几个原因。

审阅设计文档的时候应该有一定的目的性,而不是像读报纸那样随便看两眼就算了。优秀的SET在审阅过程中始终保持强烈的目的性。下面是一些我们推荐的一些要点。

注意


审阅设计文档的时候要,具备一定的目的性,需要完成特定的目标,而不是像读报纸那样随意看两眼。

在SET与相应的SWE一起沟通文档的审阅结果时,关于测试的工作量以及各个角色之间如何共同参与测试,会有一个比较正式的讨论。这是一个绝佳的时机,可以了解到开发在单元测试方面的目标,以及如果想打造一款经过良好测试的产品,团队成员需要遵守哪些最佳实践。当这种讨论以互帮互助的形式开始出现时,我们的工作就开始逐步进入正轨了。

在Google,由于接口协议与编写代码相关,所以对于开发人员来说,文档化这部分是比较轻松的事情。Google protocol buffer语言(注:Google protocol buffers 是开源的,参见http://code.google.com/apis/protocolbuffers)与编码语言和平台无关,对结构化数据而言具有可扩展性,就像XML一样,但更小、更快、更简单。开发人员使用protocol buffer的描述语言来定义数据结构,然后使用自动生成的源代码,从各种数据流中来读或写这些结构化的数据,使用任何编程语言(Java, C++或python)皆可。对于新项目而言,protocol buffer源码通常是第一份源代码。在系统实现之后,如果设计文档中仍然使用protocol buffers来描述系统是如何工作的,这比较罕见。

SET会对protocol buffer代码做比较系统全面的审查,因为protocol buffer定义的接口与协议的代码实现是要由SET来完成的。没错,SET是第一个实现所有接口和协议的人。在系统真正搭建起来之前,集成测试的运行依赖这些接口实现。为了能够尽早地开始做集成测试,SET针对各个模块的依赖提供了mock或fake的实现。虽然功能模块代码还没有实现,集成测试的代码就已经可以开始编写了。在这个时候,如果集成测试代码可以运行起来,那将会更有价值。另外,在任何阶段,集成测试总是依赖mock和fake。因为有了它们,一些依赖服务的期望错误场景和条件异常,会比较容易产生。

注意


为了能够尽早可以运行集成测试,针对依赖服务,SET提供了mock与fake。

SET时间有限且需要做的事情太多,尽早地提供一个可实施的自动化测试计划是一个很好的解决方法。试图在一个测试套件中自动化所有端到端的测试用例,这是一个常见的错误。没有SWE会被这样一个无所不包的设计所吸引并感兴趣,SET也就得不到SWE的什么帮助。如果SET希望能从SWE那里得到帮忙,他的自动化计划就必须合情合理且有影响力。自动化上投入的越多,维护的成本也就越大。在系统升级变化时,自动化也会更加不稳定。规模更小且目的性更强的自动化计划,并存在可以提供帮助的测试框架,这些会吸引SWE一起参与测试。

在端到端的自动化测试上过度投入,常常会把你与产品的特定功能设计绑定在一起,这部分测试在整个产品稳定之前都不会特别有用。在产品完成之后,这个时候如果去修改设计就已经太晚了。所以,这个时刻从测试中得到的任何反馈也将变得毫无意义。SET的时间,本应投入在提高质量方面,却白白地花费在维护这些不稳定的端到端测试套件上。

注意


在端到端自动化测试上过度投入,常常会把你与产品的特定功能设计绑定在一起。

在Google,SET遵循了下面的方法。

我们首先把容易出错的接口做隔离,并针对它们创建mock和fake(在之前的章节中做过介绍),这样我们可以控制这些接口之间的交互,确保良好的测试覆盖率。

接下来构建一个轻量级的自动化框架,控制mock系统的创建和执行。这样的话,写代码的SWE可以使用这些mock接口来做一个私有构建。在他们把修改的代码提交到代码服务器之前运行相应的自动化测试,可以确保只有经过良好测试的代码才能被提交到代码库中。这是自动化测试擅长的地方,保证生态系统远离糟糕代码,并确保代码库永远处于一个时刻干净的状态。

SET除了在这个计划中涵盖自动化(mock、fake和框架)之外,还要包括如何公开产品质量方面的信息给所有关心的人。在Google,SET使用报表和仪表盘(译注:dashboard)来展示收集到的测试结果以及测试进度。通过将整个过程简化和信息公开透明化,获取高质量代码的概率会大大增加。

在产品开发过程中,SWE和SET紧密地工作在一起。SWE编写产品代码并测试这些代码。SET编写测试框架,为SWE编写测试代码方面提供帮助。另外,SET也做一些维护工作。质量责任由SWE和SET共同承担。

SET的第一要务就是可测试性。SET在扮演一个质量顾问的角色,提供程序结构和代码风格方面的建议给开发人员,这样开发人员可以更好地做单元测试。同时提供测试框架方面的建议,使得开发人员能够在这些框架的基础上自己写测试。后面我们再讨论框架,在这里让我们首先说一下Google的代码流程。

作为开发人员,一个基本的要求就是有能力做代码审查。代码审查需要工具和文化方面的支持,这个文化习俗来源于开源社区中“提交者”的概念,只有被证明是值得信赖的开发者之后,才具有往代码库中提交代码的资格。

注意


为了使SET也成为源码的拥有者之一,Google把代码审查作为开发流程的中心。相比较编写代码而言,代码审查更值得炫耀。

在Google,每个人都是代码提交者。但是,我们使用了另外一个词“可读性”来区分有已被证明有资格的提交者和新开发人员。下面介绍整个流程如何工作的。

代码以一个被称为“变更列表”(译注:change list,下文简写CL)的单元被编写和封装起来。CL在编码结束之后会提交审查,其中使用一个Google内部工具Mondrian(以一个荷兰抽象派画家为名)。Mondrian会把需要审查的代码发送给具有审阅资格的SWE或SET,并最终通过代码审查(译注:在Google App Engine上运行着一个开源版本的Mondrian,参见http://code.google.com/p/rietveld/)。

CL可以是一段新代码,也可以是对已有代码的修改,或是缺陷修复等。CL代码的大小从几行到几百行不等,一般审查者都会要求把数量较大的CL分解成数量较小的几个CL。新加入Google的SWE和SET都需要通过持续提交优秀的CL,来获取一个“可读性”方面的代码审查资格。可读性与编程语言有关,Google内部主要的编程语言C++、Java、Python和JavaScript都有不同的可读性要求。有经验和值得信赖的开发人员,会得到“可读性”的资格,大家同心协力确保整个代码库看起来像是由一个人编写的一样(注:Google的C++代码风格指南是对外公开的,参见http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml)。

在CL提交审查之前,会经过一系列的自动化检查。这种自动化静态检查所使用的规则包含一些简单的确认,例如是否遵循Google的代码风格指南、提交CL相关的测试用例是否执行通过(原则上所有的测试必须全部通过)等。CL里面一般总是包含针对这个CL的测试代码,测试代码总是和功能代码在一起。在检查完成之后,Mondrian会给相应的CL审阅者发送一封包含这个CL链接的通知邮件。随后审阅者会进行代码审查,并把修改建议发回给SWE去处理。这个过程会反复进行,直到提交者和审阅者都满意为止。

提交队列(译注:submit queue)的主要功能是保持“绿色”的构建,这意味着所有测试必须全部通过。这是构建系统和版本控制系统之间的最后一道防线。通过在干净环境中编译代码并运行测试,提交队列系统可以捕获在开发机器上无法发现的环境错误,但这会导致构建失败,甚至是导致版本控制系统中的代码处于不可编译的状态。

规模较大的团队可以利用提交队列在同一个代码分支上进行开发。如果没有提交队列,通常在代码集成或每轮测试时都会把代码冻结,使用提交队列就可以避免这个问题。在这种模式下,提交队列可以使得规模较大团队就像小团队一样,高效且独立。由于这样增加了开发提交代码的频率,势必给SET的工作带来了较大难度,这可能是唯一的弊端。

提交队列和持续集成构建由来


by Jeff Carollo

在Google规模还很小的初期,有一个约定的习俗就是在代码提交之前需要运行所有已经编写好的单元测试,用以验证这次代码变更的质量是否满足要求。测试运行失败的情况常常会发生,大家不得不花时间去找到问题的根源并加以修复。

公司在不断变大,为了节省资源,高质量的公共基础库被工程师们编写实现、维护和共用。且随着时间的变化,这些核心公共代码在数量上、规模上和复杂性上都有显著的增长。在这个时候,仅仅依靠单元测试就不够了,在一些与外部公共库或框架有交互的地方还需要依赖集成测试的验证。此时Google也发现许多测试运行失败的原因都是由于其外部依赖所导致。但在没有代码提交之前,这些测试不会被运行,即使它们已经失败数天之久也无人知晓。

这个时候“单元测试展板(Unit Test Dashboard)”出现了。这个系统把所有公司代码库的一级目录都作为一个“项目”,当然也允许自己增加自定义的“项目”,只要提供一系列构建和测试维护人员信息即可。这个系统会每日运行所有项目的测试。在展板上展示一个报表,记录着每个项目的测试通过与失败比率。每日运行失败的项目维护者也会收到一封相应的通知邮件,虽然测试运行失败通常不会持续太长的时间,但依然还会有失败的情况发生。

有些团队希望能够尽早知道哪些代码变更可能引起构建失败。每24小时才运行一次所有测试已经不能满足要求。个别团队就开始去编写持续构建脚本,在专用机器上持续不断地构建并运行相应的单元测试与集成测试。后来发现这个系统具有一定的通用性,也可以用来支持其他团队,Chris Lopez和Jay Corbett就一起编写了“Chris/Jay持续构建”工具,其他团队通过注册一台机器、填写一个配置文件和运行一个脚本,就能够运行自己的持续集成了。这很快变成了一个标准做法,后来几乎所有的Google项目都在使用Chris/Jay持续构建工具。在测试运行失败之后,会给最近一次提交代码的开发人员发送一封通知邮件,因为他们极有可能是导致测试失败的元凶。另外,Chris/Jay持续构建工具找出了“黄金变更列表”,这些代码变更在版本控制系统上得到确认,所有相关的测试和构建都已经成功通过。这样开发可以得到干净的代码版本而不受到最近提交代码的影响,最近提交的代码可能会导致构建失败(对于挑选用于发布的版本会非常有帮助)。

还有部分团队希望能够更早地捕获引起构建失败的代码变更。随着项目规模和复杂度的上升,一旦发生构建失败就已经有些晚了,就需要花费很大代价去修复。出于保护持续构建系统的目的,提交队列就出现了。在早期实现版本中,所有等待提交的CL必须逐个排队,等待测试,如果测试通过则证明这个CL是没有问题的,可以提交进代码库(因此也需要排队)。当有大量长时间运行的测试需要执行时,CL在发送给提交队列和CL真正被提交到源码库之间可能需要消耗数小时,这确实也很常见。在后来的实现中,允许所有等待的CL在互相隔离的前提下,并发地构建并运行测试。这样的改进可能会引起一些竞争条件的出现,但实际上很少发生,他们最终也都会被持续构建系统所捕获。快速地提交代码,省下的时间远远大于解决偶尔需要修复持续构建错误的时间。多数Google大型项目都在使用提交队列,项目成员会轮流做“构建警察”,构建警察的职责是快速响应处理任何在提交队列和持续构建系统中遇到的问题。

整套系统(单元测试展板、Chris/Jay持续构建工具和提交队列)在Google存活了相当长的时间(数以年计)。它们只需很少的搭建时间成本和不同程度的维护工作,但却给团队提供了极大的帮助。可以这样讲,它已经成为一个实用可行的公用基础工具,为所有团队在系统集成方面提供帮助。测试自动化,简写TAP(译注:Test Automation Program)就是这样做的。TAP几乎应用于所有的Google项目,但Chromium和Android除外(它们是开源项目,使用了不同代码库和构建环境)。

虽然所有的团队使用相同的一套工具和基础框架有一定的益处,但这些益处也不能被过分夸大。有些简单的小工具也可以解决现实问题。工程师使用一个简单的命令在云端提交CL、并发构建、运行所有可能涉及的测试代码,并将运行结果可视化地展示在一个永久的网站上。在命令运行终端也会显示“成功”、“失败”,以及指向任务详情的超链接。如果开发选择使用这样的方式,他的测试结果(包括覆盖率信息)就会被存储在云端,并通过Google内部代码审查工具对所有的代码审查者可见。

现在让我们把所有与SET相关的东西拼装在一起,看一个完整的实例。需要注意的是,这部分将涉及部分技术内容,且会深入到某些底层细节里面。如果你只对SET概要介绍感兴趣,那么你可以跳过这一部分。

假设有一个简单的网络应用,它的功能是允许用户向Google提交URL,并把这个URL增加到Google的索引文件之中。HTML的网页表单页面上接收两个字段:url和相应的注释,然后向Google的服务器发送类似以下的一个HTTP GET请求。

GET /addurl?url=http://www.foo.com&comment=Foo+comment HTTP/1.1

在这个例子中,这个web应用的服务器端分成至少两部分:前端服务AddUrlFrontend(它接收原始的HTTP请求,并做解析和验证工作)和后端服务AddUrlService。这个后端服务接受来自于前端服务AddUrlFrontend的请求,检查数据是否有错,并与后端数据存储持久层(例如Google的Bigtable(译注:http://labs.google.com/papers/bigtable.html)或GFS Goolge文件系统(译注:http://labs.google. com/papers/gfs.html)进行交互。

SWE针对这个服务,要做的第一件事就是为这个项目创建一个目录。

$ mkdir depot/addurl/

他们使用Google Protocol Buffer描述性语言(注:http://code.google.com/apis/protocol buffers/ docs/overview.html)定义AddUrlService的协议。

File: depot/addurl/addurl.proto
message AddUrlRequest {
required string url = 1;  // The URL entered by the user.
optional string comment = 2; // Comments made by the user.
}
message AddUrlReply {
// Error code, if an error occurred.
optional int32 error_code = 1;
// Error message, if an error occurred.
optional string error_details = 2;
}
service AddUrlService {
// Accepts a URL for submission to the index.
rpc AddUrl(AddUrlRequest) returns (AddUrlReply) {
option deadline = 10.0;
}
}

上面的“addurl.proto”文件定义了三个重要部分:AddUrlRequest的消息格式AddUrlReply的消息格式、AddUrlService远程方法调用服务(RPC)。

通过查看AddUrlRequest消息的定义,我们可以知道调用者必须提供一个url字段,而另外一个comment字段是可选的。

类似地,通过检查AddUrlReply消息的定义,我们可以知道error_code 和 error_details两个服务器提供的响应字段都是可选的。我们可以安全地假设:当一个URL被成功接收以后这些字段一般情况下会返回为空,这样也可以最小化中间的数据传输量。这是Google的惯例,让常见的场景快速运行。

通过查看AddUrlService服务的定义可以知道单一服务方法——AddUrl,接受一个AddUrlRequest并返回一个AddUrlReply。默认情况下,如果client在调用AddUrl 之后10秒还没有收到任何回应就会超时。AddUrlService在实现上会与后端持久数据存储层再做交互,但client并不需要关心这一部分细节,所以在“addurl.proto”文件中没有这部分接口的定义详情。

在消息字段中出现的“=1”并不是指这个字段的值。这种使用方法是为了允许协议将来升级使用。例如,以后某人可能想增加一个额外的uri字段到AddUrlRequest消息中。为了实现这个,他们可以做如下变更。

message AddUrlRequest {
required string url = 1;  // The URL entered by the user. 
optional string comment = 2; // Comments made by the user. 
optional string uri = 3;  // The URI entered by the user.
}

但这样做会有点傻。一些人更希望直接把url字段修改为uri。如果使用相同的数值,老版本和新版本之间就会保持兼容性。

message AddUrlRequest {
required string uri = 1;  // The URI entered by the user.
optional string comment = 2; // Comments made by the user.
}

在完成addurl.proto以后,开发人员可以为proto_library创建构建规则,根据addurl.proto中定义的字段自动产生C++源文件并编译成一个C++静态库(增加额外的选项,也可以绑定到其他语言,如Java或Ptyhon)。

File: depot/addurl/BUILD
proto_library(name=”addurl”,
srcs=[“addurl.proto”])

开发人员使用构建系统,并修复在构建过程中可能出现的addurl.proto问题或构建定义文件中的问题。构建系统会调用Protocol Buffer编译器,产生源码文件addurl.pb.h和addurl.pb.cc,同时会产生一个可以被链接的静态库adurl。

现在可以新建文件addurl_frontend.h,并在其中定义AddUrlFrontend类。代码大体如下。

File: depot/addurl/addurl_frontend.h
#ifndef ADDURL_ADDURL_FRONTEND_H_
#define ADDURL_ADDURL_FRONTEND_H_
// Forward-declaration of dependencies.
class AddUrlService;
class HTTPRequest;
class HTTPReply;
// Frontend for the AddUrl system.
// Accepts HTTP requests from web clients,
// and forwards well-formed requests to the backend.
class AddUrlFrontend {
public:
// Constructor which enables injection of an
// AddUrlService dependency.
explicit AddUrlFrontend(AddUrlService* add_url_service);
~AddUrlFrontend();
// Method invoked by our HTTP server when a request arrives
// for the /addurl resource.
void HandleAddUrlFrontendRequest(const HTTPRequest* http_request,
HTTPReply* http_reply);
private:
AddUrlService* add_url_service_;
// Declare copy constructor and operator= private to prohibit
// unintentional copying of instances of this class.
AddUrlFrontend(const AddUrlFrontend&);
AddUrlFrontend& operator=(const AddUrlFrontend& rhs);
};
#endif // ADDURL_ADDURL_FRONTEND_H_

继续AddUrlFrontend类的实现部分,开发人员创建“addurl_frontend.cc”文件。这是AddUrlFrontend类的主要逻辑实现部分,为了简短说明,省略了部分文件内容。

File: depot/addurl/addurl_frontend.cc
#include “addurl/addurl_frontend.h”
#include “addurl/addurl.pb.h”
#include “path/to/httpqueryparams.h”
// Functions used by HandleAddUrlFrontendRequest() below, but
// whose definitions are omitted for brevity.
void ExtractHttpQueryParams(const HTTPRequest* http_request,
HTTPQueryParams* query_params);
void WriteHttp200Reply(HTTPReply* reply);
void WriteHttpReplyWithErrorDetails(
HTTPReply* http_reply, const AddUrlReply& add_url_reply);
// AddUrlFrontend constructor that injects the AddUrlService
// dependency.
AddUrlFrontend::AddUrlFrontend(AddUrlService* add_url_service)
: add_url_service_(add_url_service) {
}
// AddUrlFrontend destructor - there’s nothing to do here.
AddUrlFrontend::~AddUrlFrontend() {
}
// HandleAddUrlFrontendRequest:
// Handles requests to /addurl by parsing the request,
// dispatching a backend request to an AddUrlService backend,
// and transforming the backend reply into an appropriate
// HTTP reply.
//
// Args:
// http_request - The raw HTTP request received by the server.
// http_reply - The raw HTTP reply to send in response.
void AddUrlFrontend::HandleAddUrlFrontendRequest(
const HTTPRequest* http_request, HTTPReply* http_reply) {
// Extract the query parameters from the raw HTTP request.
HTTPQueryParams query_params;
ExtractHttpQueryParams(http_request, &query_params);
// Get the ‘url’ and ‘comment’ query components.
// Default each to an empty string if they were not present
// in http_request.
string url = query_params.GetQueryComponentDefault(“url”, “”);
string comment = query_params.GetQueryComponentDefault(“comment”, “”);
// Prepare the request to the AddUrlService backend.
AddUrlRequest add_url_request;
AddUrlReply add_url_reply;
add_url_request.set_url(url);
if (!comment.empty()) {
add_url_request.set_comment(comment);
}
// Issue the request to the AddUrlService backend.
RPC rpc;
add_url_service_->AddUrl(
&rpc, &add_url_request, &add_url_reply);
// Block until the reply is received from the
// AddUrlService backend.
rpc.Wait();
// Handle errors, if any:
if (add_url_reply.has_error_code()) {
WriteHttpReplyWithErrorDetails(http_reply, add_url_reply);
} else {
// No errors. Send HTTP 200 OK response to client.
WriteHttp200Reply(http_reply);
}
}

HandleAddUrlFrontendRequest是一个经常被调用的成员函数。许多Web处理函数大多如此。开发人员可以通过提取一些功能到helper函数中,用来简化这个函数。但是,类似这样的重构在构建稳定之前和单元测试编写完成并可以顺利通过运行之前是很少去做的。

在这个时候,开发人员修改已有addurl项目的构建文件,为addurl_frontend库增加入口。在构建的时候会产生一个C++静态库AddUrlFrontend。

File: /depot/addurl/BUILD
# From before:
proto_library(name=”addurl”,
srcs=[“addurl.proto”])
# New:
cc_library(name=”addurl_frontend”,
srcs=[“addurl_frontend.cc”],
deps=[
“path/to/httpqueryparams”,
“other_http_server_stuff”,
“:addurl”, # Link against the addurl library above.
])

再次运行构建工具,同时修复在编译链接addurl_frontend.h和addurl_frontend.cc过程中可能出现的错误,直到所有编译和链接不出现警告和错误为止。此时,可以去编写AddUrlFrontend的单元测试代码了。单元测试在另外一个新文件“addurl_frontend_test.cc”中。在测试中定义一个虚假(fake)的后端服务,使用AddUrlFrontend的构造函数可以把这个虚假的后端服务在运行时刻调用。这样的话,单元测试在运行时,无需修改AddUrlFrontend代码本身,代码逻辑能够进入AddUrlFrontend内部期望分支中或错误流程里(译注:阅读以下代码需要提前了解Google’s framework for writing C++ test,即googletest,参见https://code.google.com/p/googletest/)。

File: depot/addurl/addurl_frontend_test.cc
#include “addurl/addurl.pb.h”
#include “addurl/addurl_frontend.h”
// See http://code.google.com/p/googletest/
#include “path/to/googletest.h”
// Defines a fake AddUrlService, which will be injected by
// the AddUrlFrontendTest test fixture into AddUrlFrontend
// instances under test.
class FakeAddUrlService : public AddUrlService {
public:
FakeAddUrlService()
: has_request_expectations_(false),
error_code_(0) {
}
// Allows tests to set expectations on requests.
void set_expected_url(const string& url) {
expected_url_ = url;
has_request_expectations_ = true;
}
void set_expected_comment(const string& comment) {
expected_comment_ = comment;
has_request_expectations_ = true;
}
// Allows for injection of errors by tests.
void set_error_code(int error_code) {
error_code_ = error_code;
}
void set_error_details(const string& error_details) {
error_details_ = error_details;
}
// Overrides of the AddUrlService::AddUrl method generated from
// service definition in addurl.proto by the Protocol Buffer
// compiler.
virtual void AddUrl(RPC* rpc,
const AddUrlRequest* request,
AddUrlReply* reply) {
// Enforce expectations on request (if present).
if (has_request_expectations_) {
EXPECT_EQ(expected_url_, request->url());
EXPECT_EQ(expected_comment_, request->comment());
}
// Inject errors specified in the set_* methods above if present.
if (error_code_ != 0 || !error_details_.empty()) {
reply->set_error_code(error_code_);
reply->set_error_details(error_details_);
}
}
private:
// Expected request information.
// Clients set using set_expected_* methods.
string expected_url_;
string expected_comment_;
bool has_request_expectations_;
// Injected error information.
// Clients set using set_* methods above.
int error_code_;
string error_details_;
};
// The test fixture for AddUrlFrontend. It is code shared by the
// TEST_F test definitions below. For every test using this
// fixture, the fixture will create a FakeAddUrlService, an
// AddUrlFrontend, and inject the FakeAddUrlService into that
// AddUrlFrontend. Tests will have access to both of these
// objects at runtime.
class AddurlFrontendTest : public ::testing::Test {
protected:
// Runs before every test method is executed.
virtual void SetUp() {
// Create a FakeAddUrlService for injection.
fake_add_url_service_.reset(new FakeAddUrlService);
// Create an AddUrlFrontend and inject our FakeAddUrlService
// into it.
add_url_frontend_.reset(
new AddUrlFrontend(fake_add_url_service_.get()));
}
scoped_ptr<FakeAddUrlService> fake_add_url_service_;
scoped_ptr<AddUrlFrontend> add_url_frontend_;
};
// Test that AddurlFrontendTest::SetUp works.
TEST_F(AddurlFrontendTest, FixtureTest) {
// AddurlFrontendTest::SetUp was invoked by this point.
}
// Test that AddUrlFrontend parses URLs correctly from its
// query parameters.
TEST_F(AddurlFrontendTest, ParsesUrlCorrectly) {
HTTPRequest http_request;
HTTPReply http_reply;
// Configure the request to go to the /addurl resource and
// to contain a ‘url’ query parameter.
http_request.set_text(
“GET /addurl?url=http://www.foo.com HTTP/1.1\r\n\r\n”);
// Tell the FakeAddUrlService to expect to receive a URL
// of ‘http://www.foo.com’.
fake_add_url_service_->set_expected_url(“http://www.foo.com”);
// Send the request to AddUrlFrontend, which should dispatch
// a request to the FakeAddUrlService.
add_url_frontend_->HandleAddUrlFrontendRequest(
&http_request, &http_reply);
// Validate the response.
EXPECT_STREQ(“200 OK”, http_reply.text());
}
// Test that AddUrlFrontend parses comments correctly from its
// query parameters.
TEST_F(AddurlFrontendTest, ParsesCommentCorrectly) {
HTTPRequest http_request;
HTTPReply http_reply;
// Configure the request to go to the /addurl resource and
// to contain a ‘url’ query parameter and to also contain
// a ‘comment’ query parameter that contains the
// url-encoded query string ‘Test comment’.
http_request.set_text(“GET /addurl?url=http://www.foo.com”
“&comment=Test+comment HTTP/1.1\r\n\r\n”);
// Tell the FakeAddUrlService to expect to receive a URL
// of ‘http://www.foo.com’ again.
fake_add_url_service_->set_expected_url(“http://www.foo.com”);
// Tell the FakeAddUrlService to also expect to receive a
// comment of ‘Test comment’ this time.
fake_add_url_service_->set_expected_comment(“Test comment”);
// Send the request to AddUrlFrontend, which should dispatch
// a request to the FakeAddUrlService.
add_url_frontend_->HandleAddUrlFrontendRequest(
&http_request, &http_reply);
// Validate that the response received is a ‘200 OK’ response.
EXPECT_STREQ(“200 OK”, http_reply.text());
}
// Test that AddUrlFrontend sends proper error information when
// the AddUrlService encounters a client error.
TEST_F(AddurlFrontendTest, HandlesBackendClientErrors) {
HTTPRequest http_request;
HTTPReply http_reply;
// Configure the request to go to the /addurl resource.
http_request.set_text(“GET /addurl HTTP/1.1\r\n\r\n”);
// Configure the FakeAddUrlService to inject a client error with
// error_code 400 and error_details of ‘Client Error’.
fake_add_url_service_->set_error_code(400);
fake_add_url_service_->set_error_details(“Client Error”);
// Send the request to AddUrlFrontend, which should dispatch
// a request to the FakeAddUrlService.
add_url_frontend_->HandleAddUrlFrontendRequest(
&http_request, &http_reply);
// Validate that the response contained a 400 client error.
EXPECT_STREQ(“400\r\nError Details: Client Error”,
http_reply.text());
}

通常情况下开发人员会写更多的测试用例,但这里只是通过上面的示例来演示通用模式,即如何定义Fake对象、如何注入这个Faoke对象、在测试中如何调用这个Fake对象来引入期待的错误并验证程序逻辑,上面的例子就已经足够了。有一个需要注意的地方,那就是此例中我们缺少了模拟AddUrlFrontend和FakeAddUrlService之间的网络超时。这说明我们的开发人员忘记了去处理在超时条件下的检查验证逻辑。

有经验的敏捷测试高手会指出所有测试使用FakeAddUrlService有点单一,也可以使用mock来替换。这个高手的建议是对的。我们使用一个fake只是为了纯粹的演示目的。

现在我们的开发人员想去运行这些测试,他必须先要修改构建定义文件,把新测试代码addurl_frontend_test添加到构建规则中去。

File: depot/addurl/BUILD
# From before:
proto_library(name=”addurl”,
srcs=[“addurl.proto”])
# Also from before:
cc_library(name=”addurl_frontend”,
srcs=[“addurl_frontend.cc”],
deps=[
“path/to/httpqueryparams”,
“other_http_server_stuff”,
“:addurl”, # Depends on the proto_library above.
        ])
# New:
cc_test(name=”addurl_frontend_test”,
size=”small”, # See section on Test Sizes.
srcs=[“addurl_frontend_test.cc”],
deps=[
“:addurl_frontend”, # Depends on library above.
        “path/to/googletest_main”])

开发人员再一次使用构建工具编译运行addurl_frontend_test程序,修复构建中可能出现的编译链接错误,这次也会修复测试程序的错误,包括测试套件、fake和AddUrlFrontend本身的错误。上述过程在FixtureTest定义之后就会迅速展开,后面的用例添加之后也会重复上面的过程。当测试都通过之后,开发人员会创建一个包含所有这些文件的代码变更CL,修复代码检查工具提示的小问题,再把这个CL发出去做代码审查,然后就去做另外的工作(很可能是实现一个真实的后端AddUrlService服务),并等待代码审查的结果反馈。

$ create_cl BUILD \
       addurl.proto \
       addurl_frontend.h \
       addurl_frontend.cc \
       addurl_frontend_test.cc
$ mail_cl -m reviewer@google.com

当代码审查反馈结果出来之后,开发人员会做适当的修改(或与审查者一起协商方案),很可能需要再次审查,然后将这个CL提交到代码库之中。从此刻起,不管什么时候如果有人修改了这里面的任何文件,Google的自动化测试系统就会感知,并运行addurl_frontend_test这个测试来验证是否新的修改导致已有测试用例运行失败。另外,如果有人尝试去修改addurl_frontend.cc,addurl_frontend_test就像一个安全保护网一样自动运行并进行保护。

然而,测试自动化不仅仅是自动化测试程序的编写。如果想让这些测试程序有价值,必须要去考虑如何编译测试程序、执行、分析、存储和报告所有测试运行结果,这些都是自动化测试会遇到的挑战。在软件开发过程中测试自动化想真正发挥作用,还要凭借其自身的努力。

除了要关注如何正确编写自动化程序之外,还要把工程师的注意力转移到在实际项目中如何更大发挥自动化测试的价值上。只有能加速开发过程的自动化测试才有意义,测试不应拖慢开发的速度。因此,自动化必须与开发过程真正集成在一起,并使之成为开发过程的一部分,而不是孤立它。功能代码从来都不像真空一样孤立存在,测试代码也是如此。

因此,一个可以做代码编译、测试执行、结果分析、数据存储、报表展示的通用的测试框架逐渐形成了。事情正在向我们期待的方向上发展:Google工程师专注于测试程序的编写、运行的细节留给通用基础执行框架。对于工程师来说,测试代码和功能代码一样,都是代码。

在SET新增一个测试程序之后,同时会针对这个测试创建一个构建说明文件。这个测试程序的构建文件包括测试名称、源码文件、依赖库及数据、还要指明其规模大小。每一个测试程序必须要标明它的规模是小型、中型、大型还是超大型。在编写完测试程序和构建文件之后,后面就交给Google构建工具和测试执行框架了。从提交时刻开始,一个命令就可以触发构建、运行自动化、展示运行结果了。

Google的测试执行框架对我们如何编写测试程序有一定的要求限制。这些要求是怎样的以及我们是如何应对处理的,在后面会做更多解释。

随着Google不断的成长和新员工不断的增加,一些令人疑惑的测试类型方面的专业术语持续不断地涌现出来:单元测试、代码级别测试、白盒测试、集成测试、系统测试、端到端测试等,从不同的粒度级别来表述测试的类型,如图2.1所示。在不久前,我们终于觉得忍无可忍,于是自己创建了一套测试命名规则。

▲图2.1 Google执行了许多不同类型的测试

1.小型测试

小型测试是为了验证一个代码单元的功能,一般与运行环境隔离,例如针对一个独立的类或一组相关函数的测试。小型测试的运行不需要外部依赖。在Google之外,小型测试通常就是单元测试。

小型测试是所有测试类型里范畴最小的,一般集中精力在函数级别的独立操作与调用上,如图2.2所示。这样限定了范畴的测试可以提供更加全面的底层代码覆盖率,而其他类型的测试无法做到这一点。

▲图2.2 小型测试的范畴,一般只涉及单一的函数

在小型测试里,外部服务(如文件系统、网络、数据库)必须通过模拟或虚假实现(mock & fake)。为了减少依赖,适当的时候也可模拟实现被测类所在模块的内部服务。

范畴隔离且没有外部依赖,这让小型测试可以在很短时间内就运行结束。因此,它们的执行频率也会更加频繁,并且可以很快就会发现问题。通常情况下,在开发人员修改了他们的功能代码之后就会立刻运行这些测试,当然他们还要维护这些测试代码。范畴隔离可以使构建与测试执行时间变短。

2.中型测试

中型测试是验证两个或多个模块应用之间的交互,如图2.3所示。和小型测试相比,中型测试有着更大的范畴且运行所需要的时间也更久。小型测试会尝试走遍单独函数的所有路径,而中型测试的主要目标是验证指定模块之间的交互。在Google之外,中型测试经常被称为“集成测试”。

中型测试运行的时间需要更久,需要测试执行工具在执行频率上加以控制,不能像小型测试那样频繁地运行。一般情况下是由SET来组织运行中型测试。

对于中型测试,鼓励使用模拟技术(mock)来解决外部服务的依赖问题,但这不是强制的,如出于性能考虑可以不使用模拟技术。轻量级的虚假实现(fake),如常驻内存的数据库,在不能使用mock的场景下可以用来提升性能。

▲图2.3 中型测试涉及多个模块并且依赖外部数据

3.对于大型测试

在Google之外通常被称为“系统测试”或“端到端测试”。大型测试在一个较高层次上运行,验证系统作为一个整体是如何工作的。这涉及应用系统的一个或所有子系统,从前端界面到后端数据储存,如图2.4所示。该测试也可能会依赖外部资源,如数据库、文件系统、网络服务等。

▲图2.4 大型测试或者超大型测试,包括在端到端执行过程中涉及的所有模块

注意


小型测试是为了验证一个代码单元的功能。中型测试验证两个或多个模块应用之间的交互。大型测试是为了验证整个系统作为一个整体是如何工 作的。

使用统一的运行方式来执行不同的自动化测试是有一定难度的。对于一个大型工程组织来说,如果想使用通用的测试执行平台,那么这个平台必须支持运行各种各样的测试任务。

使用Google测试执行平台运行的一些通用任务如下。

上面提及的所有任务,有可能同时并发提交到Google测试执行系统。一些测试可能极度消耗资源,使得公用测试机器处于不可用状态达数小时。另外一些测试可能只需几毫秒,而且可以和其他几百个任务同时在一台机器上并发运行。当每一个测试都被标记为小型、中型、大型的时候,调度运行这些测试任务就会变得相对简单一些,因为调度器已经知道每个任务需要运行的时间,这样可以优化任务队列,达到合理利用的目的。

Google测试执行系统利用了测试规模的定义,把运行较快的任务从较慢的任务中挑选出来。测试规模在测试运行时间上规定了一个最大值,如表2.1所示;同时测试规模在测试运行消耗资源上也做了要求,如表2.2所示。Google测试执行系统在发现任何测试超时,或是消耗的资源超过这个测试规模应该使用的资源时,会把这个测试任务取消掉并报告这个错误。这会迫使工程师提供合适的测试规模标签。精准的测试规模,可以使Google测试执行系统在调度时做出明智的决定。

表2.1 针对不同测试规模的测试执行时间的目标和限制

 

小型测试

中型测试

大型测试

超大型测试

时间目标(每个函数)

10毫秒以内

1秒以内

尽可能快

尽可能快

强制时间限制

1分钟之后强制结束

5分钟之后强制结束

15分钟之后强制结束

1小时之后强制结束

表2.2 针对不同测试规模的资源使用情况

资 源

大型测试

中型测试

小型测试

网络服务(建立一个链接)

仅本地

模拟

数据库

模拟

访问文件系统

模拟

访问用户界面系统

不鼓励

模拟

系统调用

不鼓励

多线程

不鼓励

睡眠状态

系统属性

每一种测试规模都带来了一些益处,如图2.5所示。每种测试规模的优点和缺点也都罗列在这里以供参考和比较。

▲图2.5 不同测试规模类型的限制

1.大型测试

大型测试的优点和缺点包括如下。

2.中型测试

中型测试的优点和缺点包括如下。

3.小型测试

小型测试的优点和缺点包括如下。

小型测试带来优秀的代码质量、良好的异常处理、优雅的错误报告;大中型测试带来整体产品质量和数据验证。单一的测试类型不能解决所有项目需求。正是由于这个原因,Google项目维护着一个不同测试类型之间的健康比例。对于一个项目,如果全部使用大型的端到端自动化测试是错误的,全部使用小型的单元测试同样也是错误的。

注意


小型测试带来优秀的代码质量、良好的异常处理、优雅的错误报告;大中型测试会带来整体产品质量和数据验证。

检验一个项目里小型测试、中型测试和大型测试之间的比率是否健康,一个好办法是使用代码覆盖率。测试代码覆盖率可以针对小型测试、中大型测试分别单独产生报告。覆盖率报告会针对不同的项目展示一个可被接受的覆盖率结果。如果中大型测试只有20%的代码覆盖率,而小型测试有近100%的覆盖率,则说明这个项目缺乏端到端的功能验证。如果结果数字反过来了,则说明这个项目很难去做升级扩展和维护,由于小型测试较少,就需要大量的时间消耗在底层代码调试查错上。Google工程师可以使用构建与运行测试时使用的工具,来产生并查看测试覆盖率结果,只需要在命令行中额外增加一个选项即可。覆盖率结果会存储在云端,任何工程师在公司内网络环境下都可以通过浏览器查看这些报告。

Google有许多不同类型的项目,这些项目对测试的需求也不同,小型测试、中型测试和大型测试之间的比例随着项目团队的不同而不同。这个比例并不是固定的,总体上有一个经验法则,即70/20/10原则:70%是小型测试,20%是中型测试,10%是大型测试。如果一个项目是面向用户的,拥有较高的集成度,或者用户接口比较复杂,他们就应该有更多的中型和大型测试;如果是基础平台或者面向数据的项目,例如索引或网络爬虫,则最好有大量的小型测试,中型测试和大型测试的数量要求会少很多。

另外有一个用来监视测试覆盖率的内部工具是Harvester。Harvester是一个可视化的工具,可以记录所有项目的CL历史,并以图形化的方式展示,例如测试代码和CL中新增代码的比率、代码变更的多少、按时间的变化频率、按照开发人员的变化次数,等等。这些图形的目的是展示随着时间的变化,测试的变化趋势是怎样的。

无论测试规模的大小是什么,由于Google的测试执行系统是一个公用环境,因此就要求测试本身满足下面几个条件。

这两个要求比较简单也很容易理解,但必须严格遵守。测试本身会尽可能地遵守要求,但被测系统却有可能违背原则;保存数据或修改环境配置信息。幸运的是,Google测试执行环境提供了许多特性可以确保这些要求比较容易就得到满足。

由于测试用例有独立运行的要求,在运行时刻,工程师通过设置一个标记就能以随机的顺序来执行它们。这样也可以找到那些对执行顺序有要求的用例。总之,“任意顺序”意味着可以并发执行用例。测试执行系统可以选择在同一个机器上同时执行两个用例,但如果每个用例都要求独占系统某些资源,其中一个用例就可能运行失败。例如以下几种情况。

这种类型的冲突,不仅会导致自己的用例运行失败,而且可能会导致测试执行系统中其他正在运行的用例也失败,即便另外的用例已经遵守了规则。测试执行系统可以找出这些测试用例,并通知给相应的用例负责人。另外,通过设置一个特殊标记,用例可以在指定的机器上以独立排他的方式运行。但排他的方式运行只是一个临时方案。更多的时候,测试或者被测系统必须重构,彻底解决在单一资源方面的依赖。下面的做法可以帮助解决一些问题。

Google全力维护其测试执行系统,甚至文档也非常详尽。这些文档存放在Google的“测试百科全书”中,这里有对其运行使用的资源所做的最终解释。“测试百科全书”有点像IEEE RFC(译注:IEEE定义的正式标准,RFC是Request for Comment的简写),明确使用“必须”或“应该”这样的字样,并在其中详细解释了角色、测试用例职责、测试执行者、集群系统、运行时刻的libc、文件系统等。

许多Google工程师感觉没有太多必要去阅读“测试百科全书”,他们从其他人身上了解这方面的知识,或者从不断的试验错误中得到教训,也在代码评审中收到改进反馈。他们不知道,公用测试执行环境能够服务于所有Google项目,其中背后的细节都已在文档之中。他们也不知道,在公用执行环境中的运行结果为什么与工作机器上的运行结果一致,背后的原因也都在文档里了。对于测试执行系统平台的使用用户来说,细节实现是透明的。所有的一切都能正常工作。

测试的速度与规模


by Pooja Gupta, Mark Ivey, and John Penix

在开发过程中,持续集成系统在保证软件正常工作方面发挥着重要作用。多数持续集成系统按照下面基本步骤工作。

(1)得到最新的代码。

(2)运行所有的测试。

(3)报告运行结果。

(4)重复以上(1)~(3)步。

在代码规模较小时,上述过程可以很容易地工作,代码变化不多,测试也可以很快就运行结束。随着代码库中的代码不断增加,这样一个系统的效率就会下降。每次全新地取出干净代码再运行耗时较大,多次变更被勉强地塞进一次测试运行之中。如果运行失败,对于团队来说,发现定位这个错误并回滚,将成为了一个漫长且易错的过程。

Google的软件开发过程在速度和规模上日新月异。Google代码库每分钟都会收到多于20次的变更申请,50%的文件每个月都会发生变化。每个产品的发布从“头”开始就依赖于自动化测试去验证产品功能。发布的频率根据产品团队不同,也从每天数次到几周一次不等。

拥有如此庞大且不停变化的代码库,为了保持构建始终保持“绿色”,就需要花费大量的时间做维护。一个持续集成系统,如果测试失败,应该可以提供具体哪次代码变更导致失败,而不是给出一堆可疑的变更列表,或消耗较长时间做二分查找从而定位具体哪次代码变更导致了问题的发生。为了精确定位哪次代码变更导致测试用例运行失败,我们可以针对每次代码变更运行所有的测试,但这样做的代价也是非常昂贵的。

为了解决这个问题,我们对持续集成系统做了优化,如图2.6所示。利用依赖分析技术寻找所有可能受影响的模块,针对一个代码变更只运行受影响模块的测试。这个系统在Google云计算平台上构建,使得许多构建可以并发执行,并在代码变更提交的时候立刻运行可能受影响模块的测试。

这里用一个示例来说明我们的系统是如何提供更快反馈的,与传统持续构建系统相比,我们的反馈内容也会更加精准。在这个示例中,我们使用了两个测试(gmail_client_tests, gmail_server_tests)和三个可能会影响这两个测试的代码变更(change #1, #2, #3)。gmail_server_tests运行失败由变更#2导致,而传统的持续集成系统只能告诉我们可能是变更#2或变更#3引起。通过使用并发构建,我们不必等构建测试运行全部结束就可以开始新的测试。依赖分析针对每一次代码变更会限制执行测试次数,所以此例中,测试执行的总数与之前是相同的。

▲图2.6 典型持续集成系统

持续集成系统使用构建系统中的构建依赖规则。在这个规则中描述了代码是如何编译、数据文件是怎样集成在一起成为应用程序的,以及测试如何运行等信息。这个构建规则中详细定义了构建所需的输入输出。持续集成系统在内存中维护了图2.7所示的一个构建依赖图,并随着代码的变更而时刻保持最新状态。如果有代码变更提交,可以很快就计算得知哪些依赖模块可能会受到影响(直接或间接),然后重新运行构建测试,获得最新执行状态。让我们再看一个例子。

▲图2.7 构建依赖示例

我们观察两个独立的代码变更,它们发生在依赖树的不同深度上,通过分析来决定哪些测试会受影响。这些受影响的测试就是需要运行的最小集合测试,它们用来保证GMAIL和BUZZ项目的构建保持“绿色”。

1.案例:在通用库上的代码变更

对于第一个场景,考虑common_collection_util部分的代码修改,如图2.8所示。

▲图2.8 文件common_collections_util.h中发生代码变更

当这个代码变更CL提交时,我们沿着依赖图向上找到所有依赖于它的测试。当这个查找结束时(实际上只需要一瞬间),我们发现所有的测试都需要运行。在运行之后,根据运行结果更新项目的构建状态,如图2.9所示。

▲图2.9 由于代码变更而被影响到的测试

2.案例:在一个依赖项目上的代码变更

对于第二个场景,我们来看如果在youtube_client的部分做一些代码变更,如图2.10所示。

▲图2.10 youtube_client中出现了代码变更

经过展开统一的分析之后,我们发现只有buzz_client_tests受到影响,只有buzz项目的状态需要更新,如图2.11所示。

在这个示例中,我们展示了如何优化每次代码变更后触发的测试执行次数。对于一个项目来说,并没有牺牲结果的准确度。每次运行较少的测试,可以让我们有机会针对每一次代码变更都运行其所有可能受影响的测试。对开发人员来说,排查导致构建失败的代码变更会更容易一些。

在持续集成系统中使用更加智能的分析工具与云计算平台,让整个运行过程更加迅速和稳定。当我们持续不断地在改进这个系统时,成千上万的Google项目已经在使用这套平台了。这样做不但有利于加快项目进度,而且进度对于用户也是可见的。

▲图2.11 buzz需要更新

Patrick Copeland在本书的序中强调了让开发人员参与测试的难度。招聘到技术能力强的测试人员只是刚刚开始的第一步,我们依然需要开发人员参与进来一起做测试。其中我们使用的一个关键方法就是被称为“测试认证”(译注:Test Certified)的计划。现在回过头来再看,这个计划对开发人员做测试这个根深蒂固文化的形成有着巨大的帮助。

测试认证最初以竞赛的方式进行。如果我们把测试认证做成一个富有声望的事情,这会让开发对测试重视起来吗?如果开发人员遵循一些特定的测试实践,并拿到期望的结果,我们能说他们通过了认证吗?然后再给他们授予一个象征性的徽章(见图2.12),使得他们拥有炫耀的资本吗?

▲图2.12 在项目wiki页面上的测试认证勋章

好吧,测试认证是:如果一个团队完成了一系列的测试任务,这个团队会得到一个通过“认证”的标识。所有团队最初的级别都是0。如果掌握了基本的优秀代码习惯,就达到级别1,然后继续通过水平考核,最终达到级别5,与外部的能力成熟度模型一样,例如CMM能力成熟度模型(译注:http://www.sei.cmu.edu/cmmi/start/faq/related-faq.cfm)。

测试认证级别摘要

级别1

  • 使用测试覆盖率工具。
  • 使用持续集成。
  • 测试分级为小型、中型、大型。
  • 明确标记哪些测试是非确定性的测试(译注:非确定性测试指测试结果不确定的用例)。
  • 创建冒烟测试集合。

级别2

  • 如果有测试运行结果为红色(译注:表示运行失败的用例)就不会做发布。
  • 在每次代码提交之前都要求通过冒烟测试。
  • 各种类型测试的整体增量覆盖率要大于50%。
  • 小型测试的增量覆盖率要大于10%。
  • 每一个功能特性至少有一个与之对应的集成测试用例。

级别3

  • 所有重要的代码变更都要经过测试。
  • 小型测试的增量覆盖率要大于50%。
  • 新增的重要功能都要经过集成测试的验证。

级别4

  • 在提交任何新代码之前都会自动运行冒烟测试。
  • 冒烟测试必须在30分钟内运行完毕。
  • 没有不确定性的测试。
  • 总体测试覆盖率应该不小于40%。
  • 小型测试的代码覆盖率应该不小于25%。
  • 所有重要的功能都应该被集成测试验证到。

级别5

  • 对每一个重要的缺陷修复都要增加一个测试用例与之对应。
  • 积极使用可用的代码分析工具。
  • 总体测试覆盖率不低于60%。
  • 小型测试的代码覆盖率应该不小于40%。

最初这个计划在一些测试意识较高的团队中缓慢试水,这些团队成员热衷于改进他们的测试实践。经过在这几个团队的成功试验之后,一个规模更大的、公司级别的认证竞赛开始推行起来了,然后在新加入的团队中再推行这个计划就变得容易的多。

这并不像一些人想象的那么难以被接受,开发团队也从中收益颇丰。

经过公司级别的推进,绝大多数团队都在不断向前进步,并意识到这个计划的重要性。一些在这个计划中表现不错的开发总监会得到工程生产力团队的优秀反馈,而嘲笑这个计划的团队也会置自身于危险之中。换句话说,在一个测试资源相对稀缺的公司里,哪个团队会舍得与工程生产力团队疏远呢?但并非哪里都是鲜花与掌声,让运行这个计划的负责人来给我们讲述完整的故事吧。

本访谈的作者和四名Google工程师坐在一起,他们曾为测试认证计划的开展起到了关键性的作用。Mark Striebeck是Gmail的开发经理;Neal Norwitz是关注开发速度工具的SWE;Tracy Bialik和Russ Rufer是非管理角色的SET,他俩是公司级别最高的SET,也都是资深级的工程师。

HGTS:测试认证计划的起源是什么?最初测试认证团队试图去解决什么样的问题?现在这个计划尝试去解决的问题相比还是同样的问题吗?

Tracy:我们企图去改变Google的开发文化,想把测试工作也变成每个功能开发人员的职责。大家共享许多在测试方面有积极意义的经验,并鼓励整个团队都去做测试。有些团队比较感兴趣,但不知道具体怎样去操作。另外的一些团队会把“提高测试”作为团队目标或绩效(译注:objectives and key results,简写OKRs,是个人、团队,甚至公司每个季度都要订制的目标。基本上这些事情都需要个人或团队完成),这通常并没有什么实际的可操作性,有点像把“减肥”作为新的年度目标一样。那样其实也没什么不好,至少有崇高的目标。但是,如果这就是你要说的一切,未来有朝一日发现并没有变成现实,你也千万不要感觉奇怪。

测试认证计划提供了小而清晰且可操作的步骤给团队去执行。级别1是做基本准备:建立测试运行的自动化机制、收集测试覆盖率、去除所有非确定性的测试、挑选冒烟测试集合(如果全部自动化测试运行比较耗时的话)。级别越高就会变得越难,也需要越成熟的测试度。级别2开始着重提高增量覆盖率。级别3重点是测试新增代码。级别4的重点是测试历史遗留代码,通常情况下需要针对可测试性做一些重构。级别5要求更好的整体覆盖率,针对每个缺陷都增加测试用例,并要求使用已有可用的静态与动态分析检查工具。

现在,所有Google的人都已经知道测试是功能开发人员的责任。虽然最早的问题已经得到了解决,但是我们依然需要为团队提供更高的测试成熟能力度而做一些事情。测试认证持续不断地在为这个目标服务。

HGTS:测试认证团队最初从SWE那里收集到的反馈是怎样的?

Neal:测试认证计划太难了。他们认为我们把目标设定的过高,结果导致许多团队还在初级层里挣扎。我们需要重新设定认证级别,设定为使他们只要在空闲时间里努力就可以达到的级别。当时Google的工具也有一些问题,而且我们当时要求的一些想法太过超前。对于参与的同事们来说的确难以去开展进行,因此我们不得不考虑提供一些容易达成的目标,使他们相信自己在不断地进步中。

Mark:是的,我们不得不把目标向下做了几轮调整。我们设置了一些更加实际的目标,试图在半路上与他们相遇。当然最终还是要达成我们的终极目标,只是需要的时间更长。虽然我们并不在意时间变长,但还是希望在某些地方可以加速。我们把第一个级别修改为“搭建持续集成环境,保证建成,并清楚自己的测试覆盖率”。这些是很容易达到的,但它建立起一些制度并使大家的状态从无变到有,并产生积极向上的动力。

HGTS:有谁迫不及待地想参与进来?

Neal:最早参与的人通常是测试圈子里的人。这一小群人定期举行会议,多数是对测试非常热衷的人。我们慢慢地把其他认识的人也拉进来。当时有许多热心的积极参与者,这对我们来说是个惊喜。我们通过ToTT(注:ToTT,是Testing on the Toilet的简写,直译为“马桶上的测试”。本书前面的内容也有所提及,在Google测试博客“http://googletesting. blogspot.com”上经常出现)和其他的一些活动把测试搞得充满热情,更有趣味和吸引力,包括fixits(注:fixits是另外一个Google的文化活动,促使人们一起“修复”一些注定要损坏的东西。团队可能会举行一个fixit来降低bug,另外一些团队或许会搞一个针对安全测试方面的fixit,也可以用来在c代码中增加 #include的使用或者用以重构。fixit可以跨越技术领域,可以用来增加咖啡馆的食物或怎样让会议进行地更加平滑。任何一个活动,只要一起参与能够解决通用问题,都是一个fixit)、VP的邮件、海报、TGIF上的分享等活动。

Mark:一旦有的新的团队参与进来时(当时已经有许多团队对我们这个计划感兴趣),他们会意识到:① 需要提前做一些功课;② 不必有专业知识。那些专业知识会让初学者产生挫败感。

HGTS:有谁不愿意参与这个计划吗?

Neal:多数项目都不愿意参加。正如我上面提到的,这个计划难度非常大。给我们最初的勃勃野心迎面浇了一盆凉水。大概有两种类型的项目:压根儿没有测试的项目和测试非常糟糕的项目。我们需要把计划调整的更容易一些,使他们能够利用一个下午的时间就把需要的测试任务完成(在我们的帮助下他们的确也做到了)。

Mark:还有,当时还处于另外一种状况,测试的价值和自动化测试在Google还没有被真正认可。与今日不同,甚至情况完全相反。那个时候,多数团队也认可这是一个非常酷的想法,但他们还有更重要的事情要去完成(例如写产品的功能代码)。

HGTS:最初,一些参与团队必须要去克服的困难是什么?

Neal:惯性,糟糕的测试,没有测试时间。测试被当做其他开发人员的问题,或者测试是测试团队的问题,跟我没有关系。在写功能代码的时候,谁有时间去写测试代码啊?

Mark:尝试寻找下面的团队:① 足够感兴趣;② 没有太多的冗余代码;③ 在团队中有一个测试战神(对测试足够的了解的人)。这是我们测试认证计划在团队里的三大障碍,我们会一个一个团队地去解决。

HGTS:是什么把测试认证计划推向了主流?是病毒性的爆发还是线性的增长?

Russ:首先是一批试点团队,他们对测试特别友好。早期的测试认证计划鼓吹者也和我们保持比较亲密的联系。初期很好地选择了参与者,基本上都是一些很容易成功的团队。

在2007年中期,我们宣布测试认证计划“正式启动”的时候,有15个试点团队在这个计划的不同级别上运行着。在正式宣布之前,我们在山景城、纽约和其他地点的所有办公大楼上张贴“神秘的测试认证”的大海报,每个海报上用图片印着各个试点团队名字,使用的是内部项目名称,如Rubix、Bounty、Mondrian和Red Tape。海报上唯一的文字是“未来就是现在”和“至关重要,莫被遗弃”,还有一个链接。从喜爱猜谜的Google同事那里,我们得到了大量点击访问,多数人想去一探究竟,还有一些人想去验证自己的猜测是否正确。同时我们也使用ToTT来宣传这个新计划,并把读者指引到他们能够得到信息的地方。这是一个信息闪电战。

宣传网站上有一些信息,包括为什么测试认证对于团队很重要,以及用户可以得到怎样的帮助。里面强调指出,参与团队会从一个很大的测试专家社区里得到一个测试认证教练,同时还会得到两个礼物——一个表示构建状态的发光魔法球,可以告诉团队他们的(一般是新的)持续集成是通过(绿色)还是失败(红色);另外一个是一个漂亮的星球大战土豆头工具包。这个被称为达斯土豆工具包里有三个逐渐变大的格子,每当团队达到新的测试认证级别时我们都会给予奖励。各个团队展示他们的魔法球和土豆头,为这个计划吸引来更多好奇的团队和带来更好的口碑。

测试圈子里的成员是这个项目的第一批教练和发言人。随着越来越多团队的加入,有许多热情的工程师帮助造势,自己也成为其他团队的教练。

每次我们尝试说服更多的团队加入这个计划的时候,都会与他们逐一讨论理由和原因。一些团队是由于你能使他们信服每一个级别和教练都会帮助团队在这个领域有所提高而加入的。一些团队认为他们会有所改善,并坚信这种“官方”级别评定会使他们因为当前正在做的工作得到好评。另外的一些团队,他们本身的测试成熟度已经很高了,但加入这个计划,会给其他的团队发出一种信号,表示他们已经很重视测试了。

几个月之后,大约有50个团队参与进来,许多有魄力的测试工程师签约成为测试认证计划的教练。这是一个在团队工程师和工程生产力团队之间强大的合作关系的开始。

这是一个病毒爆发式的增长,通过许多一对一草根之间的对话发展起来。虽然我们有明确地要求一些团队参与进来,但也有另外的团队自己找上门来。

大概一年后,有一百多个团队通过测试认证,加入测试认证计划的速度开始慢慢地放慢。时任志愿者主管Bella Kazwell策划了一个活动——测试认证挑战。该挑战活动开发了一个积分系统,新增测试、引入新团队参与计划、提高团队测试实践或提升测试认证级别等活动会被计算进来。同时,有一些个人奖项、全球的项目都参与进来,比拼谁是最高分获得者。志愿者被激励,随之又激励了公司里更多的团队,使测试认证计划再次加速,并吸引了更多的志愿者教练。

参与测试认证的团队一般都使用每个级别的标准作为可度量的团队目标。到2008年后期,一些团队的经理开始使用这个作为他们的团队目标,而工程生产力团队使用一个团队在测试认证计划里的级别,来评测这个团队在提高测试方面的重视程度,并在决定是否向一个团队投入有限测试资源时作为一个重要的参考指标。在某些限定的领域,一个团队是否达到特定的测试认证级别已经成为管理上的期望或启动的标准。

在2011年,不断有新的志愿教练加入,也不断有新团队签约加入,测试认证已经在整个公司中运行起来。

HGTS:在最初的两年测试认证计划做了哪些变化?每个级别的要求有什么变化么?教练系统有变化么?对于参与者在体验方面有哪些改进?

Tracy:对认证级别的数量和一些级别要求做了调整,这是最大的变化。最初我们有四个级别。从级别0到级别1,有意地设计成比较容易就可以达到。许多团队,特别是一些可测试性比较差且有遗留代码的团队,发现从级别1到级别2非常困难。这些团队会比较受挫,并有意向退出测试认证计划。我们在级别1和级别2之间增加了一个稍微简单的新级别。我们把这个新级别定义成为“1.5”,但实际上还是决定把新增的级别设定为2,并把后面所有的级别+1。

我们同时发现有一些要求并不合适,例如小型测试、中型测试、大型测试的比例要求并不适用于所有团队。在我们增加了新级别之后,同时也更新了级别标准。我们在新增了“增量覆盖率”的具体比例要求的同时,把各级别测试的比例也给去掉了。

辅导教练始终都在,但许多团队已经进入“自我调教”的模式。由于测试文化已经无处不在,许多团队已经不需要我们再提供建议了。他们希望跟踪自己的进度。对于这些团队,我们不再指派教练,而是提供一个邮件列表用来回答他们的问题,这也是通过另一双眼睛来观察他们级别的转换。

Russ:值得注意的是,从一开始,我们就意识到测试认证标准必须要合理地制定。测试并像制作饼干,都是一个模子里出来的。在我们选择标准的时候会发现有一些团队的测试状况跟我们心中的所想迥然不同,无论是用以记录测试覆盖率的工具还是用其他的度量方式,各有千秋。但每一个标准都有其背后的合理性。在这里我们比较开明的没有一刀切,而是定制化了一些多数团队可以满足的标准。

HGTS:当前如果一个团队坚持参与测试认证计划会有什么收获?还需要投入什么?

Tracy:可以把这个作为炫耀吹牛的资本。清晰明确的步骤、外部的帮助、一个看起来很酷的发光球,但对于团队来说,真正的收获是质量方面的提升。

实际投入非常小,但团队需要专注于改善他们的测试成熟度。我们有一个定制化的工具,教练可以用此跟踪团队进度,检查每一步目标是否达成。在一个页面展现所有按照级别排列的团队数据,可以通过鼠标点击查看指定团队的细节数据。

HGTS:在所有的认证级别里,哪个级别会给团队带来更多的麻烦?

Tracy:最困难的一步是“对于所有的重要代码变更,都需要经过测试”。这个要求在一个可测试性很好的项目中比较容易做到,但对于一个有遗留代码的项目,特别是之前写代码的时候并没有测试意识,这就很困难。这可能需要写一个大的端到端测试并尝试通过测试验证系统的特殊代码路径,然后再自动化。从长远角度看,更好的办法是代码重构,从而获得良好的可测试性。有一些团队,在写代码的时候没有考虑到可测试性,一样会发现很难去达到足够的测试覆盖率,不管是单元测试,还是端到端的测试。

HGTS:在Google一般的活动只会持续数周或一个季度,但是测试认证计划已经运行了近五年而且没有迹象表明会停止。是什么导致测试认证计划经过了时间的考验?测试认证之后会面临什么挑战?

Russ:能够保持活力的原因,不是因为这是个体参与活动,而是因为这是一次公司文化的变迁。随着一系列活动,测试小团队、ToTT、支持邮件列表、技术交流、晋升贡献、代码风格文档,常规的测试已经演变成为公司所有工程师必须要做的事情。不管一个团队是否参与了测试认证计划,这个团队总是希望有一个经过深思熟虑的自动化测试策略,这些策略来自于一部分测试专家,不管是团队内部还是外部的专家。

能够持续至今也证明这个计划是可行的。只有很少一部分领域在使用手动测试。在这样的情况下,测试认证计划已经完成了它的使命。它会被作为伟大的历史遗产,即使这个官方的草根计划某天真的结束了。

HGTS:有什么建议要给其他公司的同行?如果他也想在自己的组织里考虑类似的计划?

Tracy:从一些对测试比较认可且友好的团队开始,培养一批可以从你的计划中受益的核心团队。在激励和赞美方面不要害羞,甚至要求其他人也来说好话。良好的教练是测试认证计划成功的一个重要原因。如果你想要求一个团队去尝试新的事物或者做某些改进,给他们提供一个联系人会更好一些,这个联系人来源于更大的社区,并可以从他那里得到帮助。一个工程师或团队如果在一个邮件列表中问了一个很傻的问题,会感觉比较尴尬,但询问对象如果是一个可以信任的测试认证教练的话,这将会好很多。

同时,寻找一些让你的计划变得有趣的方法。为你的计划取一个好的名字,最好不要包含“认证”字样,这可能会引起见识短浅的官僚主义。或者像我们一样,就使用一个目光短浅的名字“测试认证”,但要不断地提醒大家注意我们知道这是一个不好的名字,这只是一个反语,用以衬托你的计划其实并不是这样的。每一个步骤包含的内容要尽可能的少,这样大家可以看见自己的进步。不要陷入尝试去创建一个包含独立指标的完美系统的陷阱中。对所有人都完美的事情是不存在的。在没有可替代的方案时,在合理的地方达成一致并勇往直前是很重要的。需要灵活的时候就灵活一些,但一定要坚持你的原则底线。

到此本章已结束。后面是可选阅读部分,关于Google如何面试SET、与Google工程师Ted Mao的访谈,以及关于Google SET使用的工具等资料。

优秀的SET在各个方面都很出色:是一个编码能力很强的程序员,可以写功能代码;也是一个能力很强的测试者,可以测试任何产品,有能力管理他们自己的工作和工具。优秀的SET不仅可以看到树木而且可以看到整个森林,在看到小段函数原型或者API的时候,就能想到各种使用这段代码的方法以及怎样破坏这段代码。

在Google,所有的代码都存放在同一个代码库中,这意味着任何人可以在任何时间使用里面的任何代码,所以代码本身一定要可靠且稳定。SET不仅仅要发现功能开发人员遗漏的代码缺陷,而且还要去关心其他的工程师是如何使用这些代码模块,并确保这种使用方式是没有问题的,甚至还会去关心这些代码未来适用的功能。由于Google前进变化的速度非常快,所以代码一定要保持干净、连贯一致。在最初的代码作者都不再关心这些代码的时候,仍要保证这些代码可以正常工作。

在面试的过程中我们如何考察这些技能和心态呢?这可不是一件容易的事但幸运的是,我们已经找到了上百个满足条件的工程师。我们期望有这样的混合型人才:对测试有强烈兴趣和天资的开发人员。一个通用且有效的招募优秀SET的方法是,给候选人和其他开发角色一样的编程问题,并考察他们在处理质量与测试方面的方法。在面试过程中,SET有两次回答错误的机会。

常常通过一些简单的问题就可以识别出哪些是优秀的SET。在一些棘手的编码问题或功能的正确性上浪费时间,不如考核他们是如何看待编码和质量的。在SET的一轮面试中会有一个SWE或SET来考察算法方面的问题。对于候选者,最好去考察如何思索问题的解决方案,而不是解决方案本身的实现上体现得多么高雅。

注意


SET的面试重点在考察候选人如何思索问题的解决方案,而不是解决方案本身的实现上有多么高雅。

这里有一个例子。假如这是你第一天上班,你被要求去实现一个函数acount(void* s),返回一个字符串中大写字母A出现的次数。

如果候选人上来就直接开始写代码,这无非在传递一个强烈的信息:只有一件事情需要去做而我正在做这个事情,这个事情就是写代码。SET不会遵循这样的世界观。我们希望先把问题搞清楚。

这个函数是用来做什么的?我们为什么要构建它?这个函数的原型看起来正确吗?我们期望候选人可以关心函数的正确性以及如何验证期望的行为。一个问题值得更多的关注!候选人如果没头没脑地就跳进来编码,试图解决问题,在对得测试问题上他同样会没头没脑。如果我们提出一个问题是给模块增加测试场景,我们不希望候选人上来就直接开始罗列所有可能的测试用例,直到我们强迫他停下来。其实我们只是希望他先执行最佳的测试用例。

SET的时间是有限的。我们希望候选人能够回过头来寻找最有效的解决问题的方法,为先前的函数定义可以做一些改进。优秀的SET在面对拙劣的API定义的情况下,在测试的过程中也可以把这个API定义变得更漂亮一些。

普通的候选人会花几分钟通过提问题和陈述的方式来理解需求文档,例如以下几点。

基本上,最佳候选人会有针对性地提出一些新观点。如果这些观点比较明智的话,它们都是值得考虑的。

注意


一个优秀SET候选人不应该被告之要去测试代码,这应该是SET自然要考虑的地方。

所有这些面试问题,无论是针对问题本身还是针对输入参数都有一个关键之处,那就是任何通过入门级别编程课程的工程师都可以针对这个问题写出简单的功能代码。优秀的候选人和普通的候选人在提问和思路上的表现会迥然不同。我们要确保候选人能够感觉足够舒适地去提出问题,如果没有问题,我们就引导他们去提问,确保他们不会因为当前是在面试就直接去写代码。Google的人应该质疑几乎所有事情,但仍然会把问题解决掉。

在这里,如果把这个面试问题的所有正确实现与常见错误都罗列一遍,肯定会招人讨厌,毕竟这不是一本关于编程或面试的书。但为了讨论的需要,让我们使用一个简单且常见的代码实现方式来做讨论(译注:在下面代码中,第6行代码中的‘a’应该是大写字母‘A’,原书有误)。注意,候选人一般都会选择使用自己喜欢的编程语言,如Java、Python等,但这经常会引起一些问题,例如垃圾收集、类型安全、编译和运行时刻的不同关注点等。我们同时要确保候选人可以正确理解这些问题。

int64 Acount(const char* s) {
    if (!s)
    return 0; 
    int64 count = 0; 
    while (*s++) {
        if (*s == ‘a’) 
        count++;
    }
    return count;
}

候选人应该可以走查他们的代码,指出程序中出现的指针或计数器的值在测试数据输入之后在代码运行时刻是如何变化的。

一般来说,普通的SET候选人会做到以下这些。

现在,我们想看候选人是否可以测试他们自己写的代码。令人费解或复杂棘手的测试代码是世界上最差的代码,但这也比没有测试代码强。在Google,如果测试运行失败,需要清楚地知道测试代码在做什么。否则,这个测试就应该被禁止掉,或是被标记为怪异的测试,或是忽略这个测试的运行失败。如果这样的事情发生了,这是编写出坏代码的SWE的责任,或是代码审查时给予通过投票的SET/SWE的失误。

SET应该可以用黑盒测试方法做测试,假设其他人已经实现了功能;也可以用白盒测试的方式,考虑其内部的实现可以知道哪些用例是无关的。

通常情况下,普通的候选人会这样做。

更优秀的候选人会这样做的更多一些。

优秀候选人的例子


by Jason Arbon

最近有一个候选人(后来被证明他在实际工作上的表现也确实令人吃惊)在被问到如何针对这个返回值为64位整形的API做边界测试时,他很快地意识到由于时间和空间的限制,不可能使用物理的方法做测试。但为了做完这个题目和出于好奇心,在思考这个级别的扩展性时尝试使用非常大量的数据来做这个测试,并提出使用Google的网页索引作为输入数据来源。

他是如何验证这个结果的呢?他建议使用一个并行来实现,从而保证产生两份相同的结果。他也考虑到使用统计学上抽样的方法:大写字母A在网页上出现的期望频率。由于我们知道网页索引后的数量,计算后的数字应该比较接近。这正是Google思考测试的方式。即便我们不会真的构建这样庞大的测试,思考这些解决方案一般也会对正常规模的测试工作提供有意义或有效的借鉴。

在面试中需要另外考虑的是文化上是否匹配。SET候选人在面试过程中是否在技术上有好奇心?当面对一些新想法的时候,候选人是否能够把它融入到解决方案里呢?又是如何处理有歧义的地方的?是否熟悉质量方面的理论学术方法?是否理解质量度量或其他领域的自动化?例如土木工程或航空工程方面的自动化。当你发现在实现中存在缺陷时,是否心存戒备,思路又是否足够开阔?候选人不必具备所有的这些特质,但仍是越多越好。最后还要考虑在日常的工作中,我们是否愿意和这个人一起工作。

需要着重强调的一点是,如果某人在应聘SET岗位的时候没有具备足够强的编码能力,这并不意味着此人不是一个合格的TE。我们已雇佣到的一些优秀的TE,之前都是来应聘SET岗位的。

一个有趣的现象值得我们注意,Google在SET招聘过程中经常会与优秀的候选人失之交臂,原因是这些人最后成为非测试类的SWE或对测试过度专注的TE。我们希望SET的候选人具有多样性,他们可能会在以后工作上成为同事。SET是一个真正的混合体,但有些时候这也会导致一些令人不悦的面试得分。我们想确保的一点是,这些低分是由于我们的面试官在使用严格的SET考核标准而导致。

正如Patrick Copeland在序言中说的那样,关于SET的招聘目前还有一些不同的观点。如果SET是一个优秀的编程者,他就应该只去做功能开发的工作吗?SWE也是很难雇佣到的。如果他们擅长做测试,就应该只是专注于解决纯粹的测试问题么?事实总是存在于两者之间。

招聘优秀的SET是一件很麻烦的事情,但这是值得的。一个明星级的SET能够对一个团队产生巨大的影响。

Ted Mao是一位Google的开发工程师,但Ted的主要工作专注于测试工具的开发方面。特别要提到的是,Ted制作的Web应用程序方面的测试工具,所有的Google内部应用上都在使用。Ted本身在SET这个圈子里也很有名气,一般情况下SET都对优秀工具有需求,否则效率就会非常低下。Ted可能是Google内部对通用Web测试基础框架最熟悉的人员。

HGTS:你是什么时候加入Google的?是什么吸引你来这里工作的?

Ted:我是2004年6月加入Google的。在那之前,我只在一些大公司里待过,像IBM和Microsoft,那个时候Google是最热门的创业型公司,吸引了大量非常有天赋的工程师加入。Google尝试去解决许多有趣且有挑战性的问题,我想参与进来,与这个世界上最优秀的工程师们一起去解决这些问题。

HGTS:你是Google缺陷管理库Buganizer(注:Buganizer是Google内部使用的缺陷管理系统,开源版本的Buganizer被称为问题跟踪工具,在Chromium项目中有使用,参见http://code.google.com/chromium/issues/list)的创建者。与之前的BugDB相比,Buganizer尝试去解决了哪些核心问题呢?

Ted:BugDB当时是在阻碍我们的开发流程的运转,而不是为之提供支持帮助。老实说,它浪费了许多宝贵的工程开发时间,这使得使用这个工具的团队负担更加沉重。它的问题表现在许多方面,像UI延迟、笨拙的工作流模式、在非结构化的文本字段中使用特殊字符串等。在设计Buganizer的时候,我们确保我们的数据模型和UI可以反应出用户的真实开发过程。在核心产品团队与集成过程中,这个系统通过使用扩展的模式,经受住了考验。

HGTS:你在Buganizer上做的非常出色。这真是我们用过最好的缺陷管理数据库了。你又怎么开始去搞Web自动化方面的测试呢?是你看到这方面有强烈的需求吗?还是有人请求你去帮助解决这方面的问题呢?

Ted:在为Buganizer、AdWords和其他Google产品工作期间,我经常发现已有的Web自动化测试工具不能满足我的实际需求,他们并不像我期望的那样快速、扩展性强、健壮且有用。当工具团队宣布去寻找这个领域的技术人才时,我抓住了这个机会。这方面的尝试就是我们知道的Matrix项目,而我是这个项目的技术负责人。

HGTS:如今有多少个测试团队在使用Matrix做测试执行?

Ted:这个取决于你如何度量测试的执行。例如,我们在使用的一个指标,我们称为“浏览器会话”。针对所有浏览器,每一次新的浏览器会话都会保证从同样的状态开始运行。这样的话,在这个浏览器上运行的测试只由测试本身、浏览器和操作系统来决定,其行为也就是可以确定的。Matrix在Google的每个Web前端团队都有实践应用,每天提供大于一百万个新浏览器会话。

HGTS:Buganizer和Matrix这两个项目,曾有多少人为之工作?

Ted:在项目开发高峰时期,Buganizer有5个工程师,Matrix有4个工程师。当时我们的团队本可以拥有更多的人,让团队存活地更长久一些。虽然这令我有些伤感,但我觉得在当时的情况下我们已经做的足够棒了。

HGTS:在你打造这些工具的时候,你面临过的最难的技术挑战是什么?

Ted:对于我而言,我认为最艰难和最有趣的挑战总是出现在设计阶段。理解一个问题领域,权衡不同的解决方案和它们的利弊,并从中选一个最优的方案。实现阶段一般按照选定的方案去做即可。这样的选择决定和功能实现一样会贯穿项目的整个生命周期,决定项目的成败。

HGTS:对于世界上其他专注于测试工具方面的工程师,你有什么一般性的建议吗?

Ted:专注于你的用户,理解他们的需求并解决他们的问题。不要忽视一些看不见的功能,如可用性和响应速度。工程师在解决他们问题方面有自己独特的能力,要允许他们使用你无法预料的方式来使用你的工具。

HGTS:在测试工具框架领域,下一个最大的问题,或者是你最感兴趣的且最想去解决的问题是什么?

Ted:有一个问题我最近一直在思索,我们的工具变的越来越强大和复杂,但相应地,在理解和使用这些工具上也变得越来越困难。例如,使用Google当前的Web测试框架,工程师可以一键运行上千个Web测试,并发地运行,针对不同的浏览器。我们抽象封装了如何运行的细节,例如这些测试是在哪里开始真正运行的,浏览器是从哪里得到的,测试环境是如何配置的等细节。从某方面上讲,这是好事儿。但是,如果测试运行失败之后,工程师又必须去做调试,这些隐藏的细节就必须要去了解。我们已经在这个领域有所举措,但仍然有很多可以去做且必须去完成的事情,它们在等待着我们去解决。

Simon Stewart是WebDriver的创建者,也是Google在浏览器自动化领域的专家(译注:Simon于2013年离开Google加盟Facebook)。WebDriver是开源Web应用自动化测试工具,不仅在Google内部,在业内也广受欢迎,也是GTAC(Google测试自动化大会)历史上最热门的话题之一。我们的采访记者和Simon一起做了这个访谈,Simon在这里讨论了Web应用自动化的话题和关于WebDriver未来的一些想法。

HGTS:好像很多人并不清楚Selenium和WebDriver之间的区别,你能解释一下吗?

Simon:Selenium是Jason Huggins在ThoughtWorks时创建的一个项目。Jason那个时候写了一个Web应用,假定用户使用的浏览器是IE。这样做可以理解,因为那个时候IE有百分之九十多的市场占有率。但是他持续不断的得到用户反馈,指出这个应用在Firefox浏览器上有bug,这个时候他就碰到一个问题,当他修复Firefox上的bug的时候会导致在IE上出现另外的问题。对他来说,Selenium是一个可以加速开发应用程序的工具,可以确保每次变更在两个浏览器上都可以正常工作。

大概在一年前,或者不到一年的样子,我真正开始去创建WebDriver。但在Selenium真正稳定之前,我的主要精力集中在更加通用的Web应用测试上。这并不奇怪,我们两个使用了不同的方法来实现Web自动化。Selenium在浏览器内部使用JavaScript实现,而WebDriver使用浏览器本身的API集成到浏览器内部。两种方法各有优劣。例如,Selenium可以在瞬间打开一个新的Chrome浏览器,但却不能上传文件或者很好地处理用户交互,因为它是JavaScript实现,必须限定在JS沙箱之内。由于WebDriver构建在浏览器里面,它可以突破这些限制,但打开一个新的浏览器却比较痛苦。在我们都开始为Google工作的时候,我们决定把这两个集成到一起。

HGTS:但我还是听到人们在分别谈论它们。它们还依然是两个独立的项目吗?

Simon:对于所有浏览器自动化工具集,我称为Selenium。WebDriver只是其中的一个工具,官方的名字是“Selenium WebDriver”。

HGTS:那么Google是如何介入进来的呢?

Simon:几年前,Google在创建了London office的时候,雇佣了一些Thoughtworks的前员工,这些人邀请我去做一个关于WebDriver的技术分享。这次分享并没有给我带来什么信心,前排的一个家伙听着听着居然睡着了,我在分享的过程中必须与他的鼾声做斗争。碰巧的是,这个分享的录制设备也坏了。但还是有很多人对此感兴趣,于是我们再次被邀请在GTAC上做一个没有鼾声的分享。之后我很快就加入了Google。现在我也知道那个事情的真相了。

HGTS:确实,每个人有自己的秘密。说正经的,我们之前也看过你的分享,很难想象有人会睡着。他是我们认识的人吗?

Simon:不,他已经离开Google很久了。我们还是假设他前一天晚上熬夜了比较好。

HGTS:我们必须从中吸取教训。大家需要明白,在Simon Stewart的分享过程中睡觉,对你的职业生涯是非常不利的。自从你加入了Google,WebDriver是你的全职工作吗?

Simon:不,这只是我20%的工作。我的主要工作是一个产品的SET,虽然我现在还在负责推进WebDriver的前进,但已经有外部的贡献者了,他们做的非常棒。在一个开源项目的早期阶段,人们拿过来使用,因为他们需要这样的项目,而且也没有其他可以替代的。内在的激励就是要去贡献。现在许多WebDriver的用户都在口口相传如何去使用操作,这些用户更像是消费者,而不是贡献者。但在早期,WebDriver社区的草根却在真正地推进这个工具向前发展。

HGTS:我们知道故事的来龙去脉了。WebDriver在Google内部非常受欢迎,这是怎么开始的?是有试点的项目吗?有没有一些错误的教训呢?

Simon:这是一个社交网络产品,在Wave团队最先开始使用。该团队位于Sydney的办公室,但这个团队现在却已经不存在了。Wave的工程师尝试去使用Selenium作为他们的测试框架,但是却无法解决一些问题。Wave实在是太复杂了。工程师们很勤奋,找到了WebDriver并开始问许多优秀的问题,然后这变成了我20%的时间要处理的事情。他们找到我的老板,希望我能在去Sydney待上一个月,帮助他们建立自己的测试框架。

HGTS:我想你当时成功了。

Simon:是的,那个团队很棒,我们把框架做出来了。提出了大量针对WebDriver的新需求,这对于其他的团队也是一个榜样,WebDriver在Web应用方面处于领先地位。从那一刻开始,WebDriver就再也没有缺少过用户,对于我来说,全身心的投入进去也更有意义。

HGTS:第一个用户总是最难的。你是怎么改进WebDriver,并让它可以在Wave团队工作的?

Simon:我使用了一个被称为DDD(译注:defect-driven development)的流程,缺陷驱动开发。我总是宣称WebDriver是完美无瑕的,一旦用户发现了一个bug,我就立刻去修复它,然后再宣布它没有问题了,更加完美无瑕。这样的话,可以确定我修复的bug是一些人们真正关心的bug。这对于改善一个已有产品是非常有用的,这可以确保你是在修复最重要的bug,而不是修复人们并不关心的bug。

HGTS:你还是WebDriver里唯一的工程师吗?

Simon:不,我们有一个团队,WebDriver是Google内部的一个正式项目,并在开源方面非常活跃。随着浏览器数量、版本和平台的不断增加,我们告诉大家我们必须很疯狂,我们每天都在把不可能的事情变成可能。有时候我觉得比较理智的人其实并不适合做我们这个项目。

HGTS:在Wave项目之后你得到了很多动力。对于用户来说,是否意味着WebDriver替代了旧的Selenium的地位?

Simon:我想是的。许多原来Selenium工程师都去做其他事情了。由于在Wave上的成功,我对WebDriver也充满了信心和能量。一些我从来没有见过的人,如来自德国的Michael Tam,已经开始在WebDriver上做一些重要的工作了,我也很小心地鼓励这样的关系模式。Michael是第一个我没有真正见过就有提交代码权限的人。

其实我并没有特别地跟进WebDriver的扩张。比较明确的是,在物理位置上离我近的团队,更愿意去使用WebDriver。我想Picasa网络相册团队事实上是第一个真正使用WebDriver的团队,而且是在Wave团队之前,然后Ads也开始使用了。在Google,不同团队在使用各自的Web自动化框架。Chrome在使用PyAuto,Search在使用Puppet(有一个开源的版本叫做Web Puppeteer),Ads使用WebDriver,等等。

HGTS:WebDriver的未来会怎样?你们团队有什么目标吗?

Simon:好吧,目前看起来还有点乱。即便是在几年前,在市场上还有一个主流的浏览器,但现在没有了。IE、Firefox、Chrome、Safari、Opera等都拥有了自己的市场。但这还只是桌面版的而已。在移动端的浏览器引擎也正在疯狂地扩张。在2008年以后,许多商用的浏览器自动化工具把他们都给忽略了,IE除外,这其实是非常不明智的做法。下一步,WebDriver会在标准化上发力,这样可以保证相同的网络应用代码在不同的浏览器上都可以工作。当然,这也需要浏览器厂商一起参与进来,支持我们的WebDriver API。

HGTS:这听起来好像是标准委员会要做的事情。目前有什么进展吗?

Simon:是的,有一些。很不幸地是,我必须去写一些英文文档,而不是编写代码了,在W3C里有一个文档,所有的浏览器开发商都会参与进去。

HGTS:你希望的未来是怎样的?未来的浏览器自动化工具又是如何工作的呢?

Simon:我希望他们都消失到后台之中。自动化的API会对所有浏览器适用,人们不用去担心这些基础框架,他们仅仅去使用即可。希望人们能把更多的精力放在他们Web应用本身,而不是如何去自动化上。在人们真正忘了WebDriver的存在之后,我们就成功了。


相关图书

现代软件测试技术之美
现代软件测试技术之美
渗透测试技术
渗透测试技术
JUnit实战(第3版)
JUnit实战(第3版)
深入理解软件性能——一种动态视角
深入理解软件性能——一种动态视角
云原生测试实战
云原生测试实战
Android自动化测试实战:Python+Appium +unittest
Android自动化测试实战:Python+Appium +unittest

相关文章

相关课程