当前位置:文档之家› 帮你做到零bug的编程方法 - ATDD

帮你做到零bug的编程方法 - ATDD

帮你做到零bug的编程方法 - ATDD
帮你做到零bug的编程方法 - ATDD

ATDD的原则

传统的软件需求(Specification)存在一个问题:需要特别的努力才能做得好。然而一旦它们编写完成,很快就会因为各种原因(你能控制的,或无法控制的)而迅速过时。举个例子,如果一个竞争对手发布了一个新特性,你的需求要立刻响应以保持你的市场份额。由于这显然是一个非技术决策,所以至少需要一些有业务背景的人参与需求决策过程。

在确定需求过程中获得的意见越是多样化,你就越能看清楚整个应用的全景。通常来说,你至少需要从三个不同的视角考虑。首先,需要考虑业务视角。在敏捷团队中,这方面需求通常由客户代表、客户代理人或是Scrum中的产品负责人(Product Owner)来提出。

其次需要考虑技术视角。在传统的团队中,资深开发人员或技术负责人通常会站在这个视角。在敏捷团队中,你会希望讨论会中有一个参与实际开发工作并熟悉源代码的团队成员参加。

最后,需要有人在这两种视角中充当中间人。在传统项目中,你会发现业务分析人员会提供这种视角。一个富有经验的软件测试人员会带来同样的价值。

1 见识“三的力量”

可是为什么这三种不同的视角可以帮助我们确定产品的需求呢?设计软件系统包含很多对这两个方面的权衡:业务功能与技术限制。我们的软件代码中充满了对功能和限制所做的折衷。另一方面,我们的缺陷数据库中则充满了由明显决策失误而导致的问题。

这些决策中,有些在软件开发过程中很难改变,有些则很容易改正。通过尽早让业务功能和技术限制这两种不同的视角接触,我们可以尽早找到正确的决策平衡点。因而,可以将引入难以改正的bug和容易改正的bug 的概率降到最低。顺便我们也可以避免这两种极端情况中间的那些bug。

软件系统的功能分布在一个解决方案空间中[GW89]。这个解决方案空间中存在很多可以实现所需功能的可行设计方案。这就解释了为什么你可以带着性能或负载等特性的需要去探索解决方案空间。这些特性限制了解决方案空间中能满足用户期望的方案的数量。

另一方面,软件实现还存在技术方面的制约。这些制约同样限制了实现所有功能以及特性的设计方案数目。

将业务功能和特性与技术的制约尽早放在一起考虑,可以帮助参与者在解决方案空间中探索可能的设计。这是验收测试驱动开发方法可以成功的重要因素之一。并且,它证明了测试人员在这个探索过程中也能发挥作用,他们可以对软件在功能、特性以及制约方面提出问题和建议。

但是,怎么看待独立的测试团队能带来独特的视角这一观点呢?在过去数十年的测试培训中,我们学到的观点是:开发人员和测试人员应当基于需求文档这一共同的基础而完全独立的工作。这避免了在现代心理学中被称为共识偏见(confirmation bias)的现象。测试人员必须避免受到开发人员观点的影响,并且在应当在适当的时候对产品作出评价。一些团队将这条规则发挥到这样一种极致:开发人员与测试人员被完全禁止与对方交谈。

与开发人员一起描述软件需求并不会给测试人员带来先入为主的偏见。相反,开发人员、测试人员与业务专家应该一起捕捉软件需求。如果你认为这也会使测试人员产生偏见,那么你是不是该开始怀疑阅读需求文档也同样会使测试人员产生偏见?对于测试人员,与开发人员以及业务专家一起描述需求实例和参加需求讨论会的功用是一样的,这也是大多数测试人员在他们的职业生涯中都梦想能做到的。

事实上,在Gojko Adzic的书《实例化需求》(Specification by Example)[Adz11]中采访到的团队反映,由至少一个开发人员和一个测试人员提供的综合观点,会让我们在项目初期就更好地理解需求。就像我们在机场的实例中看到的一样,这种讨论通常会达到以下的效果,开发人员通过提出技术性的问题来澄清业务流程,同时测试人员提出的问题则增加了需求的可理解性,并且用表格这一更加可视化的方式记录了需求。

1

在迭代过程中,开发人员与测试人员基于对特性共同的基本理解开展工作。作为验收测试的实例则成为衡量团队进度的指标。

为了达到最好的结果,确认需求的过程应包含以下三种不同的角色:一个业务专家(例如Produce Owner,或驻厂的用户)、一个开发人员和一个测试人员。基于所包含的角色个数,Janet Gregory和Lisa Crispin把这叫做“三的力量”。

“三种解释原则”[Wei93,90页]提醒我们在对收到的信息下结论之前,至少要想到3种可能的情况。

如果我对收到的信息不能想到至少3种解释,就说明我对它的含义考虑的还不够全面。

“三的力量”可以帮助我们克服想象力的不足。让代表三种视角的人坐在一张桌子旁,充分理解特性或用户故事的可能性就会大大增加。这就是“三的力量”为何如此强大的原因。

“三的力量”不仅限于功能性需求。开发人员会注意到代码可能存在的性能问题,而要求澄清这方面的需求。测试人员通常会注意到可用性方面的问题。开发人员、测试人员和业务代表可以澄清对所有特性在质量方面的要求——有些时候这被称为非功能性需求。记住,要考虑到自动化测试无法覆盖的问题,并使用测试象限(见图9-2)来构建你的测试交响曲(见9.4节)。大多数的性能、负载以及稳定性的需求可以通过自动化测试来检测。

2 举办讨论会

如同我们在第一部分看到的,讨论会可以使整个团队对将要实现的功能获得一致的理解。你必须仔细考虑以下方面,才能组织一场让大家(包括不出席会议的)都满意的需求讨论会。

2 1 参加者

首先,你要确保自己选择了正确的参与者。对有些团队来说,这可能意味着包含整个开发团队以及来自不同公司的所有用户。这些团队通常都会举办一次比较大型的会议,但每个迭代都会开个小的碰头会。

另一方面,也有团队在讨论会中只包含三个角色的代表,就是只有三个人参加的小型会议。无论哪种情况,你都需要保证存在不同的视角。通常这意味着要包含熟悉产品业务方面的人。你同样需要有直接能接触到专业用户的人[Coc06]。

如同我在10.1节中提到的,你应当包含至少一个开发人员和一个测试人员。这个开发人员要熟悉代码。在需求讨论会中,开发人员会基于功能的技术实现提出问题。由于他们自身的特点,开发人员会考虑功能如何实现并且在他们的脑海中构建领域模型。讨论会能使他们对新功能点对应的业务领域取得一致的理解。

测试人员通过他们批判性的眼光,可以很快发现业务规则中的漏洞。测试人员在需求讨论会中发挥的另一个独特作用是:他们能够使用表格记录业务规则,使其变得清楚透明。无论他们是否使用过被称为“敏捷友好”的自动化测试框架,有经验的测试人员都知道如何使用决策表以及如何用表格形式表达复杂的业务规则。如果大家在需求讨论会中使用这样的表达方式,那么开发人员、测试人员和业务专家就可以在没有写任何代码之前就对讨论的功能点达成共识。

2 2 讨论会的目标

为了使需求讨论会能开得成功,你要知道谁是你的主要听众。虽然领域专家带来了关于新特性的必要知识,但是开发团队(开发人员和测试人员)才是你的主要听众。还要记住很重要的一点,客户和外部业务专家通常都很忙。如果你邀请他们参加会议,而会上你的开发人员们在激烈争论是否要使用最新的Web服务器,以及要使用哪种技术,那么下一次他们可能就不会再参加你的讨论会了。

你需要一个有经验的引导者使会议集中于讨论特性,而不是争论具体实现的技术问题。讨论会的目标是对业务规则取得一致的理解。你可以在业务代表离开之后再接着讨论所有的技术问题。在需求讨论会上花费的时间应该用来使团队获得共同的理解和愿景。

2 3 频率与时长

下面要讨论的问题是你应当多频繁地组织这样的讨论会。当然,这很大程度上取决于领域专家是否有时间。如果你需要联系一些业务代表,你会发现他们都很忙。另一方面,你可能有驻厂的客户,那你就可以经常向他请教一些问题。

你可以根据实际情况,每个月为需求讨论会预留一个时间段,或者在每个迭代开始前开一个简短的讨论会。

你可以根据与领域专家一起参加需求讨论会频率的高低,为会议预留不同的时间长度。如果你不经常举行需求讨论会,那么你可能希望每次多讨论一些特性,因此你需要多预留一些时间。另一方面,如果你既希望保持会议简短,又希望开会不那么频繁,那么你只能选择少讨论一些特性,或者提前做一些准备工作。

如果你希望少讨论一些特性,那么你需要根据最新确定的优先级,从代办事项列表中挑选要讨论的特性。应当首先讨论那些最有可能在下一个迭代中开发的特性。你可能需要在讨论中跳过一些对团队来说已经比较清楚的特性,例如登录功能或者网页上的某个注释信息。这会让你有更多时间讨论更复杂的特性中充满挑战的业务规则。

如果你想事先做一些准备,可以在讨论会之前研究一下代办事项列表,准备一些需要业务专家回答的问题。你可以在大多数Scrum团队都有的代办事项列表准备会(backlog grooming meeting)上收集一些开放性问题。也可以在讨论会前几天召集一个简短的会议讨论一下将要实现的特性。另一种收集关于业务规则的想法和开放性问题的方式是在会议中将当前的理解可视化,通过思维导图(mindmap)来收集开放性问题。但是你要记住,如果你在需求讨论会中打开投影仪,利用思维导图来收集答案,这样的会议就开始变成一种折磨。因为你在强迫所有人安静地坐着,看你用投影仪打出来的字。使用非高科技的工具,例如索引卡片、白板或者翻页纸版可以让你避免那些低效的会议,但你需要有人辅助你来记录讨论结果。

3 捕捉需求

需求通常都不是收集到的[Coh04]。客户与业务专家认为他们清楚地知道他们想要的是什么。但当他们看到最终成果后,你和你的团队就会发现他们需要的是完全不同的东西。

这就是为什么说使用“收集”需求这个隐喻并不恰当的原因。只有当它们明显地摆在那里,每个人都可以捡起来去实现所期望的软件时,才能说是收集需求。不幸的是,需求并不是躺在那里等你去捡的。大多数团队付出了很大的代价才明白这一点。

Mike Cohn建议采用一个不同的隐喻来取代“收集”。既然需求不是躺在那里等着我们去收集的,因此我们需要付出一些努力才能抓住它们。有一些技巧可以在不同层面帮我们做到这一点,就像使用不同网眼大小的渔网可以捕捞不同种类的鱼,而漏过其它种类的鱼一样。这个类比可以很好地用来解释“实例化需求”中的各种不同技巧。

在前面的例子中,我们见到过两种应用都很广泛的实例化需求实践。第一种是有一个独立的测试团队,可以在实现软件某个特性时与开发人员并行工作。在这种情况下,我们的渔网意味着在产品中加入任何新的特性之前获得对特性的一致理解。

停车场例子中,整个团队在一起捕捉需求。因为每个人对停车场都有一个基本的理解,所以团队成员通过提一些问题就可以对机场停车费用计算取得一致的理解。这些问题就是我们捕捉实例所用的工具。测试人员Tony很快就发现他可以通过记录实例来表达他对业务规则的理解。在他记录的同时,Phyllis和Bill可以观察,并讨论他们的“渔网”还漏掉了什么。

一段时间后,团队会将实例裁减到最少。在讨论中,这些实例可以帮助每个人理解究竟哪些是必不可少的。削减实例数量可以使渔网的网眼更粗大,使更多的鱼可以穿过。

结果就是捕捉需求的渔网可以漏过一些不是那么有趣的需求,就像在捕捉梭鱼的时候会让鲭鱼漏过一样。另一方面,渔网也可以保证你捕到鲤鱼和小梭鱼,如果你对它们更感兴趣的话。

3

在第二个例子中我们看到,多人协作的会议场景并不是捕捉需求的唯一机会。在交通信号灯的例子中,开始实现任何功能前我们挖掘了业务领域的很多额外信息。由于工作在代码库上的只有我们两个人,我们可以用另一种方式捕捉需求。我们使用一种被我称为即时(just-in-time)捕捉需求的方法。随着代码库不断扩大,我们可以在已有实例都实现后再考虑新需求。

这种模式不仅仅适用于只有两个人开发代码的团队,也适用于多人团队。虽然你可能对业务领域有些初步的理解,我发现,思考一些具体的实例来表述自己的理解对于更进一步地深入问题会有很大帮助。不仅如此,这个思考的过程会能让你看到代码中领域模型的不同实现方式和设计方案。

随着代码库的增长,我对问题域的理解也更深入。我更加了解这个应用以及它的使用目标。事实上,为了获得关于领域模型更充分的信息,我们推迟了代码中领域模型的设计。

有人可能会说:我们本可以在讨论一开始就提出这个设计,因为一个很有经验的设计者可以预见到代码可能向什么方向发展。某种程度上讲这是对的。每当我感觉到我们可能出了什么问题时,我们都会停下来回顾一下当前的代码。这种回顾帮助我们选择正确的渔网捕捉需求。就像稍后十字路口控制器的设计所展示的,我们可能发现我们抓到了所有的水母,而不是鲤鱼。回过头来看看这件事,并从中总结经验,这正是从经验中学习的有效方式。

回顾同样可以帮助你在捕捉需求时为渔网选择大小正确的网眼。大多数团队都是以简单的方式开始的[Adz11]。在使用这种方式几个月后,团队会发现这个过程的缺点。他们会回顾,并根据自己独特的情况来改进。

为了确定你捕捉需求所用渔网的正确粒度,你需要从某个方法开始,在采用这一方法的过程中收集经验,并回顾你所获取的经验从而学习并改进这种方法。如果你的团队经常性地回顾,那么你应该对学习并改进这一过程有一定的了解。

4 其它

随着时间流逝,你的测试集会增长。在某一时刻,你将面临运行所有测试耗时过长的问题。在一段时间内,你还能通过把测试分成不同部分或夜间运行来绕过这个问题。但最终你会发现,你不得不抛弃一些测试。90分钟运行时间似乎是个极限。将整个团队召集在一起,讨论一下如何能够更及时地从验收测试中获得反馈。

你的整体测试策略可能依然存在漏洞。参考一下四个测试象限,你是否覆盖了象限中每一个必须的点:是否会经常运行探索性测试?上一次邀请客户做可用性测试是什么时候?容量和性能测试结果如何?ATDD只能覆盖面向业务的象限,帮助团队前进。验收测试是很多团队遗漏了的基本部分,但你也不该因此就牺牲太多其他象限的内容。

当你自动化实例时,有几点需要注意。首先,确保你使用了正确的工具。让团队中的所有人参与决定,因为每个人都有可能会在将来的某一刻使用它。其次,你要清除团队中每个人使用此工具的障碍,并确保工具是为你的流程服务的,而不是反过来。

随着工具的障碍被消除,你们就应该可以在自动化上协同工作了。测试人员能从开发人员的设计知识中获益,开发人员能从测试人员的测试知识上获益。作为一个附带收益,这也有利于团队成员之间彼此心照不宣,帮助你们在为客户解决问题上达成共识。

由外向内开发也有助于这一点。如果应用领域代码反应了你们共同的理解,那么你可能需要让你的客户至少能读懂你的代码。如果你用验收测试来发现你所面对的领域,你可以朝着完全不同的方向来驱动应用、设计和构架。这有助于创建长期易于维护的应用。

成功运用ATDD

我先假设你之前没有使用过ATDD,并且工作在一个从某种意义上来说已经存在的产品上。你需要用验收测试翻新一些已经存在的部分功能。这也许意味着你需要同你的客户代表协商如何能腾出一些时间来做这件事。为了产出第一个可用的原型,你需要为工作设定一个固定的时间盒。曾经一个客户要求我们为一个基于SWT的应用创建一个原型。我并不熟悉SWT应用的驱动——我们选了SWTBot。我们用了半天时间一起结对研究如何将SWTBot与SWT应用程序连接起来。我们选了一个横跨整个系统的,看起来足够复杂但又有可能完成的流程作为第一个原型。

我们从一个很直接的方法开始。我们运行的第一个测试基本上是把所有东西都写在了一个很大的函数里。然后我们开始重构代码,将它变得模块化并且从长远来看更具有可维护性。在随后的Sprint里,我们又花了半天时间将它从JUnit中移植到了FitNesse里。这样我们就向验收测试安全网迈出了第一步。

5

先选取哪一部分开始做验收测试并没有什么太大的关系。你应当争取从最普通的业务场景开始。以前在一个公司,我们对业务实例做了风险分析,并决定从风险最高的实例和那些最常被执行到的实例开始。这确保我们迈出了正确的一步,而且,这第一批测试以及自动化代码也成为其他实例的基础。当你对自己所选择的方法越来越熟悉,越来越有经验时,你要确保能跟业务代表一起讨论需求。尝试找到一种方法,用你熟悉的结构记录实例。通过表格跟你的业务代表交流,并且向她介绍这些实例自动化之后会带来的好处。

记住要确保让正确的人在正确的时候参加你的需求讨论会。避免邀请太多人参加,但同时要确保必要的人都在桌旁。你同样需要选择正确的时机。如果使用Scrum,在Sprint的最后几天可能你的产品负责人正在忙于接受或拒绝团队的产出,这时可能并不是讨论下个Sprint需求的好时机——对于产品负责人和团队来说都是如此。

当你自动化了前几个实例之后,你需要努力将它们加入你的持续集成系统。只有当你的自动化测试能经常运行时,它们才可能有效果。大多数工具都提供常见的CI系统插件。如果没有,那么很可能也有其他的团队面临与你相同的问题。可以在官网或技术网站提问以获得帮助。也许工具的维护人员已经有办法将结果整合到你特定的CI系统中了。

随着自动化测试代码集不断增长,你一定要确保它们也有安全网覆盖。随着时间推移,你也许会改变测试的组织结构,以反映你对问题域的理解。曾经在一个公司,我们一开始通过实例来组织测试,稍后我们发现变化都来自于特定的费率模型。于是,我们围绕不同的费率模型而不是业务实例重组了我们的测试。不管开始时你用什么方式组织,在后来改变组织方式应该不是什么大问题。

同样要盯紧你的自动化代码集。确保为自动化测试添加单元测试。如果你使用了很多的关键字或者场景表格,那么代码将变得复杂,因为通常它们是难于添加单元测试的。

确保重构你的代码。不论你是否用这种方法从自动化代码中发掘领域对象,你都应该力图将领域概念封装在独立的对象中。对于网页,这也许意味着你要为每一个网页使用一个页面对象,你可能还需要处理其他的领域对象,如钱、账户、用户以及收据等。为这些概念提供独立的类,可以使你将来扩展时更加容易,从而也可使你避免陷入很多团队面临的自动化测试代码维护炼狱。

随着时间推移,当你发现最初的实现步骤可靠性有问题时,你会想重写最初的一些实例。不要吝惜时间,现在就去重写它们,在项目变得更复杂之前。简单的复制粘贴一下实例,改一些数据,然后检查相应的结果这种做法是很有诱惑力的。千万别这么做!相反,在加入新的实例时一定要考虑如何才能使将来再增加额外实例变得简单。也许你需要以表格方式记录你的测试,或者你需要在实例中增加一个新的抽象层。复制粘贴也许很简单,但要将它看作代码存在问题的征兆。

开始时,记得在一个固定的时间盒内尝试一些方法,反思你从中学到的经验,然后做出一些调整。你也许可以想象出很多更好的方法。试着将它们付诸实践,解决你在使用它们时遇到的问题。记录你是如何通过一步步小的改变最终实现飞跃的。我相信这就是成功应用某种方法的秘诀——不只是ATDD,也包括整个软件开发。

相关主题
文本预览
相关文档 最新文档