简单而言,BDD是一系列基于TDD的工具和方法集发展而来的开发模式,一般不认为是一种新的开发模式,而是作为TDD的补充。因此,首先对TDD的概念进行进行。
测试驱动开发(TDD)
TDD模式采取的是迭代式的开发流程。软件的每个功能特性的开发都是从编写一个针对性的测试开始。一开始测试无法通过,然后开发人员通过以下步骤进行迭代:
1.编写测试,观察是否未通过;
2.实现早期版本,以便通过测试;
3.如果需要,重构部分代码。继续编写其他测试。
简单示例(基于Ruby)
假设我们要开发一个用户管理的功能,一个用户需要通过姓名进行初始化,以便后续可以使用姓名进行检索。
第一个测试:
describe User do
it "lets me assign a name" douser = User.new "Paul"user.name.should == "Paul"
endend
运行后会报错
uninitialized constant User
编写测试时,请总是确保该测试首次运行时是肯定失败的,这意味着你不是对一个已经成功的功能进行测试。接下来,编写user的内容:
class User end
继续测试,并报错:
ArgumentError: wrong number of arguments (1 for 0)
然后实现一个能够接受姓名作为参数的构造器:
class User def initialize name end end
继续测试,会给出错误。
NoMethodError: undefined method 'name'
然后添加函数name后,测试结果如下:
user.name.should == "Paul" expected: "Paul" got: nil
接下来必须为name赋值,最后得到:
class User def initialize name end def name "Paul" end end
尽管这个用户永远都叫Paul,还有未实现的细节,之前编写的测试确实顺利通过。根据TDD的原则,我们现在可以对代码进行重构。具体的代码不再介绍,但是首先必须先添加测试:
describe User do it "lets me assign a name" do user = User.new "Paul" user.name.should == "Paul" end it "lets me assign a different name" do user = User.new "Sarah" user.name.should == "Sarah" end end
继续测试,还是得到错误:
user.name.should == "Sarah" expected: "Sarah" got: "Paul"
这是意料中的错误,所以继续修改,直到测试通过:
class User def initialize name @name = name end def name @name end end
到这里我们完成了期望的功能。但是,代码的重构没有停止,Ruby提供了以后简单的读属性的方法attr_reader。因此,重构的代码如下:
class User attr_reader :name def initialize name @name = name end end
TDD的理由
测试驱动开发的步骤看起来很繁冗,那需要我们这么做的理由是什么呢?
许多应用程序在开发的初始阶段看起来很细琐但是简介,然而几周后就变成了垃圾堆,充满了无法预料的“惊喜”。惟一的解决之道,就是在每次代码编写后手动的测试,你或许会花费几周的时间用来进行测试。
TDD让开发人员更加敏捷,谨慎而缓慢的的开始,可以避免在后期踩上埋藏很深的地雷。重要的是,哪怕是几年后你的应用程序变得庞大而又繁杂,甚至丢失了所有的信息资料,你的测试仍然可以保证程序可以正常运行。
TDD和持续集成
测试的惟一问题在于,随着时间推移,需要花费的时间成倍的增长。想要执行所有测试,可能会花费很长的时间。一个好的解决方案就是引入CI(Continuous Integration)。在此基础上,还可以引入自动编译、部署等方案,以便减少人工干预,自动化的执行测试和后续流程。
结论
TDD的优势在于通过额外的努力获得长期的回报,并获得应用程序的敏捷、速度和安全性。
来源:http://blog.codeship.io/2013/04/16/tests-make-software.html