测试驱动开发TDD实战与模式解析TDD概述•KentBeck先生最早在其极限编程(XP)方法论中,向大家推荐“测试驱动”这一最佳实践,还专门撰写了《测试驱动开发》一书,详细说明如何实现。经过几年的迅猛发展,测试驱动开发已经成长为一门独立的软件开发技术,其名气甚至盖过了极限编程。简介•测试驱动开发(TestDrivenDevelopment,英文缩写TDD)是极限编程的一个重要组成部分,它的基本思想就是在开发功能代码之前,先编写测试代码。也就是说在明确要开发某个功能后,首先思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码满足这些测试用例。然后循环进行添加其他功能,直到完成全部功能的开发。代码整洁可用(cleancodethatworks)是测试驱动开发所追求的目标。优点•(1)完工时完工。表明开发人员可以很清楚的看到自己的这段工作已经结束了,而传统的方式很难知道什么时候编码工作结束了。•(2)全面正确的认识代码和利用代码,而传统的方式没有这个机会。•(3)开发小组间降低了交流成本,提高了相互信赖程度。•(4)避免了过渡设计。优点•(5)系统可以与详尽的测试集一起发布,从而对程序的将来版本的修改和扩展提供方便。•(6)逃避了设计角色。对于一个敏捷的开发小组,每个人都在做设计。•(7)大部分时间代码处在高质量状态,100%的时间里成果是可见的。•(8)由于可以保证编写测试和编写代码的是相同的程序员,降低了理解代码所花费的成本。优点•(9)为减少文档和代码之间存在的细微的差别和由这种差别所引入的Bug作出杰出贡献。•(10)在预先设计和紧急设计之间建立一种平衡点,区分哪些设计该事先做、哪些设计该迭代时做提供了一个可靠的判断依据。•(12)发现比传统测试方式更多的Bug开发过程•概括起来,测试驱动开发的基本过程如下:•(1)明确当前要完成的功能。可以记录成一个TODO列表。•(2)快速完成针对此功能的测试用例编写。•(3)测试代码编译不通过。•(4)编写对应的功能代码。开发过程•(5)测试通过。•(6)对代码进行重构,并保证测试通过。•(7)循环完成所有功能的开发。•更概括的来说,可以分为三部曲:•红条模式绿条模式重构实例演示----Fibonacci数列•测试驱动开发,那么测试先行是必然的了.•第一个测试:•publicvoidtestFibonacci(){•assertEquals(0,fib(0));•}•我们都知道:Fibonacci数列的第一个数是0,现在测试代码有了,那么我们先运行一下测试吧.•报错,红条模式!•显然,因为我们甚至还没有fib()这个函数!•我们赶紧让这个测试通过吧,于是添加如下功能代码:实例演示----Fibonacci数列•intfib(intn)•{•return0;•}•绿条模式,测试通过!•第二个测试•publicvoidtestFibonacci()•{•assertEquals(0,Fib(0));•assertEquals(1,Fib(1));•}实例演示----Fibonacci数列•为了能让测试通过,我们编写如下代码:•intfib(intn)•{•if(n==0)return0;•return1;•}•绿条模式,测试通过!•从测试代码中我们看到:•assertEquals(0,Fib(0));•assertEquals(1,Fib(1));实例演示----Fibonacci数列•重复!不仅在源代码,就是在测试代码中,我们也要避免重复!(因为测试代码中也会出现模式!)•改写测试代码:•publicvoidtestFibonacci•{•intcases[][]={{0,0},{1,1},{2,1}};•for(inti=0;icases.length;i++)•assertEquals(cases[i][1],fib(cases[i][0]));•}演示实例----Fibonacci数列•这样添加测试代码就容易多了.•测试代码改过了,再运行下,绿条模式!还能通过,那我们就可以放心继续了!•publicvoidtestFibonacci•{•intcases[][]={{0,0},{1,1},{2,1},{3,2}};•for(inti=0;icases.length;i++)•assertEquals(cases[i][1],fib(cases[i][0]));•}演示实例----Fibonacci数列•红条模式!测试失败,修改功能代码如下:•intfib(intn)•{•if(n==0)return0;•if(n=2)return1;•return2;•}•绿条模式!测试通过!实例演示----Fibonacci数列•现在,我们准备对上面的代码进行重构。我们将返回值设置为2,但是,我们的真正的用意并不是2,而是1+1,第一个1是fib(n-1)的一个实例,第二个1是fib(n-2)的一个实例:•intfib(intn)•{•if(n==0)return0;•if(n=2)return1;•returnfib(n-1)+fib(n-2);•}实例演示----Fibonacci数列•对于fib(2)来说,上面的结构同样可以工作,•所以我们对上面的代码结构进一步重构:•intfib(intn)•{•if(n=1)return1;•returnfib(n-1)+fib(n-2);•}•运行上述所有测试,绿条模式,全部测试通过!TDD的负面看法•任何一种新的开发思想都是不完美的,我这里找了几种比较典型的对TDD的负面看法,供大家讨论:•1:某些TestCase并不一定那么好写,你可能80%的时间花在某个TestCase的编写和设计上,然而,需求一变,你又得重写TestCase,有时候,你会发现写TestCase其实和做实际设计没有差别,你同样要考虑你TestCase的正确性,扩展性,易读性,易维护性,甚至重用性。如果说我们开发的TestCase是用来保证我们代码实现的正确性,那么,谁又来保证我们的TestCase的正确性呢?编写TestCase也需要结对或是Codereview吗?软件开发有点像长跑,如果把能量花在了前半程,后半程再发力就很难了。TDD的负面看法•2:测试驱动开发不能节省开发投入,也很少能够节省开发周期。测试开发所编写的大量测试代码都是要投入时间与精力的,我现在的代码统计显示,测试代码与实现代码的比例基本在3:2,即使因为测试驱动开发能得到一个简洁的设计,也不能弥补测试代码的工作量。当然,测试代码可以一定程度保证高质量的实现代码,从而减少后期软件测试与修正缺陷的工作周期,并进一步在软件发布后减少代码修正维护的工作量。但至少在开发阶段,两相抵消,开发周期并不能有明显改善,如果是第一次采纳测试驱动开发,甚至会延长开发周期。TDD的负面看法•3:测试驱动开发不可能让人立即具有设计出优美解决方案的能力,或者说是优秀的分析与解决问题的能力。TDD不是TestDrivenDesign。它只是一个过程,也许可以帮助你发现并帮助你实现优美的解决方案,但是它不能变魔术一样,只要学会了就变出一个优美的设计出来,优秀的分析问题与解决问题的能力还是要靠不断地学习与借鉴他人成就才能得到提高。That'sall,thankyou!logo