woodheadz 发表于 12-10-2010 14:35:21

聊聊测试驱动开发(TDD)(已完成)

我想,在所有的软件方法中,最让人容易产生误会的,大概就是测试驱动开发了 (Test-driven development )
测试驱动开发比一般的开发方法而言,结果中会多出一大块自动完成的单元测试。所以很多TZ自然而然就把这部分单元测试当成TDD的主要特征甚至目的,把TDD单纯地理解为“完成代码以及为代码添加自动化的单元测试”,因而认为TDD实际上是测试方法的一种。 这种理解很自然,其实我一开始也是这么理解的。
后来在自己尝试了几次TDD开发后,我才逐渐意识到TDD精妙,才开始理解TDD其实是一种设计/编码方法,而不是测试方法。TDD的目的是得出优雅简洁的设计和代码,而不是得到经过完全测试的可发布产品。

TDD方法对我帮助极大,它里面包含的一些原则和理念完全影响了我编码设计以及看待软件开发的思路和观点。 我觉得很多TZ只是没有机会去真正接触TDD,一旦接触和了解了,我相信也能得到不少助益。 所以我想尝试从我的角度向大家介绍下TDD。

TDD的开发过程包含有两个状态,在任何时间点上,你必须在且只能在其中的一个状态上,这是TDD实践的铁律。所谓的两个状体指:
a.测试/开发状态:这个状态专注于功能的增加,在此状态下绝对不应该去考虑设计的优化。这个状态包含下列步骤:
1.编写自动测试代码描述一个最微小功能
2.运行测试,通过编译错误或者自动测试的失败结果确定这个微小功能并不存在
3.编写代码,使用最简单(尽量简单,简单到简陋的程度也不怕)的方式,完成这个微小功能。
4.再次运行测试,确认测试结果OK。
5.重复1~5步,用测试代码从各个角度描述功能。直至功能被充分地描述。
这个过程里面最关键的是第3步,你的代码必须尽量简单。此外,如果在开发过程中你忽然想到代码中可能存在某个问题,此时务必先在此状态下添加测试案例,通过测试案例的失败验证你的认识,才能去修改代码。
当完成一个小集合的微小功能后,进入第二种状态。

b.重构状态:这个状态专注于设计的优化。 包含以下步骤:
1.进行一个重构步骤
2.运行自动化测试确保重构无误
这个状态下绝对不能添加任何新的测试案例,也不允许为代码添加新的功能。在重构过程中,应该全力集中在基于当前的测试案例描述的需求来优化代码,要避免因为预见性的想到测试案例未描述的需求而做出任何预见性的设计。
当当前的代码足够优化后,回到状态a

整个TDD的过程就是这两个状态不断切换的过程。切换的频度一般会比较频繁,就像一个人踩着小碎步前进。

TDD的过程没什么复杂的,那么它怎么带来那么大的好处呢?

[ 本帖最后由 woodheadz 于 13-10-2010 11:50 编辑 ]

woodheadz 发表于 12-10-2010 14:35:54

继续 :lol
TDD到底带来什么好处?又如何带这些好处?

充分抽象
TDD要求为每个微小功能编写测试,并且是在这些功能以及相关的支持功能存在前就开始编写。这就要求必须对系统的各个部分进行充分的抽象,否则你根本无法编写测试。 这就保证TDD产生的代码几乎不会存在抽象不足的问题。当然这样的坏处就是程序员必须具备足够的设计技巧,否则很多时候、尤其当系统的规模扩大到一定程度后根本就不知道如何完成测试代码

避免过度设计
TDD的整个过程始终贯彻“尽量简单”的原则。如果你是一个能够完成测试代码的程序员,你肯定已经掌握的足够的设计技巧,对设计模式应该已经比较熟悉。这个时候你面临的问题就是过度设计。在掌握了设计模式和面向对象的原则之后,程序员的一生,就会转变为同“过度设计”斗争的一生:lol 。设计水平的高低,往往就体现在对设计的度的把握上。TDD在“增加功能”状态下,强烈要求用最简单(简陋)的代码完成测试用例所要求的功能。而在“重构”状态下,严格禁止使用预见性的设计,严格禁止增加新功能。这样使得TDD最大限度的避免了不必要的过度设计。另外从接口开始自外而内的设计顺序也让TDD更加不容易出现设计过度的情况。

为变化而生的设计
其实TDD的方式隐含了个核心哲学:变化促使进步,拥抱变化。 TDD始终严格禁止预见性的设计,而专注在“把当前的代码重构为足够优秀”这个问题上。设计是否足够优秀?让下一次变化来检验把!:D经过N次变化重构后的代码,必然具有超低的耦合度和很高的适应能力。 我想这点应该能够解答楼下kukri TZ“tdd只是一个权宜之计”的疑问。 TDD开发的系统不会有“一旦系统规模较大,后期会面临系统结构大改的麻烦”。通常的系统结构大改是因为需求或者对需求的认识在某几个核心方面(肯定不会是全部方面)发生了变化,而系统中这些方面相关的代码和其它代码间存在很大的耦合,而TDD能够避免这种耦合。另外TDD会产生大量的单元测试代码,当后期系统重构的时候,这些代码能够最大限度地降低重构的成本和风险。


TDD的问题
我接受TDD已经好几年了,有成功的尝试,也有不少失败的经验,今天在这儿总结下。这些失败的经验有些可能和我个人水平有关,有些我认为是TDD本身存在的局限。
1.TDD其实是一种精英主义的开发思路。把TDD用于团队,我所有的成功经验都是在高素质的小团队中取得的。TDD的前提是程序员必须具备一定的抽象能力,不满足这个前提,TDD就是折磨。 其实我认为,极限编程(XP)整一个就是精英主义的开发方式。
2.TDD并不适用于所有的场合。UI是一例。另外DB,网络相关的模块TDD编程也要谨慎考虑。当然TDD可以采用Mock对象的方式来适应这些场景。但对于Mock对象,我的意见也是小心使用。有时候Mock对象带来的麻烦比好处多多了。


资料推荐
最后推荐三本书,第一本是BOB大叔惊天地泣鬼神的《敏捷软件开发原则、模式与实践》。这本书继侯捷《深入浅出MFC》让我明白了面向对象设计的含义后,给我带来最大冲击的一本书。 其中第六章从头到尾描述了一个使用结对编程和TDD开发一个保龄球记分程序的场景,如果可以,请你务必尝试跟着他们的思路编一遍那个程序。我实在找不到更好的TDD开发的例子啦。顺便说一句,这本书对于OOP的本质和设计模式的阐述也是我见过最好、最易懂的。
第二本是TDD大师Kent Beck编写的“Test Driven Development: By Example ”,有中文版的。 这个书里面神奇地用TDD开发了一个XUnit工具,也就是说用XUnit自己开发出了自己。只是Kent的文字浅显程度比BOB大叔有明显差距,入门还是推荐第一本书。
第三本是Martin Fowler的“重构:改善既有代码的设计”。里面枚举了n个重构的手段。去读读这本书,并且实践下,你会发现我们经常做的随意的“修改代码”的工作,原来可以变得那么有序,那么严谨。

尝试下TDD吧,你会发现原来coding是那么有趣! :lol :lol :lol

[ 本帖最后由 woodheadz 于 13-10-2010 12:02 编辑 ]

woodheadz 发表于 12-10-2010 14:45:05

原帖由 花蕾般的钟声 于 12-10-2010 14:42 发表 http://www.freeoz.org/ibbs/images/common/back.gif
哇,高深:D
可能是我表述得繁琐了点,回头我专门介绍一两个TDD的例子给大家,真的自己去做一遍就会发现其实也没什么啦

kukri 发表于 12-10-2010 16:35:35

个人感觉使用tdd的前提条件有2个:
1. 对将要完成的系统不了解。或者说操作系统,硬件部分已经稳定实现,应用部分需要重新设计。但是对该应用,只限于阅读文档级别。
2. 该系统的接受,通过一套既定的测试流程来检测。
这样一来,有明确的结果却没有现实经验,通过tdd来开发,会快速看见成果,有助于边学边练。坏处是一旦系统规模较大,后期会面临系统结构大改的麻烦。

所以,我还是希望在有一定经验的前提下,有了系统架构,流程后再开发。tdd只是一个权宜之计。

洗耳恭听楼主的例子介绍,共同探讨。

woodheadz 发表于 13-10-2010 11:52:58

原帖由 kukri 于 12-10-2010 16:35 发表 http://www.freeoz.org/ibbs/images/common/back.gif
个人感觉使用tdd的前提条件有2个:
1. 对将要完成的系统不了解。或者说操作系统,硬件部分已经稳定实现,应用部分需要重新设计。但是对该应用,只限于阅读文档级别。
2. 该系统的接受,通过一套既定的测试流程来检 ...

我补充完整了。
不太同意TDD是权宜之计的说法,原因请参看我补充的内容 :lol
页: [1]
查看完整版本: 聊聊测试驱动开发(TDD)(已完成)