TDD到底美还是不美?
最近CoolShell的上一篇《TDD并不是看上去的那么美》引起了敏捷社区的高度关注和激励辩论。今天,InfoQ甚至专门举行了一个“虚拟座谈会”《TDD有多美?》,几位国内敏捷社区的名人专门就此问题展开了深入地讨论。不论结果如何,这个纯技术的探讨精神还是非常值得赞赏的。事件实际上可以简单的归纳为“一个有一定影响力的开发人员质疑TDD,一群敏捷社区名人对TDD进行解释和辩护”。现在,就让我坚定地站在CoolShell一边,为对TDD的质疑和批判添砖加瓦吧!
TDD的核心理念是什么呢?第一是Specification by Example,即把测试用例作为表达需求的一种方式。传统的需求表达方式包括文档,Use Case等,而TDD强调通过测试用例来表达需求。另外,TDD的测试用例是黑盒的基于外部接口的,所以,它实际上又是对外部接口的设计。如何看待测试用例是TDD与传统测试的一个重要区别。“不把测试用例单纯地视为测试,而从需求和设计的角度来看测试用例”的理念本身是好的。另外,TDD的第二个理念是Test First,即强调先“写测试用例,再实现和重构”。在Specification by Example的理念下,Test First的实质是“先理解清楚需求,并做好外部接口设计,把它表达为测试用例,然后再来实现和重构”。
我认为,Specification by Example是不错的,因为测试用例作具有精确性,容易自动化的优点,这是传统的文档和Use Case在表达需求时所欠缺的地方。但Test First理念本身则有很大的问题,尤其“在没有测试用例失败之前,不要写任何一行代码”的极端方式则更是极端的错误。
如果测试用例是需求和设计,那么为什么不能先写出测试用例(即理解清楚需求)再来实现呢?这不是我们最熟悉的先需求再设计再编码吗?答案是:不能执行的测试用例(TDD先写的测试)和能执行的测试用例有着天壤之别。不能执行的测试用例和写在纸上的文档相比对实现的指导意义不见得能好到哪里去!除非是一些很简单的情况下,在实际的软件开发中,你很难在没有执行测试用例的情况下写出真正符合最终需求的测试用例来。比如:你做一个页面,页面的效果需求通常会在真正可以运行之后不断调整。如果片面强调先写出测试用例(即理解需求做好设计),再实现,那么实际上隐含了“需求可以在实现之前固定下来”的假设,这是非常不敏捷的和不现实的!我认为要做到真正的敏捷必须承认“需求无法在用户真正能运行看到效果之前固定下来“。如果片面强调测试用例对于实现的驱动作用,其本质和瀑布式思想没有区别。除了简单情况不存在脱离实现的需求,你能够在明确了需求之后就实现出一套linux系统吗?所以,到目前为主,我赞同的方式是快速实现,在实际运行中体验效果,不断优化探索需求,最终固定需求并用测试用例表达出来。
另外,即使是在相对简单的单元测试级别,TDD仍然有很多问题。我们来看看单元的需求有什么特点。首先,单元的需求是哪里来的呢?单元的需求来自于更高模块的设计, 也就是说高层模块的内部设计产生了低层模块的需求。而这种内部的设计具有很大的不稳定性,带有很多假设的成分,在没有进行集成测试的情况下,很难讲这种内部设计(即单元需求)是否合理。实际项目开发通常会在集成运行之后不断调整内部的设计(即影响单元的需求)。那么,如果按TDD,单元的不成熟需求表达为单元测试,实际上推迟了能进行集成测试的时间, 对于真正快速弄清需求稳定设计反而是不利的。具有讽刺意味的是,Test First理念居然是和敏捷理念矛盾的!
所以,我认为TDD的理念Specification by Example没错,但Test First即“可以在实现之前把需求具体化成测试用例”的理念错了。真正符合实际开发情况的理念是“需求是在实际运行过程中根据效果不断探索调整得来的,不可能脱离实际运行写出真正符合最终需求的测试用例来”。所以,我们真正应该做的是尽快看到实际运行的效果,而测试作为需求或设计的表达方式是在看到效果固定需求之后。过度的TDD只会导致迟迟看不到实际运行效果,看到效果需要调整需求又会废掉或改掉一大堆的测试用例。
当然,我不是全盘否定TDD,TDD在某些需求特别固定的场合是适用的。TDD的先思考外部接口,外部设计的理念也对开发人员有很大益处;另外,TDD通常也具有较高的代码覆盖率。本文的主要观点在于:需求是在实际运行看到效果之后才逐步确定的,我们的开发过程必须能够“敏捷地”适用需求的变化,而TDD的Test First理念恰好与之矛盾。所以,对于TDD不了解的朋友,我建议应该学习和实践TDD,从而获得其益处;同时我也提醒TDD的测试用例随时可能被修改或废弃,这可能拖慢你看到实际运行效果的时间。
所以,我认为TDD的理念Specification by Example没错,但Test First即“可以在实现之前把需求具体化成测试用例”的理念错了。真正符合实际开发情况的理念是“需求是在实际运行过程中根据效果不断探索调整得来的,不可能脱离实际运行写出真正符合最终需求的测试用例来”。所以,我们真正应该做的是尽快看到实际运行的效果,而测试作为需求或设计的表达方式是在看到效果固定需求之后。过度的TDD只会导致迟迟看不到实际运行效果,看到效果需要调整需求又会废掉或改掉一大堆的测试用例。
当然,我不是全盘否定TDD,TDD在某些需求特别固定的场合是适用的。TDD的先思考外部接口,外部设计的理念也对开发人员有很大益处;另外,TDD通常也具有较高的代码覆盖率。本文的主要观点在于:需求是在实际运行看到效果之后才逐步确定的,我们的开发过程必须能够“敏捷地”适用需求的变化,而TDD的Test First理念恰好与之矛盾。所以,对于TDD不了解的朋友,我建议应该学习和实践TDD,从而获得其益处;同时我也提醒TDD的测试用例随时可能被修改或废弃,这可能拖慢你看到实际运行效果的时间。
关键字 tdd到底美还是不美?
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架