2008-05-01

关于BDD,RSpec,翻译A NEW LOOK AT TEST-DRIVEN DEVELOPMEN

关键字: ruby rails tdd bdd
缘起:
最近开始应用BDD开发实践,刚刚开始我跟很多人一样,找不到北,用不来,在无数地方看到这篇介绍BDD的pdf文档,学习并了解BDD背后的基础知识中,就顺便翻下,我是个懒人...不懂的...难翻的...我统统放着不翻,"specification"这个词,按我自己理解翻译成"规范"有点词不达意,我更倾向于不翻,总之只要能够理解他是什么,我不在乎他应该翻译成什么.

A NEW LOOK AT TEST-DRIVEN DEVELOPMENT

Dave Astels
dastels@daveastels.com

The Problem
目前测试驱动开发(TDD)正如日中天,很多大公司耗费巨资让他们的程序员学习如何TDD.TDD也是活动聚会的热门话题...就象"敏捷"或其他的热门话题一样.已有很多关于TDD的专业书籍.其中有的甚至获得了Jolt大奖.所以一切看起来真不错,所有人都完理解TDD并从TDD中获得了巨大的好处,是么?

Fat Chance!
我接触的很多人中真正懂得TDD的人其实不多.这意味着很多人其实并没有从TDD上得到什么好处,出了什么问题?

The focus on testing
好吧...首先人们总是认为测试没什么大不了的.

确实,差不多是这样,and you end up with a nice low level regression suite... 但这个过程不会产生一些好的体验.所以为什么不让体验更好一点呢?为什么大家都没有做到让过程体验更好? 让我们从历史开始讲起吧.多年以前,我为Coad Letter写了一篇文章. 文中我给出了一些TDD的背景知识(是从Ron Jeffries哪里获得的):

"XP 的一条基本原则是测试所有可能break的情况,现在,不管怎样,XP的测试实践者演进出了测试驱动开发"

回到那个时候,他们讨论编写测试,测试这个词变成了他们的中心词,所以人们才会这样理解测试这个词!当他们说TestCases或者TestSuites,或者Tests时他们其实不是在说其他的东西...虽然不是所有,但是大多数xUnit测试框架都要求测试方法以test命名.新版本的JUnit(版本4)尽量不依赖命名约定或让测试继承TestCase,但是这些关键词均被保留或用annotation来作为测试的关键词.

实事是,当TDD完成进化后,我们看到的已经是一个完全不同的东西了... 不是在原来的基础上修订,原来的XP实践者们编写测试去测试一切可能break的东西,到他们先编写测试,进而演化到用一个小测试来描述我们的新功能,然后编码,然后写下一个小测试...周而复始.,,TDD不是我们追求的最终目标...只是过程中的里程碑

Units
当然,"unit"这个词成了矛盾的焦点.首先这是一个含糊的词,第二他暗示将代码从结构上分割(例如人们认为他们应该测试独立的method或者class).我们不应当这样理解'unit'...我们应该把他理解成行为的一个切面

想像一下'unit testing'引导我们按照代码的结构来分割并安排测试测试.比如按照类的文件一一对应

这个不是我们想要的...我们想要从行为上切分...我们希望在一个比典型的'单元测试'更加细粒度的层面上开展测试.就象我之前说到的TDD,我们应当把注意力集中到更小的,可以被分割的行为上...一个方法的一个小的切面,比如"当list是空的时候,调用对象的add()方法,list中应该只有一个东西".方法可以在各种上下文中被调用,通常伴随这明确的参数,返回明确的结果...我甚至想跑题去写一篇题为"为每个测试写一个断言"的blog.

所以,这正是要的.一个令人难以致信的好主意...把他封装到包中并改变人们对测试的看法.

The Result
为什么有这个问题? 让我们考虑一下人们通常是怎么思考测试的.

程序员经常思考"我不准备写所有的测试","这是简单代码,没必要测试","测试在浪费时间",或者"我已经作这件事很多次了(循环/数据 获取/功能 等等)"

项目经理经常这么想"等编码完成后我们测试","我们有测试专员",或者"目前我们不能在这个上面浪费时间"

所以随着人们多测试的看法,很容易得到一堆关于测试的缺点以及我们不做测试的理由...尤其是在时间已经项目压力下

So if it's not about testing, what's it about
这是解决以及在你作了一半之前在去思考你要做什么的问题.你写下一个需求,针对一个小的行为切面,用清晰的,没有岐义,可执行的形式描述.这并不难.这是在写测试么?不,这意味着你在写一个代码要作什么的需求.这意味着你在编写代码之前定义代码的行为.而且不需要占用太多时间.事实上最好的时机就是在你写代码之前作这一步,因为这个时候你有足够的信息可以让你作好这件事.类似于TDD最佳实践,小步前进...一次定义小的行为切面,然后实现它.

当你意识到这是在编写行为而不是在编写测试.你的思想已经升华了.你会突然间发现为每个类或着每个方法编写测试这个办法变得可笑.

Sapir-Whorf Hypothesis
从Wikipedia上摘抄的背景知识:

(略,请看pdf原文,或者到这里浏览)

我的目的是要向你证明你所使用的语言影响了你的思想...如果你想要改变你的想法,理解这一点可以帮助你改变你的语言.

So what to do?
首先停止思考术语"测试", Bob Martin很多年前就说"Specification, not Verification".Bob的意思是verification(众所周知的"测试")是关于确认(verifying)你的代码的功能正确,当你specification是指你定义(specifying)你的代码应该有什么样的功能才对.

使用一些xUnit工具让这件事情变得困难,因为他是以"测试"为中心的语言.所以我们需要一个新的框架来specifying行为.ThoughtWorks的Dan North正开始着手一项名为jBehave的项目来解决这个问题.而我和其他一些人为Ruby的行为specifying框架rSpec提交代码.我将向你展示更多rSpec的细节.

A Behaviour Specification Framework
行为specification看起来应该是什么样子?好吧,第一眼看上去很象从前的xUnit:
  • 可以工作
  • 任何人都熟悉他


一个最主要的区别是关键词的使用.用继承Context来替换原来的做法,继承TestCase.用'should'替换方法名前缀'test',这样给你带来一个好处,你不必受到命名模式带来的困扰,从而可以选择更合适的名字,替换调作检查用的断言(例如 assertEquals(expected, actual)) 你specify提交条件类似ShouldBeEqual(actual, expected).

在Smalltalk和Ruby中,把测试框架嵌入到类库中的做法显得更加自然些(顺便说下,这是Smalltalk的通常做法),你应该写一些类似于范例的东西,比如下面这些

(略...请看pdf原文档,我不会用JE的编辑器编辑表格)

What Now?
前面提到过, Dan North的jBehave项目创建一个jUnit替代方案为行为specification,你现在已经可以下载并体验了.

如果你使用Ruby,你可以现在就得到我们的rSpec框架并开始使用他.目前rSpec提供了下面这些方法来调用任何的对象...请注意...是任何对象:

(略...我发现pdf中提到的这些用法已经过时了...新版本已经用更加简单清晰的办法了)

所有的表达式都是可选的,如参数信息.跟xUnit一样,setup和teardown可以被overriding.他们用同样的方法作同样的事情.

所以行为specification看起来象什么?好的,这里是一个我的TDD书中的范例的rSpec版
require 'spec'
require 'movie'
require 'movie_list'
class EmptyMovieList < Spec::Context
  def setup
    @list = MovieList.new
  end
  def should_have_size_of_0
    @list.size.should_equal 0
  end
  def should_not_include_star_wars
    @list.should_not_include "Star Wars"
  end
end
class OneMovieList < Spec::Context
  def setup
    @list = MovieList.new
    star_wars = Movie.new "Star Wars"
    @list.add star_wars
  end
  def should_have_size_of_1
    @list.size.should_equal 1
  end
  def should_include_star_wars
    @list.should_include "Star Wars"
  end
end


Guidelines
你最关心什么,就用它来命名Context classes,比如EmptyMovieList和MovieList.他们只包含了specification方法直接关联到上下文.

用你关注的specification命名举例
should_have_size_of_0

Context class和表达式方法都应该容易读取,并准确的告诉你发生什么: EmptyMovieList.should_have_size_of_0.
想一下多么轻松容易的表达了specification内容

你的specification方法应该尽量简单,短小,并关注你正在作的东西.一个表达式...太棒了...他应该是你追求的圣杯.简单既是美/小的,简单的,内聚的,容易理解的类和方法比大的,冗长的,复杂的类或者方法更好.

Summary
  • 我用TDD是最大的问题是他倾向于影响我走不同的方向...错误的方向
  • 我们需要开始思考行为specifications,而不是验证测试
  • specifications的价值是让每个让我们考虑清楚每个行为,更少的依赖类测试或者方法测试,并最终得到一个好的,可执行的文档
  • 在TDD,任何人都不去改变名称的含义(不是我们希望的),我们需要新的名字来命名新的工作方式,Dan North给了我们BDD:Behavior Driven Development.


Acknowledgements
感谢Steven Baker,Gabriel Bauman和Aslak Hellesoy讨论这篇文章的第一版并开始写ruby的框架,让这个想法变成现实.谢谢Kay Pentecost,他的feedback中的好主意test-avoidance已经包含在这篇文章的草稿版中.
  • BDD_Intro.pdf.zip (126.2 KB)
  • 描述: 这是这篇文章的原版pdf,下载zip解压缩即可得到.
  • 下载次数: 14
评论
chenk85 2008-05-03
支持一下。
发表评论

您还没有登录,请登录后发表评论

lgn21st
搜索本博客
存档
最新评论