TDD(Test-driven development), 也就是我们常说的
测试驱动开发
, 是由 Kent Beck 在 1996 年提出的概念 TDD 这个术语, 经常被人挂在嘴边, 然而真正在项目实施, 却寥寥无几
是 TDD 对开发者要求太高? 还是 TDD 根本就不值得去做?
非也为了让大家对 TDD 有一个具体而亲切的认识, 我先给大家举一个在编程中使用 TDD 进行开发的实际例子
Bob 大叔的保龄球训练
. 这是一道计算保龄球比赛一局总得分的编程题, 保龄球的计分规则非常简单:
. 每一局总共有十轮, 每轮一开始会有十支球瓶, 球手可以扔两次球, 目标就是用尽量少的球把全部球瓶击倒
. 如果第一球就把全部的球瓶都击倒了, 也就是 STRIKE, 画面出现 X, 就算完成一轮了, 所得分数是 10 分再加后面两球的倒瓶数,
. 如果第一球没有全倒, 就要再打一球, 如果第二球将剩下的球瓶全都击倒, 也就是 SPARE, 画面出现 /, 也算完成一格, 所得分数为 10 分再加下一格第一球的倒瓶数,
. 如果第二球也没有把球瓶全部击倒的话, 那分数就是第一球加第二球倒的瓶数, 没有奖励(bonus), 再接着打下一格依此类推
. 第十轮有机会扔三次球如果在第十轮出现 STRIKE 或者 SPARE, 则球手可再加打第三球
. 全部十轮的得分相加就等于这一局的总得分
题目要求我们提供一个名字为 Game 的类, 这个类有两个方法:
. roll(pins : int): 每次球员扔球后执行这个方法, 入参是此次扔球击倒的球瓶数量
. score(): 每局比赛结束时执行的方法, 返回这局比赛的总得分
下面开始使用 TDD 来完成这个编程训练
如果此时你已经在开始构思要如何实现, 请打住! 因为这不是 TDD 的风格
记住, 先别想着怎么去实现, 先写测试用例, 也就是先把你调用这个 Game 类的代码写下来
首先, 我们创建一个 BowlingGameTest 类:
import junit.framework.TestCase; public class BowlingGameTest extends TestCase { }
接着添加第一个测试用例:
当我们刚刚 new 了一个 Game 对象时, 编译器就提示错误了, 此时暂停测试用例的编写, 开始编写产品代码!(这么做似乎有点过于耿直, 不过对于加深对 TDD 的印象还是很有帮助的)
我们创建了 Game 类, 此时编译通过, 执行所有单元测试, 绿条!
接着我们在第一个单元测试中调用 roll 方法和 score 方法, 同样的, 我们遇到编译不通过的问题, 再依次给 Game 加上对应方法后, 我们得到了下面这段代码:
我们心里很清楚, 这个代码是经不住考验的, 我们随便添加一个单元测试, 都可以让测试用例不通过比如我们让一个保龄球世界排名倒数第一的球手去比赛, 每轮他都只击倒一个球瓶, 测试用例毫不犹豫地失败了:
于是我们要修改一下逻辑, 在每次 roll 的时候, 加上分数细心的读者可能还发现了, 下面这段代码还对测试代码进行了重构, 把每个单元测试都要做的 new Game()操作抽取到了 setUp 方法中:
接着我们又发现我们经常要模拟很多次击倒相同数量球瓶的操作, 因此我们把这个操作抽取成一个 rollMany 方法:
我们的代码到这里还完成不到 1/3, 但是我们已经做了两次重构, 没错, TDD 的过程, 也是不断小步重构的过程
接下来, 我们测试一下 Spare 的场景, 这一次测试用例又理所当然的失败了 (不要担心一次次失败会打击自信心, 因为这些都是我们刻意制造的失败, 人们面对意料之中的失败往往更有激情) 而当我们准备动手修改产品代码时, 却发现了一个代码设计层面的问题, 那就是我们在 roll 方法里面做了 roll 不应该做的事情, roll 意味着扔球, 而我们却在里面修改了得分:
此时我们需要把新增的用例暂时屏蔽掉, 然后对产品代码进行重构, roll 专心做它的事, 把计算得分的活交给 score 来做:
接下来, 就是继续放开我们之前屏蔽掉的测试用例, 继续修改产品代码, 让测试用例通过, 然后再添加 STRIKE 的测试场景添加更多的测试场景 这些过程就不再赘述了, 因为作为一个 TDD 的例子, 前面这几个步骤, 已经足够让大家对 TDD 有一个比较深刻地理解了
来源: http://www.51testing.com/html/49/n-3724649.html