过去几个月间,互联网上关于测试先行还是测试居后、代码测试比率或者行为驱动开发(BDD)是否真的是测试驱动开发(TDD)的讨论进行得如火如荼。InfoQ 就此访问了行为驱动开发(BDD)和测试驱动开发领域知名的专家们,请他们对测试驱动开发(TDD),行为驱动开发(BDD)以及测试比率的运用发表各自的观点。(译注:此后测试驱动开发和行为驱动开发均用简称 TDD 和 BDD 代替。)
座谈会成员:
- J. B. Rainsberg ——顾问及测试驱动专家,关注他的博客: The Code Whisperer
- Dan North ——DRW Trading Group 的精益技术专员, 行为驱动开发(BDD)的定义者
- Gojko Adzic —— 顾问,《 Specification by Example 》及《 Bridging the Communication Gap 》作者
- Ron Jeffries ——极限编程(XP)及敏捷模式的独立顾问,曾指导过极限编程(XP)的项目
- Steve Freeman —— 敏捷讲师及顾问,《Growing Object Oriented Software, Guided by Tests》作者
问题列表:
- 对项目而言,你认为哪些标准会影响你做决定,是采用 TDD 还是 BDD 或是什么都不用呢?
- 现在,人们似乎普遍认为 TDD 等同于单元测试,而 BDD 则等同于验收测试(无论使用的是哪种工具)。你们认为这种说法是否正确?其他类型的测试,如构件测试与系统集成测试,又与 TDD/BDD 有何关联呢?
- 一直以来,TDD 被公认为是一种(代码)设计准则、测试准则或沟通工具。然而,在 TDD 方法中,作为这些准则和工具的目标是如何影响设计的?在测试方面,TDD 现在与未来的价值又是怎样的?
- 关于单元测试、集成测试以及验收测试比率的自动化以及相应的维护成本,有哪些准则可以告诉大家?
- 除了至关重要的系统,现在似乎比较统一的说法是,不能把 100% 的测试覆盖作为目标,那也是不实际的。你是否认为代码 / 测试比率能提升测试的专注程度和效率吗?
- TDD、BDD、验收测试驱动开发(ATDD),测试优先等等,对不同的人意义也不尽相同,并且容易让人搞混。我们是否需要一种通用的语言,能够描述我们的软件开发方法,并培养一种可达成共识的内容驱动的好实践呢?
- 关于这些主题有什么最后寄语?
InfoQ:对项目而言,你认为哪些标准会影响你做决定,是采用 TDD 还是 BDD 或是什么都不用呢?
JB:对这个话题做概述,我觉得有点不合适,莫不如让我来说明下,我在什么时候会用到 TDD 和 BDD,以及这么做的理由吧。
我第一次接触 TDD 是因为我试图寻找一种方法来减少代码中的错误(也叫代码缺陷或 Bug)。曾经,我在程序中引入的错误数量让我一度认为自己永远都无法完成编码——不管我怎么努力,都看不到任何希望。我猜测,如果那个时候,能自己测一下代码,就能找到许多简单、愚蠢的代码并自己修复掉。TDD 对我来说,并不只是为了不让别人觉得你写的代码有多糟糕,而是避免你错误地认为自己已经完成了编码,却留下一大堆 Bug。然而,TDD 帮我解决了这个问题,多年之后,我还意识到 TDD 不仅帮我避免了程序逻辑的错误,更帮我减少了程序设计中的问题。在我运用 BDD 之后,我发现 BDD 帮我减少了许多在选择特征与完成特征时的错误。日复一日,我逐渐明白代码的错误不仅让我费时费力,也让我无法按时完成编码工作。这个时候,我开始在项目中贯彻实施 TDD 与 BDD 的方法。
让我们再回到问题本身,我鼓励大家去思考为什么你们需要做 TDD,想想你的理由。不要限于典型的实施 TDD 与 BDD 的理由,例如:更好的设计、更少的程序缺陷、更有商业价值的产品特征以及更少的无用功。我鼓励人们多思考一下驱动你这样做的原因,这才是重中之重。我相信你会发现,一旦你采用了这两种方法,必然能按时完成分配的任务,因为这会让你更有条不紊,而不是到后来手忙脚乱。当然,我认为这些理由是因人而异的。
Dan:首先允许我给出一些定义。TDD 是一种编程技术,它引导程序员思考自己的代码是如何被其他代码所使用,从而避免由代码实现引起的“意外设计”(emergent design)。你首先要写一个测试来描述如何使用下一块代码,然后实现这块代码,并保证它通过测试。这项技术是需要编程技巧的,并且需要考虑何时进行合适的运用(我稍后会展开)。这样做有很多优点,编写测试帮助你理解行业知识并帮助你更好地去命名。一个测试可以发现理解上的差异(“你认为在这个用例中应该怎么做?”),当然,一整套自动测试可以帮助我们发现回归测试中的缺陷。
我并不认为我们必须决定是否在这个项目中采用 TDD,你几乎可以在任何一个项目中使用 TDD。然而,我建议程序员在采用 TDD 前思考一下,这样做是否有价值。不可否认的是,除了 TDD,我们还有其他很多方法可以用来做设计、对某个行业探索和建模以及减少回归测试的风险。有些项目,最有效的方法是逐一采用这些方法,而有些项目不是。不夸张地说,我认为 TDD 是每个程序员都应该了解,并知道如何去做的、很有用的方法。对于重构,我也持有相同的意见——你必须去了解这个方法,但你需要时间的磨练,去区分何时何地采用这种方法,或转投他法。
BDD 是一种开发方法,形式类似于极限编程(XP),但不能把它单单看成是 TDD 的一种实现方式。BDD 的作用是把利益关系人、交付团队等不同方面的项目相关人员集中到一起,形成共同的理解,共同的价值观以及共同的期望值。如果你没有碰到过这样的难题,那就可能是哪里出问题了。当然,我也是最近才渐渐地运用起这种方法,之前我都是与较小的团队一起工作,通常也能有比较迅速和通畅的利益关系人反馈。因此,由理解不同而造成影响的情况比较少见。通常,利益关系人会说:“这里你能帮我改一下吗?”或者“这不是我想要的,我来举个例给你。”
BDD 会从业务目标着手,将这些目标与产品特征和故事衔接起来。BDD 提供了如何确定你的验收标准的建议,以及你如何将这些标准落实到决定代码行为的自动测试上。如果你的项目决定采用 BDD 方法,那就必须从整个项目的层面上来考虑问题(尽管你可以在子项目中运用 BDD),而且必须拉上整个团队一起做。
我赞成以下说法:只要能够提交合格的代码,那么每个程序员或配对编程的人员就都有权利以他们所偏好的方式交付软件。如果他们打算使用 TDD,我们就应该支持他们这样做。如果他们打算做些其他的,只要团队成员没有问题,我们也可以允许。
Gojko:这取决于你如何定义 TDD 和 BDD。看起来,在过去几年间,TDD 的定义被局限在仅仅作为设计的单元测试。而 BDD 则变成以实例和业务导向的测试来驱动功能的、涵盖所有开发阶段的方法。根据 Brian Marick 在《 Agile Testing by Lisa Crispin and Janet Gregory 》中的敏捷测试象限的定义,TDD 应该在第一象限,而 BDD 在第二象限。我并不完全同意这点,但既然你的问题是什么是影响你采用 TDD 还是 BDD 的标准,我假设你对 TDD 的定义是排除在 BDD 与其他方法之外的。基于这点,我们分析以下三个方面:
- 这是个一次性项目吗?是否为了将技术的不确定性降到最低,从技术层面,帮助团队了解他们最终想要什么?在这种情况下,维护很多自动测试用例集合都是一种浪费,而且极有可能团队真正需要的只是一小部分相关联的简单用户用例。因为我们不清楚技术壁垒,所以编写技术测试可能会是个问题。我可能会选择写一些指导性的测试,而非教条式地为每个设计写一段单元测试。这符合 Steve Freeman 和 Nat Pryce 所著的有关《成长性面向对象软件》的大型系统测试。
- 项目的复杂度是否由技术所决定?我们是否认同我们所要完成的是技术上的,而非业务逻辑上的风险和不确定性?是否所有项目相关人员都是技术出身,能够读懂代码?例如:搭建 Web 框架的项目、数据库平台的项目、查询系统的项目、或是云部署平台的项目。众多开源项目都可以归为这几类。如果是这样,那么偏技术的 TDD 就比较适用了——我会用单元测试工具来驱动业务场景和技术设计。我可能还会在白板上写些例子以求得到普遍认同(BDD 的核心概念),但我不会浪费时间把这些例子录入到可执行用例或非技术自动工具(Cucumber)中了。
- 项目是否也有复杂的业务逻辑需要讨论,或者存在不清晰的业务需求,需要在不懂编程语言的成员之间沟通的情况?如果是,我将各个击破。先使用实例来探讨业务需求,确保大家对业务有共同认识。然后为这些实例建立规格说明书,并将这些实例录入到诸如 Cucumber、FitNesse 或类似的自动工具中去。最后,用单元级别的测试来驱动代码的设计。
Ron:我多希望在过去的半个世纪的开发生涯中,我能够在每个项目中都运用这两种方法。我并没有发明这两种方法,我只是最早接触的那几个人之一。TDD 和 BDD 让我确信,没有更好的方法了,这两种方法也让我的代码缺陷数量大大降低。并且,当我更好地了解到系统或产品该设计成什么样子时,我的设计就更得心应手了。
TDD 和 BDD 太难被超越了。
Steve:你需要为你的系统写测试吗?如果需要,为何不在代码实现前就写完呢?这样一来,你就能知道怎么把测试变得更简单。你也并不可能把所有的测试用例一下子都写完,何不边写代码边完成测试用例呢?
InfoQ:现在似乎有种普遍的认识,认为 TDD 等同于单元测试,而 BDD 则等同于验收测试(无论使用的是哪种工具)。你们认为这种说法是否正确?其他类型的测试例如构件测试与系统集成测试 又是如何与 TDD/BDD 关联的呢?
JB:我想从两个方面谈这个问题:TDD 对我的设计提供了反馈,而 BDD 则是对我们开发的产品理解提供了反馈。起初,我以 TDD 作为一种测试技术开始实施,慢慢地我把 TDD 作为设计手段,再后来把 TDD 作为一种深入了解产品设计原理的方式。而对于 BDD,我则一开始把它作为提醒我从真正终端客户和利益受益者的角度看待产品的一种方法,最终变成改进业务与技术人员之间沟通的一种技巧。你可以看到,虽然测试在其中扮演着配角,却举足轻重。
我并不把 TDD 和 BDD 当作测试技术,我认为测试策略与 TDD/BDD 是无关的。无论项目使用 TDD,还是 BDD,还是什么都不用,我都期望从微测试(microtesting)、系统测试和可用性测试的角度来考虑这个问题。
Dan:这真的是不幸的历史产物。事实上,TDD 和 BDD 都涵盖这两种测试,甚至更多。Kent Beck 在极限编程(此后在他的 TDD 书中)中说到——TDD 在不同等级粒度中均能适用,这也契合 Nat Pryce 和 Steve Freeman 在《成长性面向对象软件》所描述的。你撰写用户级别的功能测试与低级别的单元测试的目的是相同的,都是为了阐述你希望代码如何表现。TDD 的激励作用多过于实际意义:如果你只为了得到更多的自动测试覆盖率而撰写自动测试用例,那不是 TDD。类似地,你可以测试诸如并发、延迟、failover 或吞吐量等非功能性需求。
BDD 之中用户级别测试与代码级别测试的区别更加明显。用户级别的测试是基于自我澄清和自动化的场景。并且这些构建的步骤可以在其他场景中重用。代码级别的测试也叫做实例(或者规格 Spec,虽然我不倾向于这么叫),与 TDD 相比更接近人的思考方式。时过境迁,有不少不同类别的工具出现。比较有名的有:用户级别的跨语言工具 Cucumber,以及代码级别的工具 RSpec、NSpec 等。我的经验是,我倾向于使用团队喜欢的工具。比如,大部分的 BDD 源码,我用过时的 JUnit 与 JMock 的 Hamcrest matcher library 编写。最近,我用 Python 的 py.test 和 nodeunit 来写 node.js,这与 JUnit 风格类似,“BDD 风格”的框架在两者中均有体现。因此我的建议是,这只是代码,如何实现它由你决定。
Gojko:同样,这取决于你对 TDD,BDD 以及其他概念的定义。我对 Kent Beck 著作的理解是用户测试与单元测试属于 TDD 范畴。而我对 Nat Pryce 和 Steve Freeman 的《成长性面向对象软件》中的理解是 TDD 包括系统测试,构件测试以及单元测试。我对规格说明书的解释是好的文档会将业务概念拆分,并自动化,以求风险得到控制——如果大部分的风险来自于一个实例,我们则需要验证实例的 Java 方法;如果风险来自系统,我们则需要对系统进行 30 次的 Web 服务运行和 100 次数据库运行。
Ron:BDD 起源于 TDD 的另一种描述。而现在,在 Chris Matts 和 Liz Keogh 手中,BDD 演化成实现产品特征描述与验收测试的方法,这也是我所理解的 BDD 描述。
而其他形式的测试,当然也很重要。我特别指出,我们需要把用户体验测试加入到你的测试列表中。关于构件测试和系统集成测试,敏捷项目的最佳方式是使用持续集成(Continuous Integration)。这样,所有分散的构件就能关联起来,集成的系统所能承受的测试也随之增加。通常,这些分类很模糊。我们针对不同事件,在不同的间隔运行不同的测试集,比如:新的类库或构件,或新构建的版本。这样的测试集包含了用以描述单元测试、验收测试等所有的测试用例。 测试的精髓在于,分清什么需要测试,尽量在创建或有变化的时候就进行测试。这就是我们防止代码缺陷和及时发现代码缺陷的秘诀。
Steve:我可不会做这样的类比,很明显,这是基于对 TDD 的错误理解。TDD 中最基本的问题是“我怎么知道这样是可行的?”——所有的业务和组织都可以这样去考虑问题。
我并不认为将测试细分成筒仓(silo)会有多大帮助,相反顺畅的测试给团队带来自信,相信系统能够正常运行。
InfoQ:一直以来,TDD 被公认为是一种(代码)设计准则、测试准则或沟通工具。然而,在 TDD 方法中,作为这些准则和工具的目标会如何影响设计?在测试方面,TDD 现在与未来的价值又是怎样的?
JB:我认为这很大程度上取决于方法的实践者。当我实践 TDD 的时候,发现最有价值的部分是测试规范这块,这可能是因为我期望在这个方面有所提高。而只有当我的错误数大幅度地减少时,我才注意到 TDD 是如何帮助和指导自己改进设计。在你实践 TDD 的时候,所有这些都指向对测试的当前价值的个人感受:你可能期望从其他测试中获益。
我觉得测试的当前价值远比未来价值要高得多。虽然从未这样试过,但我曾经打算在几个月后把测试都扔掉,仅当我需要改进某些东西的时候才去重写测试。
我从未在不是我编写的测试上获益,我也不认为这会对我所工作的项目有什么样的帮助,或是对 TDD 实践者的基本规范有什么样的贡献。对此我有所顾虑,就好象合同工走进要装修的房间,然后对之前的装修出言不逊。
我曾经宣称, TDD 风格的测试会起到变更探测器的作用(引用 Cem Kaner 的术语),用来减少代码变更的代价。我也听到过有人像我这么宣称。尽管没有仔细地度量过,但的确见识过 TDD 所带来的益处。我甚至听说有人宣称,这些测试可以为从未了解系统和 API 的人,讲述清楚其中的内容。对我而言,这些益处仍旧是理想化和理论上的。
Dan:TDD 是一种设计规范,每样东西都有两面性。在 “测试驱动”这个词组中,“测试”这个词很不幸。 你所写的用来描述行为的实例并不是测试,你所写的代码也只是简单的实例。这些实例只有当与类似持续集成等实践联系在一起时,才成为一套的回归测试。但这些无法代替测试的需求,特别是代替类似 Brian Marick 和 James Marcus Bach 所倡导的有技巧的、直接的探索性测试。TDD 测试的另一个特性就是它的决定性。在回归测试中,这是一个优势,但在发现阴暗角落方面(译注:指不易发现或重现的 Bug/Defect)做得却不怎样。随机化的测试技巧能够帮复杂的系统找出许多细微之处,然后你就可以利用 TDD 逐个解决。Haskell 和 Scala 的 QuickCheck 工具就是个很好的例子。
关于沟通,这是 TDD 的主要目的之一。特别是在你需要向其他程序员讲述你的代码意向的时候。在文章《 Introducing BDD 》中,我描述了有含义的命名方式对测试起到了多么大的帮助。否则,你就无法得知你的测试用例失败时所揭示的真相。你必须能够像读故事一样地去理解 TDD 测试用例,而出色的测试命名则决定了功能文档的易用程度。
Gojko:我认为答案总是位于这些因素的平衡点上。为了将 TDD 作为一种规范,我们必须找到一种方法来完成所有事情。好的单元测试,能够指引设计。但也必须能够帮助我们缓解关键技术难题带来的风险,并且告诉人们设计的代码应该怎样表现。
Ron:TDD 会用到测试,但不仅仅是测试而已。它是我们开发系统的方式,是所有测试和程序的骨架。TDD 以及其他相关实践让我们逐步地、一个特征接一个特征地开发系统。从始至终,它保证了代码的活性,以及可塑性。这让我们更清楚地了解我们究竟完成了什么,也让产品负责人或管理者清楚下一步要做什么,不论是通过揭露代码满是缺陷,或者设计是错误的,或者我们不能太快地改进。这也极大地减少了在项目最后阶段才了解到坏消息的可能性。
我不认为这些目标是“独立的”。好的软件开发,需要整合很多想法,也需要我们平衡很多目标。我们并不想放弃这些,相反,我们希望能够找到一种方法服务于所有的目标。而这一切,让开发产品变得更快捷。
Steve:当发觉沟通决定着其他方面的结果时,我必须强调所有级别测试中的沟通因素。例如:如果我致力于把一个测试用例写得可读性很高,这真的也能帮我发现对象引用的不恰当。 我见过很多团队陷入过维护性很差的测试用例的泥塘,而从拖累了整个进程。特别是在新的理解或概念出现的时候,你必须像对待生产代码一样(甚至超过)谨慎地对待测试代码。
InfoQ:关于单元测试、集成测试以及验收测试比率的自动化以及相应的维护成本,有哪些准则可以告诉大家?
JB:在团队实践 TDD 一到两年的时候,我不停地接到团队的消息,向我倾诉测试成本与收益的不平衡。每次这种情况发生,往往是因为团队尝试用较大的测试集(集成测试,系统测试以及端到端测试)去检验较小的事情(独立对象的具体行为)。这往往会导致更大的测试套件,更频繁地运行测试套件(一个失败意味着 23 个测试失败),也会降低程序员维护测试用例的信心和兴趣。这样的测试,反而会给项目带来负面影响。
在这种情况下,我建议为微行为编写微测试,然后合并微测试。契约式测试是通过连接相邻层(adjoining layer)的接口,来检查这个层,不会再深入。这就意味着从集成测试及系统测试转移到检查我所指的“基本对错”上——例如:在无限的时间与空间的条件下,这个对象是否能得到正确的计算结果?我所说的“集成测试是骗局”就是这个意思。
尽管对于不同的项目,不同的团队,要具体事件具体分析,但我还是强烈地建议程序员们从集成测试和系统测试转向微测试。
Dan:我不认为对于比率的建议会行之有效,对我而言,这甚至会有风险。如果某个错误出现的可能性较高,或某个错误的影响较大,我会花更多的时间去解决。举例说明,我曾希望以测试驱动的方式编写转换数据的代码,因为我知道把转换数据搞糟是多么容易,而发现错误又是多么的困难。类似地,如果我在为系统的外围交互模块写代码,我会非常非常小心需要传送和接受的数据。一旦发现 Bug,我就会编写一段测试用例来隔开这个 Bug,并且用测试驱动的方式去修复它。其他时候,我会用 REPL(一种命令行接口的语言)来实践,并找出 Bug。
Gojko:我想这个问题过于宽泛,没有一个具体的项目,我没法给你答案。
Ron:比起“把这些事情做起来”,我并没有更好的答案。运用 TDD 及相关的实践方法进行编码,花费的成本会更少,结果也会更好。有些人或团队认为,如果他们运用 TDD,搭上的时间会更多,这也是解释得通。也许会存在一些真实的开发情况,但我并没有找到。通常来说,这些人比较简单,也并不喜欢 TDD。这样做的后果是,他们认为自己会很快速,但得到的只是不停增长的缺陷数。这些缺陷必须消除,却使设计变成恶梦,反过来又增加了缺陷,使缺陷不易被发现和修复,从而拖慢整个进程,让进程变得异常困难,造成恶性循环。往往在项目最后的几周,他们最后只能收到坏消息。
这就是“死亡行军”(译注:越做错越多,越无法收拾)。当然,有些人或产品也能侥幸“活”下来。但遗憾的是,花如此大的时间和精力“活”下来,会让团队以为,所有项目都必须这样完成。(译注:感触颇深,同时有过 3 个项目,我带 2 个,另一个 PM 带一个,我的项目成员几乎不用在 UAT 前加班,氛围也非常好;另个项目天天加班,士气低落,民不聊生)。这才是大错特错!在有更好、更简单的选择的时候,他们几乎将自己逼进绝路。
Steve:我没法给出答案,除非你已经为相同的团队搭建了可辨识的系统。敏捷方法的基本要领就是对应已发生的情况。同样重要的一点是这些比例会随着项目进程而改变。
InfoQ:除了至关重要的系统,现在似乎比较统一的说法是 100% 的测试覆盖不能作为一个目标,也是不实际的。你是否认为代码 / 测试比率能提升测试的注意力和效率吗?
JB:正相反,如果组织重点关注在这些目标,那么就无形中创造出灾难的、会受到炮轰的“成熟模式”。你懂的:级别 1 表示“我们写测试”;级别 2 表示“我们为所有新代码写测试”;级别 3 表示“我们对系统做 50% 的覆盖”;我假设级别 5 表示“我测试故我在”。我认为这是没有意义的事情,我可以做这些,但结果还是交付了垃圾的产品。我觉得这是对我所教和我所崇尚的实践的一种嘲讽。
当我有我自己的“网络瞬间(Network moment)”时,我开始关心起这些事情了——你知道的,“我像个疯子,我再不会接受这些!”我尝试让人们学会有自己的“网络瞬间”,然后给他们建议如何去解决问题。我相信比起目标式的测试覆盖率,这更有效。
Dan:我认为教条式的代码测试比率恰巧有着相反的作用。这意味着所有的代码都是同等重要,具有相同的风险,这样的想法是错误的。相反,我提倡对不同的代码,采用不同关注程度及审查力度的测试。任何企图达到代码统一的测试覆盖率的机会成本都是疯狂的,特别在用户接口测试方面。把时间和精力放到改进需要重点关注的代码的质量上,会更加行之有效,立竿见影。
Gojko:只关注代码覆盖率很可笑。关注在 10% 的风险最高的代码比关注 99% 可忽略风险的代码,收益要多得多。我认为风险覆盖比起测试覆盖要重要得多。我偏好使用属性构架能力矩阵(Attribute Component Capability Matrix),然后决定什么需要覆盖及怎样去覆盖。(详见 James Whittaker 的《How Google Tests Software》一书——译注:好书一本!)
Ron:测试覆盖率永远都不该成为我们的目标。如果我们的测试很棒,那么我们势必能找到缺陷,这是显而易见的。那缺陷还会在哪里呢?在我们没有测的地方。因此覆盖率并不需要做得美好,只要“够用”就足够好了。我们需要做两件事情:
首先,我们需要不停地提升我们的测试技巧,那样我们测不到的地方就会越来越少。如果我们仅仅做 TDD 的教条是没有意义的——“为得到失败而写测试(译注:为了找到错误而拼命地写更多的测试)”。我们会自然而然地得到完整的代码覆盖,以及很好的路径覆盖。
第二,我强烈建议团队分析测试覆盖和这类信息,这样才能更好地决定什么需要改进。人无完人,但我们必须警觉,如果发现了缺陷,那么我们就需要回顾所发生的情况,补充漏掉的测试用例,保证将来不再发生。
Steve:我还是认为,在数据和划分不清楚之前,这是评价会有失精准。代码覆盖率有用,但作为外部的、过分强调的目标,也会影响团队应有的关注程度。
InfoQ:TDD、BDD、验收测试驱动开发(ATDD),测试优先等等这些,对不同的人都意味着不同的东西,也容易让人搞浑。我们是否需要一种通用的语言,用以描述我们的软件开发方法并培养一种可达成共识的内容驱动的好实践呢?
JB:不,我不这样认为。我想对每个实践,我们都有足够多的术语来描述。我发现,当我停止担心如何去定义它们、去分享我是如何理解、并鼓励人们分享他们的理解的时候,反而会有意想不到的收获。我还记得,我最有影响力的一次关于 TDD 和 BDD 的定义、意义和目的的讨论,发生在临晨 4 点半的一家旅馆的房间里。那次讨论,我言辞激励地与人争论 Dan North 和 Chris Matts。如果我们只是推动大家在社区网站上发布某些定义集,而抵制形式上更生动的、有点疯狂的、激烈的辩论,这将是非常遗憾的事情。
Dan:这只是我们对这块领域发展的理解的一个征兆。我起初建议把 BDD 用作辅助传授 TDD 的目的。我喜欢 Gojko Adzic 的名言“将规格说明书实例化”,只因它很清晰,没有歧义。很长一段时间中,我在“测试”、“实例”和“规格说明书”这些专用词汇中挣扎,我无法做出我的选择。有个短语“通用语言”本身就是有误导。“通用”指的是在边界之内的世界。换句话说,我们依据内容对同样的事物做出不同的描述。某人的测试是另一个实例,也是另一个规格说明书。这关乎你是否能将你的意图很好地传达到你的内容中。
Gojko:我尝试用实例来定义规格说明书,将其变得更清晰、有内容边界并把范围缩小,就是为了避免 TDD、BDD、ATDD 或其他概念的混乱。我认为这个命名对实践的探索很有意义;对我们从业务角度确定要开发的系统并运用实例和搭建在线文档作为支持,也很有意义。某些建议和实践对此是有用的,有别于当我们用技术测试来驱动设计时所用的那些实践与建议,他们有自身的价值。
我不喜欢 ATDD 或验收 TDD 这个命名,因为这给人们一种错误的印象,让大家觉得是因为关注在错误的事情上所以才造成失败。我希望人们不要这样思考,但不幸的是近期出版的书籍已经使用了这个名称。
我理解的 BDD 是,包括了很多不仅仅是规格的实例,并以单元测试驱动技术设计。例如,我考虑特征注入(Feature Injection)、拉动需求、外围设计、定义商业价值的模式以及诸如此类的 BDD 事件。这些才是 Liz Keogh 所做的激动人心的事情。当然,除了这些实例化的规格说明书或单元测试,还是有很多其他事情可以做。例如,效果映射(Effect Mapping)是又一项令人激动的、全新的计划与路演的技术。这与整个 BDD 的系统价值完美地符合,将测试驱动拔高到业务对象的层面,并对任何形式的自动化都没有关联,也无需去做。
Ron:好吧,我认为这是人类沟通的方式。这个世界上并不存在一种每个人听到都能理解的、清晰的语言。在我们边学边成长的商业世界,差异是不可避免的。在我眼中,最重要的差异是人们花很少或不花力气去理解这是些什么东西。相反,他们要么没有理解就开始诋毁这些建议;要么就是没有真正地去运用这些建议或没有理解地去运用这些建议,只是宣称他们在做这些。
这样做有两个严重的后果:第一,许多项目和个人不去做他们可以做的。这将导致人员对工作的不满、失败的项目以及糟糕的结果。第二,误解通常会减缓大家对这些好建议的运用。
Steve:当我们拥有完美的准则时,就能把它归纳成术语 我认为现在讨论什么技术该运用在什么地方还为时过早。我还认为不同的“领域“该有更宽泛的范围,用来获知他们的不同,但不至于弄个底朝天。
InfoQ:关于这些主题有什么最后寄语?
JB:没什么特别的。多多地实践那些技术吧,因为你希望它们能指导你改进工作。去做吧,因为这能给你“个人成功”。去做吧,只因它能帮助你更好地享受你的工作。总之,无论什么原因,去做吧!
Dan:我在这里讲的每个主题,今后我将分享更多我的感受。
Ron:尽管我会在项目中运用其他方法,但在半个世纪里,我所用的所有方法中,这些是我见过最好的方法,虽然我不会强迫每个人都去使用。
然而,我将做的是,我会建议关心这方面专业的每个人都能学习如何去运用这些技术……到一个“很好的程度”,然后拥有能够决定何时、何处使用它们的能力。在能够对某种技术进行客观的评测前,你不该去回避这种技术。
因此我将要做的是,展示给大家我所要做的软件开发,让大家放心、大胆地去尝试这些技术。并且能够让人们对此类技术有较好的口碑,愿意花足够长的时间去做出一个好的决定。
对我而言,好的决定是指能够在正确的时间正确地使用这些实践。我希望大家能发现这些技术的价值,并从中获益。
Steve:较常见的是,我所看到的关于 TDD 的主要问题,都不是测试难题,而是基本设计技巧的缺憾;人们之所以对测试比较纠结,是因为代码有着错误的结构。类似地,我看到过代码不能表现清楚。我越来越觉得,程序员的面试应该包括一个测试预选者编写一段可读性强的段落的测验。
关于座谈会成员
J. B. Rainsberger帮助许多软件公司更好地服务他们的客户,提升服务的质量( jbrains.ca )。长久以来,他学习如何写有价值的软件,克服众多自身的不足,最终找到了自己想要的生活。他的足迹遍布全球,边走边分享他的所学,希望借此帮助别人也能得到他们想要的生活与工作。尽管最近两年他经常在欧洲出差,他和他的夫人 Sarah 及三只猫,住在加拿大的亚特兰大。J.B 的博客是 The Code Whisperer 。
Dan North自己编写软件并以敏捷及精益的方法影响着自己的组织。他崇尚以人为本,编写简单、实用的软件。他认为团队面临的主要问题就是沟通,这就是为何他强调“用对文字”,以及他为何如此奉行 BDD,沟通以及人们如何学习。自从 1991 年开始,他就在 IT 行业工作,偶尔他会在 dannorth.net 写自己的博客。
Gojko Adzic是有战略性眼光的软件交付顾问,为致力于改进软件产品与流程的质量的,有野心的团队服务。他在敏捷与精益质量改进领域有着专长,特别是敏捷测试,以及实例化的规格和行为驱动开发。Gojko 是领先的软件开发和测试协商会的积极分子,他还运营着 UK agile testing user group 。在过去的 11 年中,他曾做过程序员、架构师、技术总监及顾问。他做的项目涉及的领域有金融与能源交易平台、移动平台、电子商务应用,在线游戏及复杂的配置管理系统。他是《 Specification by Example 》、《 Bridging the Communication Gap 》、《 Test Driven .NET Development with FitNesse 》和《 The Secret Ninja Cucumber Scrolls 》的作者。
Ron Jeffries是极限编程和敏捷方法( XProgramming.com )的独立顾问。他有着非常丰富的软件开发经验,甚至比某些人的岁数都大。Ron 曾经是最初极限编程项目的现场教练,也是《Extreme Programming Adventures in C#》和《Extreme Programming Installed》的作者以及《 Object Mentor’s popular XP Immersion course》的合著者。
Steve Freeman《成长性面向对象软件,测试引导》(Addison-Wesley 出版社)一书的作者,英国敏捷软件开发的先驱。他曾为不同的机构开发过软件,从小型外包商到跨国投资银行。Steve 为全世界的开发团队做培训和顾问。此前,他曾服务于研究所和软件院,并获得剑桥博士学位。他为 IBM 写过 shrink-wrap 应用,并在伦敦大学学院任教。Steve 是国际工业协会的演讲者和组织者,第一届伦敦 XpDay 的主席。
查看英文原文: Virtual Panel: Code-to-Test Ratios, TDD and BDD
感谢侯伯薇对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。
评论