验收测试驱动开发:ATDD实例详解

978-7-115-31062-0
作者: 【德】Markus Gärtner
译者: 张绍鹏冯上
编辑: 杨海玲

图书目录:

详情

本书是第一本成功实施和运用ATDD(验收测试驱动开发)的入门级的实践指南。书中通过两个端到端的案例,演示了如何使用不同的框架和语言来实施ATDD。每一个案例研究里都涉及了大量的实际产出,包括测试自动化类、步骤定义和完整的案例实现。这些实际的例子,形象地说明了ATDD的基本原则,展示了ATDD是怎样融入开发流程的,给出了来自作者的广泛经验的重要提示,也指出了要避免哪些危险的陷阱。

图书摘要

软件开发方法学精选系列

ATDD by Example:A Practical Guide to Acceptance Test-Driven Development

验收测试驱动开发 ATDD实例详解

[德]Markus Gärtner 著

张绍鹏 冯上 译

乔梁 审校

人民邮电出版社

北京

图书在版编目(CIP)数据

验收测试驱动开发:ATDD实例详解/(德)加特纳著;张绍鹏,冯上译.--北京:人民邮电出版社,2013.3

(软件开发方法学精选系列)

书名原文:ATDD by example:a practical guide to acceptance test-driven development

ISBN 978-7-115-31062-0

Ⅰ.①验… Ⅱ.①加…②张…③冯… Ⅲ.①软件开发 Ⅳ.①TP311.52

中国版本图书馆CIP数据核字(2013)第031546号

内容提要

本书是第一本成功实施和运用ATDD(验收测试驱动开发)的入门级的实践指南。书中通过两个端到端的案例,演示了如何使用不同的框架和语言来实施ATDD。每一个案例研究里都涉及了大量的实际产出,包括测试自动化类、步骤定义和完整的案例实现。这些实际的例子,形象地说明了ATDD的基本原则,展示了 ATDD 是怎样融入开发流程的,给出了来自作者的广泛经验的重要提示,也指出了要避免哪些危险的陷阱。

通过本书,读者可以掌握成功实施ATDD的思维过程。通过Cucumber使用ATDD来描述业务人员能理解的软件,使用ATDD工具测试网页;通过FitNesse这种基于wiki的验收测试框架把ATDD带到Java中;通过全新的讨论会形式来协作地捕捉软件需求,实现更加用户友好的、协作的测试自动化,使测试更整洁;倾听测试结果,并以价值驱动测试重构。

本书非常适合测试人员、分析师、开发人员或项目经理阅读。

软件开发方法学精选系列

验收测试驱动开发:ATDD实例详解

◆著 [德]Markus Gärtner

译 张绍鹏 冯上

审校 乔梁

责任编辑 杨海玲

◆人民邮电出版社出版发行  北京市崇文区夕照寺街14号

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

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

北京 印刷

◆开本:700×1000 1/16

印张:12.5

字数:236千字  2013年5月第1版

印数:1- 000册  2013年5月北京第1次印刷

著作权合同登记号 图字:01-2012-7102号

ISBN 978-7-115-31062-0

定价: 元

读者服务热线:(010)67132692 印装质量热线:(010)67129223

反盗版热线:(010)67171154

广告经营许可证:京崇工商广字第0021号

版权声明

Authorized translation from the English language edition,entitled:ATDD by Example:A Practical Guide to Acceptance Test-Driven Development,9780321784155 by Markus Gärtner,published by Pearson Education,Inc.,publishing as Addison-Wesley Professional,Copyright © 2013 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 & Telecommunications Press Copyright © 2013.

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

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

版权所有,侵权必究。

其他

献给我的妻子Jennifer、我的宠物Leon和我的女儿Katrin,感谢你们允许我用本来应该是陪伴你们的时间来完成此书。

译者序

随着敏捷软件开发方法在国内越发普及,验收测试驱动开发(ATDD)作为其中重要的一个实践,也越来越多地受到人们的重视。尽管常常听到这个名词,但相比于Scrum、持续集成等一些敏捷实践,ATDD在很多人的脑海中还都是一个比较模糊的概念。虽然在网络上经常可以看到有关 ATDD 的文章,但系统地介绍ATDD的书籍则是少之又少。

ATDD究竟是什么?它和TDD(测试驱动开发)、BDD(行为驱动开发)、Specifi-cation by Example(实例化需求)到底是什么关系?很多人都心存疑惑。看完本书后,你应该就能得到属于自己的答案了。

确定软件的需求是软件开发中最基本的问题,构建正确的软件比正确地构建软件要重要得多。ATDD正是这样一种确保我们构建正确的软件的方法。本书作者Markus具有丰富的敏捷测试经验,他在书中通过两个不同情景下的例子,生动地讲述了ATDD是如何帮助我们开发正确的软件的。除此之外,书中还总结了成功应用ATDD的一些模式及其理论基础。

本书深入浅出地介绍了ATDD。不论是对敏捷开发的爱好者,还是打算采用ATDD 方法的敏捷团队都有很高的参考价值。对于国内处于起步阶段的敏捷团队,更是不可多得的工作指导。当然,任何团队、任何项目都是独一无二的,没有一个“标准模式”是在任何情况下都适用的。我们需要根据团队和项目的特点去创造属于自己的方法,而不是全盘照搬“标准模式”。这一点对于应用 ATDD也不例外。Markus 在书中不断告诫我们:在实践中,每个团队都需要找到适合自己的ATDD应用方式,通过应用一些简单的ATDD实践,总结经验,不断调整,才能找到适合自己的方式。虽然不存在所谓的“标准模式”,但书中对如何创造属于自己的ATDD方法指明了方向。

这次能有机会将这样一本好书介绍给国内读者,我们十分荣幸。在此特别要感谢乔梁的校阅,他在技术书籍翻译方面的深厚功底让我们受益匪浅;也谢谢以下同事和朋友在翻译过程中给我们的宝贵意见:林雪清(诺西)、齐贺(百度)、杨景希(百度)和啜悦;最后,此书的完成也离不开百度敏捷教练组领导和同事们的支持。

张绍鹏 冯上

2013年2月于北京

Kent Beck序

本书介绍验收测试驱动开发(Acceptance Test-Driven Development,ATDD)的方式,与使用 ATDD 开发软件的过程,有着奇妙的对应关系。通过选择特定的程序行为实例引出系统的正确行为是一门艺术,通过挑选特定的编程实例让读者更好地学习某种编程技术(如 ATDD)也是一门艺术。Markus 选取和呈现实例方面的能力的确令人钦佩。

要阅读本书,你就需要阅读代码。如果你跟得上,你就有机会完成掌握ATDD所必需的思维转变。这种转变简单来说,就是很快从“这是我需要的一个特性”转变为“我们如何去测试它呢?这是个例子。”阅读这些例子,你就会反复在不同的上下文环境中看到这种转变具体是什么样子。

我之所以喜欢这种以代码为中心的表述方式,是因为我充分信任读者的学习能力。这不是那种印在薄薄的技术宣传包装纸上的“测试Web应用的12个简单原则”,一放到残酷的现实中就会大打折扣。在这里你能看到在具体上下文中所做的具体决定。这些决定你可能会(如果你想从书中获得最大的收获,你一定会)不同意,争辨,并作出自己的决定。

本书的后半部分给出了一些普遍的结论,总结了在工作中使用“实例”的一些原则。如果你是那种熟悉了普遍概念之后学东西会更有效率的人,那么从那里开始阅读是个不错的主意。但是,你能从本书学到多少,直接取决于你愿意花多少精力研究那些实例。

就像我曾说过的,TDD(测试驱动开发)的一个弱点是,它可能会退化为一种用来满足开发人员需求的编程技能。某些开发人员从更宽泛的角度来看待TDD,轻易在他们测试的不同抽象级别间跳跃。然而在 ATDD 中不存在歧义,这是一种加强与非编程人员沟通的技术。我们之间良好的协作关系,以及作为这种关系基础的沟通,能够使软件开发更有效率。采用 ATDD 是向着沟通更清晰这个目标迈进的重要一步,而此书是一本全面又平易近人的入门读物。

Dale Emery序

最终没能向客户交付符合要求的软件的项目太多了。多年来,我听到不少项目的客户这样解释失败的原因:开发人员根本不重视我们到底要什么。同时,我听过上百个程序员这样解释:客户根本不告诉我们他们到底要的是什么,大部分时间甚至他们自己都不知道自己要的是什么

我研究了足够多的项目得出一个不同的结论:描述一个软件系统的职责是很困难的。这需要人们讲述和倾听的精确度都远远超过平常交流的程度。编写好的软件是很难的,做好软件测试也是很难的,但软件业中最难的工作是清晰地表达我们希望系统做什么。

验收测试驱动开发能帮助我们应对这个挑战。采用 ATDD 使整个团队在开发开始前就相互协作,以获得对系统清晰一致的理解。ATDD有两个核心实践:首先,在实现任何特性前,团队成员相互协作构造出当前特性的一个具体实例;其次,团队将这个实例翻译成自动化的测试用例。这些实例及其测试作为重要的组成部分,一致而精确地定义了每个特性“完成”的标准。

取得一致的理解到底有什么用?一个开发者在 ATDD 研讨会上是这样解释的:“一旦我们开始一起创建实例,我就真正开始关心我们正在做的事情了。最终我理解了我们要做什么以及为什么要这么做。更重要的是,我知道整个团队都明白我们的目标。于是,我们拥有了共同的目标,我们成为了一个真正的团队。”

ATDD不仅帮我们了解何时才算做完,还能告诉我们进度如何。因为我们对每一个测试进行了自动化,并且编写程序通过一个个测试(和以前的所有测试),那些实例就像是路边的路标,引导我们到达最终的目标。同时,由于每一个实例都代表了一个对用户有价值的功能,所以我们相信自己不仅是取得了进展,而且这些进展都是有价值的。

好了,我已经列举了 ATDD 的核心要点和一些主要的好处。这不难,难的是在现实世界中究竟如何做才能成功?这个问题的解答就留给 Markus Gärtner了。在本书中,Markus不仅仅向你讲述,更向你展示了ATDD在实践中是如何运用的。他让你看到测试人员、开发人员以及业务专家在运用这些 ATDD 原则和实践时到底是怎么想的。

这里我要告诫本书读者,在最初的几章里,我们将跟随业务专家Bill、测试人员Tony、开发人员Phyllis和Alex一起来描述和实现一个小的软件系统。这个系统初看上去可能有些简单,或者过于简单化了,但是不要被表象所迷惑,其实这几章涵盖了很多内容。这是一个高素质的团队,他们拥有的某些技能十分精巧。举例来说,你应该注意到,在需求讨论会上,团队成员没有提及任何技术细节。他们完全关注于系统的业务职责。同样需要注意的是,在Alex和Tony对最初几个测试进行自动化时,Tony充分利用了他“缺乏”编程经验的特点。任何时候,当他对技术细节感到困惑时,他都会要求Alex向他解释,然后和Alex一起修改代码,直到使它们变得清晰明了。还要注意,Alex 经常强调要将测试代码提交到源代码管理系统中——但只在这些测试能运行之后。如果你刚接触ATDD,那么这些技巧可能不那么明显,但它们对于成功运用ATDD是至关重要的。

幸运的是,只要你能继续读下去,你就能学到这些精巧的技能。Markus 经常会停下来解释团队正在做什么,以及为什么这么做。在每章的末尾他都会总结团队是如何协同工作的,并且在本书结尾,Markus 会做一个总结,详细描述成功运用ATDD需要遵循的原则。

本书是验收测试驱动开发的绝妙介绍。即使像我这样实践 ATDD 有一段时间的人,也能在本书中发现全新的视角。最后,这是一本值得反复阅读的书。阅读,实践,再阅读。每次阅读你都能学到一些有用的新东西。

前言

在这本书中,我会对现在被称为验收测试驱动开发(ATDD)的实践做一个入门级的介绍。当我在2008年第一次接触ATDD这个词时,我认为它有点儿做作,没什么必要。当时我已经学会了测试驱动开发而且发现这已经足够了,ATDD对我来说有点儿多余。究竟为什么我需要为验收标准做测试呢?

“时间能改变一切”[Wei86]。所以四年后我发现自己正在写一本关于验收测试驱动开发的书。2009年,我遇到了Gojko Adzic,他刚完成自己的著作Bridging the Communication Gap,他给了我一本,在回伦敦的路上我立刻读了起来。当我读完之后,我对什么是 ATDD 有了更好的理解,并且明白了为什么我们要避免使用这个词。

但是为什么我还是用ATDD by Example命名你手中的这摞纸呢?[1]

关于命名

ATDD这个概念存在一段时间了。它有许多不同的名字,下面是一个不完整的清单:

验收测试驱动开发(Acceptance Test-Driven Development);

行为驱动开发(Behavior-Driven Development,BDD);

实例化需求(Specification by Example);

敏捷验收测试(Agile Acceptance Testing);

用户故事测试(Story Testing)。

在我看来,这些名字都存在缺陷。验收测试驱动开发会给人这样一种印象:验收测试通过了,我们的迭代就完成了。这是不正确的,对任何一组选定的测试,覆盖率都不是百分之百的。测试保护网也会有漏洞。测试不可能覆盖所有的情况,这在测试领域是众所周知的。不过就像Michael Bolton说的,至少当验收测试没有成功时,我们必然能知道工作还没有完成。

虽然对这些名字做了一些讨论,我还是决定将可能的候选名称都列在这里,并让读者根据自己的需要选择。最终我并不关心你怎么称呼它,只要它对你是有用的。软件开发的世界里,充满了有误导性的词汇,并且这种情况很可能还会持续很多年。软件工程、测试自动化、测试驱动开发全都会在某些方面引起误解。正如任何抽象概念一样,不要混淆了它的名字跟它真实的内涵。专家会知道这种方法的名字的局限性。

但为什么同一个方法会有这么多不同的名字呢?而且采用的实践可能也很不同。通过咨询和访谈多个公司使用 ATDD 的多个团队,我发现他们有一个共同点:每个团队都跟别的团队不同。同一种实践对你现在所在的公司的团队是有效的,很可能在另一个环境中会带来灾难性后果。你是否会奇怪咨询师的回答常常是“这要看情况”?这就是原因所在。

为了写作Specification by Example[Adz11],Goiko Adzic采访了不同公司中的50多个使用ATDD的团队。他发现ATDD方法伴随着多种多样的实践。所有成功应用 ATDD 的团队都开始于一些最基本的实践,过一段时间再回顾一下,然后根据他们自己的实际情况做一些改变。从一个轻量级的过程开始,并且在发现有问题的时候引入一些新的东西,对于实践任何方法都是一种十分敏捷的方式。当你尝试 ATDD 的时候,要记住:最初采用的那些实践可能无法解决你所有的问题。过些时候,当你有了更多的经验,你就可以定制自己的解决方案了。

为什么要写另一本关于ATDD的书

虽然Gojko描述了很多成功运用ATDD的模式,我发现目前关于ATDD的书籍还有一些缺失。一种技巧或方法的高级使用者和入门级使用者的需求是有显著区别的。

在浏览关于 ATDD 的文献时,我发现有几本书在较高层次解释了 ATDD,提出了一些原则。对高层次的学习者来说,在他们特定的环境下应用原则是非常简单的。但是对于新手来说,并不是这么回事。一个新手需要更具体的指引才能上路。一旦他有了基本的经验,他就可以去打破方法中硬性的约束了。

按图索骥是新手最好的学习方式,但本书并不是一本 ATDD 攻略。通过本书中的例子,我提出了两种 ATDD 的应用方式,并展示了其中参与者的思维过程。新手可以根据这些内容在自己的团队中开始尝试ATDD。在后面的部分,我提供了一些更深层次的学习资料。

本书最基本的想法来自于 Kent Beck 的《测试驱动开发》(Test-Driven Development:By Example)。Beck提供了两个测试驱动开发的真实例子,最后解释了背后的一些原则。它的目标就是成为一个入门级的 TDD 介绍,提供充足的学习资料供新手去学习——认为通过反思和实践,就可以掌握TDD。从某些角度来说,本书也是这样的。

术语表

本书中我会用到一些敏捷软件开发中的术语。考虑到并不是人人都了解敏捷软件开发,这里只提供一个术语的简单解释。

产品负责人(product owner)。敏捷方法Scrum定义了3种角色:开发团队、Scrum Master和产品负责人。产品负责人对团队所开发的产品是否成功负责。他需要为团队要实现的特性确定优先级,并且和其他利益相关人一起工作来获取上述特性。对于团队来说,他还是客户代表,负责决定功能的细节。他通过与其他利益相关人协商来确定这些细节。

迭代或Sprint。敏捷开发基于一种称为迭代的固定开发周期,在Scrum中称作Sprint。在这些短周期内,团队需要实现一个潜在可发布的增量产品。通常的迭代长度是1~4周。

用户故事(user story)。用户故事是让团队感到在一个迭代中能轻松完成的有限的功能集。这是一些很小的功能片段。通常团队会尽力在一个迭代中完成多个用户故事。业务代表或产品负责人负责定义这些故事。

任务看板(taskboard)。大多数敏捷团队在一个所有人都可见的白板上规划他们的工作。他们使用卡片说明当前正在进行的工作。任务看板通常有多个列,至少包括ToDo(待办)、Doing(进行中)和Done(完成)。

随着项目的进展,团队通过更新看板来反映进度。

故事卡(story card)。用户故事通常写在真实的卡片上,在迭代中,卡片会被贴在任务看板上。

站立会议,每日 Scrum 会议。团队成员每天至少更新一次他们在迭代中的进度。团队花15分钟在一起讨论如何完成迭代中剩余的任务。

产品需求列表(product backlog),Sprint任务列表(sprint backlog)。Scrum中的产品负责人在产品需求列表中记录所有的未完成的故事。一旦有新的需求,他会负责更新这个列表。当团队一起为下一次迭代做计划时,团队成员会为下一次迭代确定一个任务列表。这就是Sprint任务列表。从产品需求列表中选出的故事自然成为Sprint任务列表的一部分。Sprint任务列表通常在计划会议结束后被记录在任务看板上。

重构(refactoring)。重构是在保持功能不变的基础上,改变原代码的结构。通常我会在修改代码前重构代码。通过重构代码,可以使将来修改代码变得更容易。

测试驱动开发(TDD)。采用测试驱动开发,你首先需要写一个失败的测试,写足以使这个测试通过的代码即可(同时保证其他之前通过的测试依然通过),然后重构你的代码,为下一步做准备。TDD是一种设计方法,它帮助使用者编写更好的代码,因为这些代码默认就是可测试的。

持续集成(continuous integration,CI)。在持续集成中,你频繁集成改动的代码。一个构建服务器将构建整个分支,运行所有的单元测试和全部的验收测试,并且将此次构建的结果通知你的同事们。持续集成依赖于自动构建,如果当前分支的状态出现问题,它能帮助团队在第一时间发现,而不是在产品发布前一小时才发现。

如何阅读本书

本书在提供具体实践的同时,还提出了一些有用的原则。阅读本书可以用多种方式,根据自己的经验水平,你可以挑选任何一种。

你可以一页页地顺序阅读本书,你会更多地了解 Cucumber、行为驱动开发以及如何用 ATDD 工具测试网页。在第一部分中,第一个案例基于一个区分开发专家和测试专家的团队。在这里你会发现相互协作是最重要的成功因素。

在第二部分中,我会与你结对。通过结对,我们可以弥补任何缺失的测试或开发知识。我们会用一种很实际的方式利用 ATDD 来驱动我们的应用开发。我们会用到 FitNesse,一个基于 wiki 的验收测试框架。第二部分的实例是用 Java语言编写的。

在第三部分中,你会发现应用这种方法的一些入门的指导。我也会给出进阶读物的指引,并给出一些关于其他团队如何开始,什么对他们有效,以及什么对他们无效的经验。

你可以在附录中找到书中用到的两种工具和另外一种工具更详细的使用说明。如果你没有接触过Cucumber或FitNesse,你可能希望从这里开始看起。

高阶读者可以跳过前两部分,直接从第三部分我总结的原则看起。如果你希望稍后向你的同事提供一些背景知识,你可以参考第一部分和第二部分的内容。

你也可以先读前两个实例,然后回过头在你的工作中开始尝试应用它。当你遇到无法解决的问题时,再阅读第三部分中更多的内容。不过,我并不推荐以这种顺序阅读本书。

如果你已经在你的团队中开始了 ATDD 的尝试,也许你愿意更深入地研究一下第二部分。在那里,我解释了如何由实例驱动出领域代码。

这些是我能想到的一些阅读本书的方式。如果你喜欢我,或许你会想跟随案例自己编写一下书中的代码。我为所有的实例代码准备了一个GitHub代码仓库(repository)。这样我可以验收测试我自己的示例代码。如果你发现自己有什么问题,你也可以以这个作为参考。你可以在http://github.com/mgaertne/airport 找到第一部分的代码,第二部分的源代码在http://github.com/mgaertne/trafficlights。

[1].或者,为什么我要用特别的0/1排列,使它在你的电子设备上显示为“ATDD by Example”呢?

致谢

一个项目,就像写这本书,如果没有那么多人的支持和帮助是不可能成功的。首先,我要感谢Dale Emery,他对我的写作风格提供了很好的建议。作为一个母语非英语的作家,我非常感谢Dale给我的反馈。

特别要感谢 Kent Beck。2010年8月,我跟他探讨了用他写作Test-Driven Developmet:by Example的方式写一部关于ATDD的书。他还将我介绍给Addison-Wesley的Christo pher Guzikowski,后者为我出版本书提供了全面的支持。

一些人对我早期的书稿提供了反馈意见。在这里我要感谢Lisa Crispin、Matt Heusser、Elisabeth Hendrickson、Brett Schuchert、Gijko Adzic、George Dinwiddie、Kevin Bodie、Olaf Lewitz、Manuel Kublbock、Andreas Havenstein、Sebastian Sanitz、Meike Mertsch、Gregor Gramlick还有Stephan Kamper。

最后同样重要的是,我要感谢我的妻子 Jennifer 以及我们的孩子 Katrin 和Leon,他们支持我写完了这本书。我希望在未来几年能够补偿他们这段缺乏丈夫和父亲陪伴的时光。

第一部分 机场停车场

在这一部分,我们来看一个在线应用。通过GUI(图形用户界面)对网页进行自动化测试是当前比较成熟的技术之一,不过这种方法也有缺点。然而无论怎样,大多数从事在线应用开发的团队都能从这本书中找到一些如何驱动其测试的线索。

我们要做的是一个国际机场的停车费计算器。这个国际机场中有几种不同的停车场,而且停车费用随停车时长的不同也有所不同。

停车费计算器的业务规则太复杂,以至于无论团队怎样努力,最终可能也无法完成这个在线应用。团队成员觉得他们以前得到的需求是错的,所以这次他们决定用不同的方式,他们要和用户一起在“需求讨论会”里讨论这个应用。测试人员、开发人员和客户坐在一起讨论能描述停车费计算器业务规则的各种实例(example)。

与编程活动同时进行的是,测试人员使用 Ruby、Cucumber 配合 Selenium将这些实例自动化。在某些时刻他可能需要开发人员的帮助,在介绍里我们就不详述了。

你想知道测试人员Tony用什么工具编辑Cucumber的测试用例吗?当然是emacs,不是vi。[1]

第1章 停车费计算器讨论会

不久前,Major国际航空公司决定扩展其在互联网上的服务。具体而言,他们想让自己的网站为潜在旅客提供预先计算停车费的功能。旅客只要通过提交一个在线表单,系统就可以按其停车时长来计算停车费用。

以前Major国际航空公司做过这样一个表单,但旅客的反馈非常差。所以管理团队决定再重新做一个。

总结前面项目失败的经验教训,项目团队(包括资深开发人员Phyllis、开发人员Alex和测试人员Tony)决定采取一种新的方式。

上次做的时候,项目需求从头到尾一直在变,导致代码也是一个补丁接着一个补丁,最后发现一开始实现的东西就是错的。他们不想再重复这个过程。所以,这次团队要采用“需求讨论会”的方式来更好地收集停车费计算器的业务规则。为此,Phyllis和Tony特别邀请了Major国际航空公司停车场管理部门的经理Bill参加讨论会,Bill是停车费用的业务专家。

1.1 代客泊车

Phyllis:好吧,那我们讨论一下停车费计算器的需求。Bill,你能讲一下吗?

Bill:我们基本上有3种不同的停车位。有些是按小时计费,有些按天计费,有些设有每日或每周的收费上限。

Phyllis:这3种不同的停车位具体指什么?它们有名字吗?

Bill:代客泊车、临时停车位和常规车位。如果丢失小票,会被加收10块钱。

Phyllis:让我们集中看这3种类型。它们的区别是什么?

Bill:对于代客泊车,客人把车放在代客泊车区,得到一张取车的凭据。

Phyllis:能讲一下停车费用吗?

Bill:代客泊车每天18美元,5小时以内(含5小时)优惠6美元。

Phyllis:等一下,Bill。你的意思是即使30分钟我也要交12块钱,3个小时也是一样,5小时也是?但如果是5小时零1分钟,我就不得不交18美元,和12小时或24小时一样?

Bill:是的,完全正确。

Phyllis:那24小时零1分钟呢?是30美元还是36美元?

Bill:哦,那当然是36美元了。

Phyllis:那每周上限呢?代客泊车有吗?

Bill:没有,这基本就是代客泊车的全部了。

Tony:好,那让我把它们写下来作为实例。

Tony把讨论的实例记在表1-1中 ,标记为“代客泊车”。

Phyllis:这些实例对“代客泊车”有意义吗?

Bill:是的,这些实例概括了我们的谈话的内容。

1.2 临时停车

Phyllis:好的,那其他还有什么收费类型?你说过有3种不同的类型。

Bill:我们也为接送旅客的车辆提供临时停车位。

Phyllis:那是如何收费的?

Bill:第一小时收2美元,之后每半小时收1美元。

Phyllis:有任何边界吗?比如最长停车时间。

Bill:不,没有停车时长限制。不过我们每天最多收24美元。

Phyllis:就是说每天收费的上限是24美元?

Bill:对。

Phyllis:那第一天之后再一个小时是收2美元,还是以半小时为单位增长?

Bill:哦,一天零半个小时收25美元。

Phyllis:那每周上限呢?有吗?

Bill:没有,临时停车不会停一周,因为这对他们来说可能太贵了,而第三个选择对他们更有吸引力。

Phyllis:好的,你觉得这个表怎么样?

Tony已经写好了表1-2,拿给Bill和Phyllis看。

Bill:对,就是这样。

1.3 经济停车和长期停车

Phyllis:那现在我们需要计算的第三种停车费用是什么?

Bill:还有就是经济停车。这个车场离机场比较远,所以价格比较便宜。我们有摆渡车可以把旅客带到候机楼。

Phyllis:好,那有多便宜呢?

Bill:这个规则相对更复杂些。首先,停车费是每小时2美元。

Tony:每天都是吗?还是说周末的费用是不一样的?

Bill:不,是周几都无所谓。

Tony:那么,在经济停车场停30分钟或者60分钟都是2美元,对吗?

Bill:没错。

Phyllis:嗯,那听起来并不复杂。3个小时或许就是6美元,10小时估计是20美元。

Bill:对,又不对。我们每天收费的上限是9美元。也就是说第1个小时到第4个小时都是2美元/小时。第5个小时就达到每天9美元的上限了。然后到明天之前的其他小时就不再收费了。

Tony:那么,我们有0.5小时和1小时是2美元;3个小时6美元,4个小时8美元,5个小时9美元,6个小时9美元,24个小时9美元……

Bill:是的,这听起来很好。

Tony:好,那第二天会发生什么?我们是继续按2美元/小时累加,还是按9美元/天计算?

Bill:不,我们还是按2美元/小时累加,直到又到达9美元的上限。

Tony:那1天加0.5个小时是11美元,1天加5个小时是18美元。那我假设第三天、第四天、第五天也是一样的?

Bill:是的,但是有另外一个上限。就是每周最高收费 54 美元,基本上就是说第7天是免费的。

Tony:好的,我明白了。让我把这些总结一下。这是我根据我们的谈话创建的一个表格。

Tony给Bill和Phyllis看了表1-3,标记为“经济停车”。

Bill:对,看起来很好。

Phyllis:等一下,Bill。6 天零 1 小时是多少钱?累计成 56 美元,还是 54美元?

Bill:不,还是54美元,因为第7天是免费的。不过也许我们应该把这个实例也加上。

Tony:我已经加上了。

Phyllis:很好,那这就是关于停车场费用的全部了吧?

Bill:不,经济停车还有两个不同的情况:在车库里的长期停车,费用是每小时2美元,每天收费上限是12美元,而且也是第7天免费;不在车库里的长期停车,费用是每小时 2 美元,每天收费上限是10美元,同样第7天也免费。

Phyllis:那这两个表正确反映这些需求了吗?

Tony已经创建了表1-4和表1-5,让Bill和Phyllis看。

Bill:耶,这个很不错啊。

Phyllis:还有个事儿。对于这个24小时的实例,如果我们晚上11点到,待到第二天晚上11点离开,那这种跨两天的情况,是每天10美元,总计20美元吗?

Bill:不,这种只收10美元,因为总的停车时长是24小时。

Tony:那多天停车的情况也是这样处理的吗?

Bill:毫无疑问。所有的情况都不考虑日历天,只考虑总的停车时长的天数。

1.4 基本实例

Tony:现在,我们已经接近尾声了。对所有的实例,我们还有最后一步要做。我认为我已经理解了业务需求,但是我现在想减少实例的数量以便能反应出业务规则的本质。让我们最后一次重温这些表格,看看哪些实例可以而且应该被删除。

Bill:好的,让我们回头看一下。我想删除地面长期停车的一些实例。

Bill删去了一些地面长期停车的实例,详见表1-6。

Phyllis:这个3天的实例怎么样?我们已经覆盖了1天和6天的情况。我们可以把这个也删掉吗?

Tony:是的,可能吧。Bill你觉得呢?

Bill:嗯,把它删了吧。我们已经差不多覆盖所有的情况了,我认为把这个删掉也是安全的。

表1-7列出了清理之后的实例。

Bill:对于车库长期停车,我觉得可以删掉3天的实例。

Bill从表1-4里删掉了一些实例,创建了表1-8。

Bill:嗯,我们把经济停车的3小时实例去除吧,因为我们已经包含了4小时的情况。

Tony:同样我们也应该把3天的实例删掉。

BILL:是的,你说的对。

Bill又把经济停车的实例剪裁后放到表1-9中。

Bill:很好。临时停车的实例可以把1.5小时、2小时和12小时30分钟的实例删掉来精简一下。

Tony:等等,Bill。我想我们不应该删掉12小时30分钟的实例,它反映了每天24美元的收费上限。

Bill:哦,你说的对。我们把它放回去。

在表1-10中可以看出Bill删掉了临时停车表里的冗余实例。

Bill:最后,让我们看一下代客泊车的实例。我没看出来哪个实例需要删掉。

Tony:同意。这些实例已经代表了最基本的业务规则,就像你给我们解释的那样。

Phyllis:好,那我们好像已经搞定了停车场用户故事的范围。多谢 Bill 和Tony。

1.5 总结

在这一章里,我们看到了业务专家、开发人员和测试人员是如何协作,在会议中挖掘出软件需求并对其达成共识的。虽然开始时Tony并没有贡献太多新的想法,但是他通过把实例可视化帮助大家达成了共识。凭借Tony独特的测试领域知识,他的贡献主要集中在使用表格来抽象描述各种停车方案的实例。

在Tony拿出第一个实例表格后,大家对需求的讨论变得更有意义了。开发人员Phyllis在他们已识别出的经济停车场的实例中发现了一个bug。她还要求对6小时零1分钟的实例进行了确认。围绕这些表格化的实例,3人进行了一场从业务视角看软件预期行为的对话。

Bill也更加清晰地表达了他的想法。他可以直接查看并确认这些实例的表述是否正确。甚至对代客泊车的实例,Bill在看到Tony创建的第一个表格之前就可以说出停24小时零1分钟的费用是多少钱。在这个需求讨论会上,就在写下第一个实例的那一刻,团队的沟通取得了明显的进展。

在讨论会进行的过程中,每个人都可以贡献一己之力。在Tony用批判性的思维讨论边界情况时,Phyllis给出了她的观点。Phyllis在开始写代码之前就发现了经济停车实例中的一个bug。修正这个缺陷只用了几句话,而不是在走完整个开发流程之后。

Tony检查了Bill最初提供的需求。他从需求中提取了实例,又仔细查看了像24小时零1分钟这样的边界条件,以便立刻从Bill那里得到正确答案。假如这些问题中的某一个在这个迭代前没有被答复,那等团队意识到这个缺陷时,Bill也许会因出差在外而无法回答他们想问的问题。那时,团队可能会为了继续开发工作,会按自己的理解给出解释。可是,假如他们的解释是错的,这个问题就只能在为客户做演示时才能发现,甚至更晚——在产品部署到生产环境几个月后才被发现。

在讨论会上,作为业务专家,Bill做了所有关于这款软件的决策:哪些要保留,哪些可以删掉。当他提到代客泊车24小时零1分钟的费用是36美元时,对他来说是很明显的事,但是Tony的问题揭开了Bill的隐含假设。仰仗参与者的多元化,团队很容易对实现的方式达成一致的目标。

团队讨论出5个清理后的表格,详见表1-11至表1-15。他们很快会实现并对这些实例进行自动化。在敏捷模式下,这可能就是下一个迭代,或3个月之内的某一个迭代要做的事。既然团队成员已经对需求进行了讨论并理解了这些基本实例,那么,即使在以后的某个时间点去实现这些已达成共识的需求故事,其过程也会是清晰的。

第2章 代客泊车的测试自动化

团队决定从表 1-11 所示的停车场故事的代客泊车的实例开始做。大家决定使用Cucumber[2]来实现测试自动化。Cucumber使用Ruby语言将实例的数据表示和被测试系统粘合在一起。在 Cucumber 中,每个测试集合被称为一个特性(feature),每个特性由一个单独的文本文件来描述。

为了使用Cucumber来实现测试自动化,我们需要一组特性来记录测试数据,一些用来描述与被测应用交互的测试步骤定义,以及一套环境设置信息。

Tony脑海中的总体架构如图2-1所示。

最顶端的那些实例是在讨论会中确定的。现在,Tony开始将它们导入Cucumber。Cucumber 需要一些粘合代码以执行被测应用。粘合代码可以分为步骤定义、支持代码和第三方库(例如Selenium中驱动浏览器的代码库)。

Tony 计划将停车费计算器中的支持代码放入一个单独的库中,以便步骤定义中的粘合代码可以使用这个库。

自动化测试环境的建立需要使用Selenium[3]。自动测试代码通过Selenium可以驱动浏览器,使得自动测试代码可以与网页交互并且验证取值的正确性,例如停车费用的计算结果。团队建立了一个持续集成系统,并连接了一个Headless Selenium服务器[4]。这样测试代码就可以在构建中连接服务器了。

2.1 第一个测试用例

Tony 首先选了“30 分钟”这个用例。他开始在 Valet.feature 文件中描述这个代客泊车的特性。Tony 在开讨论会用的桌边完成了他的第一个测试(见程序清单2-1)。

现在,代客泊车特性有了第一个“30分钟”的测试。期望的停车费是12美元,就像在讨论会中确定的一样。在程序清单2-1所示的代码中,第1行描述了我们要测试的特性的名字。第2行是进一步的说明。Cucumber运行时会在控制台显示这个说明。Tony 通常在这里交代他的测试意图,以便后来的测试编写人员理解,当然,那个人很可能就是几个月后的他自己。

在第4行中,他把第一个测试命名为“计算半小时的代客泊车费用”。由于这个测试目标就是停车30分钟,这个名字取得很确切。第5~6行用到了关键字When和Then,以便描述测试的两个不同阶段。

关键字When描述了触发被测系统运转应该采取的动作。可能包括调用一个函数,或者按下某个按键。

车位类型以及停车时间是When关键字的参数。Cucumber将会解析这些参数,并将它们提供给系统,这样应用程序就可以计算停车费用了。

Then 关键字描述了系统执行应用程序之后测试的后置条件,任何期望的结果都应该写在这里。在这个例子里,Tony加入了停车费计算结果的检测。

现在,Tony 保存 Valet.feature 文件,然后通过命令 cucumber Valet.feature让Cucumber运行它。运行结果如程序清单2-2所示。结果首先输出了 Tony 刚才编辑的测试场景代码。在第 8 行中,运行结果提醒 Tony Cucumber试图运行一个场景,但是这个场景未定义。同样在第9行中,Cucumber指出有两个未定义的步骤。从第12行开始是一个提示,提醒Tony如何实现这些未定义的步骤。

遵照Cucumber的提示,Tony新建了一个名为Valet_steps.rb的文件,编写步骤定义,并将提示里的示例代码复制、粘贴到这个文件中。为了将测试数据和在被测系统上执行命令的粘合代码分开,Tony 将支持代码(步骤定义)放在一个新建的目录step_definitions下的一个文件中。

Tony在提示的代码桩(stub)上做了一些修改。这样在以后把第一个测试实例扩展到不同停车时长的时候会比较方便。修改结果在程序清单2-3中给出。

Cucumber 可以从测试的描述语句中分析出变量,解析停车时长和费用就用到了这个特性。pending也是Cucumber可识别的关键字,运行时会输出信息,说明这个测试当前是挂起的,很可能因为这个测试正在编写中。现在步骤定义已经就位,当Tony重新执行这个测试时,得到了输出如程序清单2-4所示。

为了能让被测系统运行,Tony还需要配置一下 Web 浏览器的驱动程序 Selenium。Selenium有一个服务器组件,还有一个客户端库,供支持代码驱动浏览器及浏览网页。Tony在单独的env.rb文件中编写了支持代码,这样支持代码就可以和操作被测系统的粘合代码分离开了。他把这个文件放入了一个新建的etc文件夹(文件目录结构见图2-2)。他需要的每样东西都列在程序清单2-5中。

这段支持代码使用了 Selenium 提供的库,启动一个浏览器,然后打开ParkCalc页面。当所有的测试运行完后,它还会关闭浏览器窗口。

文件env.rb同样需要一个库文件lib/parkcalc来进行停车费的计算。在开发测试的同时,Tony 将会逐步地完善这个库文件。文件的初始内容如程序清单2-6所示。

在初始化函数中,传入的 page_handle存入本地属性 page中,并打开了/parkcalc页面。这段初始化代码,完成了用传入的浏览器打开Web表单的功能。传入的浏览器正是在env.rb中配置的那个。

Tony开始分步开发他的测试。他需要完成的第一步是向Web接口填入停车时长参数。Tony 现在还不知道具体的实现细节,但是他已经看过手绘的页面布局的最终设计。When I park my car in the Valet Parking Lot for <duration>语句包含两个基本步骤。首先,选择正确的停车场。其次需要填入代表正确停车时长的值。Tony 打算根据自己的主观期望来处理这个特定的问题,他在Valet_steps.rb中写下了运行这个实例所需用到的API(见程序清单2-7)。

第一步对应程序清单2-7的第2行。Tony认为应该有个停车场的选择机制。根据实现细节,这可能意味着在文本框中输入字符串,或者从选择组合框中选中正确的停车场,又或者从一个下拉菜单中选择停车场。由于目前Tony对此还一无所知,他将这个具体实现的细节推迟到ParkCalcPage类被实现。

第二步就是程序清单2-7的第3行,它描述了停车时长通过某种方式填入了页面。同样,这也许意味着在输入框中输入一个时长字符串,也许有两个输入框分别对应停车起始时间和结束时间,然后根据输入计算出时长。由于具体的用户界面实现方式还没有敲定,Tony会等到ParkCalcPage类实现后再做决定。

为了表明这些测试步骤定义还未完成,Tony 在语句定义的最后加上了关键字 pending。这样一旦另外两个关键字实现了就能提醒未来的实现者修改步骤定义。

现在 Tony 为他期待实现的两个方法提供空的实现,通过这种方式来向ParkCalcPage类未来的实现者告知他对这个类接口的设计(见程序清单2-8)。

Tony 给 ParkCalcPage 添加了 select(parking_lot)和 enter_parking_duration(duration)方法。前一个以后用来选择停车场,后一个负责填入用户界面提供的停车时长。

Tony现在集中注意力处理他测试中的验证步骤。同准备步骤一样,Tony同样根据自己的主观期望来表述停车费的最终验证方法。程序清单2-9中展示了他对step_definitaions/Valet_steps.rb文件的修改。对ParkCalcPage类必要的修改见程序清单2-10。

Tony 完成了他目前能够驱动的第一个测试。现在他面临着如何继续的抉择。一方面,他可以继续完成代客泊车需求讨论会中确定的其他实例。另一方面,他也可以与开发人员结对去对实例进行自动化,以便驱动这个特性之后的开发。第三种选择是继续应付剩下的4种停车场,并且在Cucumber中写好它们各自的第一个测试。Tony决定和开发人员结对去实现第一个代客泊车功能并且对他的第一个测试进行自动化。这样,Tony可以获得他对目前已完成工作的及时反馈,稍后就能继续开发其他测试了。这么做还有一个好处,即 Tony 不会在实现开始之前加入太多无法通过的测试。

2.2 结对完成第一个测试

Tony和Alex开始结对写实现代码并对第一个测试进行自动化。Alex已经为网站做好了第一个布局,他向Tony介绍了他的想法。

Alex:嘿,Tony,你有兴趣看看停车费计算器的进展吗?

Tony:其实我想跟你结对儿,对第一个测试进行自动化。

Alex:哦,太好了,你从哪个开始的?

Tony:代客泊车,我花了30分钟引入了第一个测试,过来之前我刚把第一个挂起的实例提交了。

Alex:很好,我先给你看看我这边的完成情况。

Alex给Tony展示了他对页面布局的初始设计(见图2-3)。

2.2.1 初始化

Alex:我设计由下拉菜单展现不同的停车场。日期可以直接由输入框填入字符串,或者用日历控件选择日期。停入和离开的时间由文本输入,有一个单选按钮决定上午还是下午。当计算按钮按下之后就会显示估算的停车费。

Tony:看起来不错。看看这个,现在这些步骤定义还是挂起的,我们需要解决它们。

Alex:好的,这些看起来并不复杂。我们从下拉菜单选择停车场开始吧。这里我用的id是ParkingLot,所以从下拉菜单选择合适的值一步就可以做到,就像这样。

Alex实现了lib/parkcalc.rb文件中的select方法,如程序清单2-11所示。

Tony:好的,看起来很直观。我从id为 ParkingLot的元素上选择了传入参数代表的停车场。很好,那么输入停车时间呢?

Alex:这需要更多的思考,我们用Ruby里的hash(散列表)来实现吧。以后你就可以扩展这些停入和离开的日期和时间值,然后模拟你所需要的所有不同的时长。

Tony:那咱们应该怎么做呢?

Alex:基本上我们根据传入的时长字符串查找实际的停入日期、停入时间、上午还是下午、离开日期、离开时间以及离开是上午还是下午的值,并将它们填入页面。这6个值代表了我构造的表单中的6个项。不过我们先来定义这个散列表(hashmap)。

Alex在ParkCalcPage类的开头构造了一个时长的散列表(见程序清单2-12)。

Tony:两个@说明durationMap是一个类变量,对吧?

Alex:对的,现在我们要从散列表中得到我们关心的那6个值,我演示给你看如何从散列表中取值。

Alex开始实现enter_parking_duartion函数(见程序清单2-13)。

Alex:现在,让我们把这些值填入表单。我们从停入日期和时间开始。

如程序清单2-14所示,Alex继续扩充这个方法,填入停车的时长。

Tony:你能解释一下吗?我看不懂最后一行。

Alex:我解释一下这里的细节。首先我们以时长为键值从散列表中取出6个参数,然后我们填入相应的停入日期和时间。

Tony:对,这很直观。但是最后那行乱七八糟的东西是干什么的?

Alex:我用它来定位单选按钮。这是个xpath路径,表示单选按钮所在位置和它的值。它告诉驱动程序点击名为“StartingTimeAMPM”且值为输入参数的按钮。

Tony:我希望能把它放在别的地方。它包含太多技术细节,不适合放在这个相对抽象的函数里。

Alex:我想你是对的,Tony。我们把它记下来,先完成这个函数再说。离开日期和时间还没有呢。和停入时间类似。不过我们先看看现在能否正常运行。

Alex开始执行这个测试,Alex和Tony一起看着浏览器窗口被打开,进入停车场计算页面,填入停车场、停入日期和时间值。最后浏览器被关闭,运行结果显示出来。

Tony:看起来不错,我们继续处理时长吧。我们还需要填入离开日期和时间。

Alex:当然,代码和填入停入日期和时长类似。我们把代码复制、粘贴过来,然后改一下变量名。如果这能运行,我们再回过头来清理代码。

Alex继续向函数中添加代码,填入离开日期和时间(见程序清单2-15)。

Tony和Alex运行这些步骤,检查是否正确填入了离开日期和时间。

Alex:很好,能运行。我们现在来清理吧。这两部分代码看起来真的很类似。咱们把它放入一个单独函数中吧。

Alex和Tony写了一个新函数用来填写停入或离开的日期、时间,并逐步替换了原函数的实现。每做一点小的改动他们都会验证第一个测试还能正常运行。他们完成后,代码如程序清单2-16所示。

Alex:现在,我们来看一下如何提取那些怪异的xpath路径。

Tony:把它声明为一个常量表达式如何?

Alex:我也是这么想的。同时我还想把其他固定的字符串也放入变量里,这样将来我们就可以很容易地修改它们了。我们一个一个来提取吧。我们先来消除掉 xpath。我们需要给这个变量取个名字,你说叫什么好呢?

Tony:叫"amPMRadioButtonTemplate"如何?

Alex:我没意见。我们以后是不是可以把日期和时间字符串也放进timeTemplate和dateTemplate?

Tony:听起来不错。我们同样可以把前缀放入startingPrefix和leavingPrefix.

Alex:对的,我还想把停车场的ID也放入一个常量中去。

Tony:现在看起来就很好了。

程序清单2-17展示了ParkCalcPage类在Alex和Tony提取常量之后的最终版本。现在,初始化步骤就完成了。

2.2.2 检查结果

Tony:现在我们来检查一下输出。我们还没有点击你在表单里放的Calculate按钮,并且我们需要从页面中获取费用的计算结果。

Alex:当然,我们先把pending语句从步骤定义中去掉,这样我们的定义的Then部分就可以运行了。

Tony:噢!我差点就把这个忘了,要不然我得花半天时间才能搞清出了什么毛病。

Alex:这就是结对的好处,不是吗?

Tony:那么现在我们可以把 pending 语句从检查中去掉,然后我们来实现它。

Alex:好的。现在我们来看看怎么检查正确的价格。首先,我们需要点击计算按钮。我想把这作为第一步加入 parking_costs函数,放在返回费用之前。之后我们需要等待页面载入新的值。然后我们获取记录费用的元素并返回它。

Alex实现了parking_costs函数,见程序清单2-18。

Tony:那个常量10000是干什么的?

Alex:那是个超时值。驱动最多等待10秒钟,要么新页面成功载入,要么测试会失败。

Tony:现在我们来清理一下这些乱糟糟的代码。我建议同样把xpath提取到一个常量中。

Alex:首先,我们看一下是否能正确运行。我们来运行一下测试。

Alex和Tony启动了测试,看着它执行了停车场费用计算,最后在命令行中显示绿色,代表所有代码工作良好(见程序清单2-19)。

Tony:很好,测试通过了。在我们做任何修改之前,首先提交一下代码,这样将来我们可以回滚我们所做的任何修改,以防万一嘛。

Alex:好主意。

Alex和Tony把他们的成果提交到版本控制系统。

Alex:现在我们回到你的建议。想法很好,但是我想先把这个函数分成两步。第一个函数用来点击“提交”按钮并等待页面载入。第二个函数会取到算好的费用并返回。我写给你看我是什么意思。

Alex从最初的parking_costs函数提出两个函数(见程序清单2-20)。

Tony:现在我们来把定位费用元素的xpath路径移到常量中去。

Alex:既然要做这个,不如把“计算”按钮的名字也用一个有意义的变量名表示好了。

Tony:现在,最后再跑一遍测试,然后我们就可以把代码提交到源代码库里了。

Tony和Alex看着测试运行通过了。他们将文件提交到了代码库。程序清单2-21 展示了 step_definitions/Valet_steps.rb 的最终内容,程序清单 2-22 是lib/parkcalc.rb的最终内容。

2.3 表格化测试

现在,随着第一个实例的自动化,Tony 可以简单地重用第一个测试的步骤对剩下的经讨论会确定的实例进行自动化。作为第一步,他需要将Valet.feature里的场景转换为一个表格化的实例场景框架。为了完成这个目标,他把30分钟的时长用一个占位符<parking duration>代替,并且把期望的价格用占位符<parking costs>代替,并把这个场景标记为场景框架。Tony把实际数值放在场景框架下记录所有实例数据的表格里。Tony用占位符的名字作为表格项的名称。结果见程序清单2-23。

现在,Tony可以开始将讨论会上确定的实例逐一输入表格中了。Tony首先运行了测试,确保他仍能正常工作。他得到的输出如程序清单2-24所示。

现在他开始填入剩下的实例。最后Tony把讨论会上所有的实例都做成了表格化的测试(见程序清单2-25)。

为了执行这些测试,他需要用合适的值扩展 ParkCalcPage 类中的durationMap(见程序清单2-26)。

Tony执行了所有的测试并看到它们全部通过了。似乎Alex已经实现了代客泊车的所有功能。故事卡背面的验收条件为Alex提供了实现功能所需的信息。作为最后一步,Tony 将他修改过的所有文件提交到源代码库中,并且在团队的任务看板上将代客泊车的故事卡标记为已自动化且通过测试。这天下班前,Alex和Tony击掌庆祝了他们的成功。

2.4 总结

代客泊车实例的自动化工作结束了。我们看到Tony从Cucumber开始。他在一个文本文件里用自然语言写下了第一个实例。然后他开始根据自己掌握的专业技能和信息来对他的第一个实例进行自动化。当Tony发现他的自动化代码无法继续写下去的时候,他开始与测试自动化开发人员Alex结对。

Alex和Tony完成了ParkCalcPage驱动,它会在网页表单中填入停车场、开始和离开的日期和时间。在点击 Calculate 按钮之后,停车费的值将由一个函数返回。这样测试框架就可以检验实际值是否和期望值相符了。

当 Alex 和 Tony 开始结对后,他们都可以贡献出自己的专业技能。测试员Tony可以批判性地思考测试代码,同时自动化开发人员Alex可以帮助Tony解决测试自动化中的技术问题。通过结对工作,他们还可以帮助对方从不同的角度看问题。最后,Alex和Tony共同写出的代码自然是经过了代码审查的。代码被编写的同时有另一双眼睛在审查代码在基于团队的软件开发中是极其有价值的实践,对基于团队的测试自动化也是一样。

通过把第一个测试转化为场景框架,Tony 可以最直观地对剩余的代客泊车实例进行自动化。业务专家Bill可以在测试的输出中直接看到Phyllis、Tony和他在讨论会中确定的实例。

相关图书

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

相关文章

相关课程