TDD到底美还是不美?

发表于:2011-7-14 15:10

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Todd Wei    来源:51Testing软件测试网采编

  最近CoolShell上的一篇《TDD并不是看上去的那么美》引起了敏捷社区的高度关注和激励辩论。今天,InfoQ甚至专门举行了一个“虚拟座谈会”《TDD有多美?》,几位国内敏捷社区的名人专门就此问题展开了深入地讨论。不论结果如何,这个纯技术的探讨精神还是非常值得赞赏的。事件实际上可以简单地归纳为“一个有一定影响力的开发人员质疑TDD,一群敏捷社区名人对TDD进行解释和辩护”。现在,就让我坚定地站在CoolShell一边,为对TDD的质疑和批判添砖加瓦吧!

  TDD的核心理念是什么呢?第一是Specification by Example,即把测试用例作为表达需求的一种方式。传统的需求表达方式包括文档,Use Case等,而TDD强调通过测试用例来表达需求。另外,TDD的测试用例是黑盒的基于外部接口的,所以,它实际上又是对外部接口的设计。如何看待测试用例是TDD与传统测试的一个重要区别。“不把测试用例单纯地视为测试,而从需求和设计的角度来看测试用例”的理念本身是好的。另外,TDD的第二个理念是Test First,强调测试对于实现的驱动作用,先写测试用例,再实现和重构。Test First的实质是“先理解清楚需求,并做好外部接口设计,把它转化为测试用例,然后再来实现和重构”。

  我认为Specification by Example是不错的,因为测试用例作具有精确性,容易自动化的优点,这是传统的文档和Use Case在表达需求时所欠缺的地方。但Test First则有很大的问题,尤其“在没有测试用例失败之前,不要写任何一行代码”的极端方式则更是极端的错误。

  如果测试用例就是需求和设计,那么为什么不能先写出测试用例再来实现呢?这不是我们最熟悉的先需求再设计再编码吗?答案是:不能执行的测试用例(Test First)和能执行的测试用例有着天壤之别,你写出了测试用例不代表你就看到了运行的实际效果。不能执行的测试用例和写在纸上的文档相比对实现的指导意义不见得能好到哪里去!除非是一些很简单的情况下,在实际的软件开发中,你很难在没有执行测试用例的情况下写出真正符合最终需求的测试用例来。比如:你做一个页面,页面的效果需求和设计通常会在真正可以运行之后不断调整。如果片面强调测试对实现的驱动作用,那么实际上隐含了“需求可以在实现之前明确下来”的假设,这是非常不敏捷的和不现实的!

  Test First要求写测试用例时对软件需求有精确的了解,但实际软件开发过程中用户需求和外部环境的不确定性会导致软件需求难以把握和频繁变动。用户需求是指用户所期望的效果,它从目标的角度影响了软件需求;外部环境是指软件所运行和依赖的外部环境,它从实现基础的角度影响软件需求。

  用户需求的不确定性是指“需求无法在用户真正能运行看到效果之前明确下来”,比如:让你开发一套Wow这样大型的游戏,你能想象游戏的效果是设计者一开始就想好了精确到每一个细节吗?对于游戏这样的软件,测试用例几乎不可能在实现之前写出来,即使游戏设计者脑子里已经有了游戏应该是什么样子,他也无法写出自动化的测试用例来。所以,实际上,游戏的设计者通常只能借助文档,草图,Use Case等非精确的方式大致提出需求,在看到效果之后才能逐步地细化和明确,需求的增加和改变会伴随整个软件开发过程。另外,还有一种极端的情况是根本不存在精确的用户需求,比如:自动化翻译软件,你能在实现之前就把翻译效果用测试用例固定下来吗?存在绝对正确的翻译方法吗?

  外部环境的不确定性是指“当我们的系统需要和外部系统集成时,关于外部系统行为的假设也无法在实际集成运行前完全确定”。比如:做一套股票客户端系统需要连上交易所系统,尽管通常交易所会有相应的协议,但实际开发过程中,协议会有很多定义不清晰或未定义,甚至有实际行为与协议不符合的地方,这样我们只有先做假设实现,然后在交易所提供的测试环境中去确定。对于像交易所这样非常重视质量的环境都存在协议定义不清或者不全必须在集成测试时才能弄清楚的情况,普通的系统更是大量存在没有接口文档,接口文档不详细不准确等情况。如果是测试驱动,需要识别出被测系统的边界并模拟出外部环境,你如何去模拟交易所?即使你愿意投入很大的精力去模拟,在最初没有明确其行为的时候你的模拟也是没有依据的假设,随时可能在真实集成后被推翻。

  所以,Test First需要对于被测系统的需求和环境有精确的了解,但由于需求不确定性和外部环境不确定性两大问题,Test First在很多时候都是不现实的。其实,Test First和瀑布式思想没有区别,都强调需求先于实现,而忽略了软件需求的产生是一个在实际运行中不断调整探索完善的过程。TDD无非是把需求分析的结果用测试用例表达,替代传统用文档表达需求,但从宏观上看,TDD和瀑布比是换汤不换药,这都不是真正的敏捷。除了简单情况,不存在脱离实现的需求,你能够在明确了需求之后就实现出一套linux系统吗?既然你根本无法实现一套linux系统,那么这样所谓的需求又有多大的意义呢?所以,能提出什么样的需求不能脱离你的实现能力。需求和实现之间不是简单的谁驱动谁,而是一种相互反馈的关系,这与需求用什么方式表达没有关系。正如瀑布模型无法在初始阶段做出完美的需求分析,TDD也无法在初始阶段做出完美的测试用例,不仅如此,自动化测试用例的开发维护成本还远高于文档。到目前为主,我推崇的方式是快速实现,在实际运行中体验效果,不断优化探索和明确需求和外部环境,当需求和对外部环境的认识达到一个比较稳定的程度才编写测试用例将需求固化下来。

  上面的论述主要针对贴近用户的外部需求(如ATDD),下面我会进一步解释即使是在内部的单元测试级别TDD仍然有问题。我们还是首先从需求入手,思考一下单元的需求是哪里来的呢?答案是:需求来自于设计, 也就是说高层模块的内部设计产生了低层模块的需求。而这种内部设计具有很大的不稳定性,带有很多假设的成分,在没有进行集成测试的情况下,很难讲这种内部设计是否合理。实际项目开发通常会在集成运行之后不断调整内部的设计,即影响单元的需求。那么,如果是按测试驱动,首先按不成熟的内部设计把一个个单元需求编写成单元测试再来实现,实际上大大推迟了能进行集成测试的时间, 对于真正快速弄清需求稳定设计反而是不利的。假设最终还是所有单元都完成,然后开始运行集成或验收测试,这时候有两种可能:1.用户看到实际效果,决定调整需求;2.发现集成前在单元层面的假设不成立或者是有没有考虑到的情况。不论是哪一种情况发生,以前所写的单元测试都面临着被废弃或必须修改的命运。实际上,多数与业务相关的单元测试用例比起集成或验收测试用例更加不稳定,因为它会受到所有其上层模块的需求和设计变动的影响。由于我们在不稳定的单元测试上浪费了大量的时间(按我的经验编写单元测试比编写实现更耗时),这就导致了迟迟无法进行集成看到实际效果,也没有办法敏捷地应对需求的调整。也就是说具有讽刺意味的,Test First理念居然是和敏捷理念矛盾的!

21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号