1. 起步 - 从哪里开始?
万事开头难。不过如果有人拿一个例子,一步一步演示给你看,那就不难。Kent Beck在《测试驱动开发》一书中就这样做了。这本书很薄,但它是给初学者准备的极好的入门书。虽然书中的例子是用Java演示的,但C++的学习者理解起来也没有什么难度。
书中使用了“多币种资金(Multi-Currency Money)”的例子,需要考虑多币种,汇率等多种情况,刚好能演示如何分解复杂的任务,又不会让读者厌烦。在逐步达到目标的过程中,我们能看到:
- “先写测试,再实现”的工作方式。以测试用例为单位,培养“小步前进”的节奏感。
- 从一个大任务中切下一组小任务,逐步实现,逐步接近目标。
- 小心构造测试用例,持续运行自动测试集,保证代码持续可靠。
- 容忍中间过程的不完美,将它看成一个持续完善的过程;同时保持对坏气味(bad smell)的敏锐度,挑选适当的时机消除它,得到整洁的代码。在容忍和不容忍两者中达到平衡。
- 清晰的设计和可靠的代码,是产生自信的土壤,而自信会激发人的灵感。抓住突然涌现到头脑中的灵感,对设计做出优化调整。
- 从使用者的角度审视接口,给接口塑形(Shape)。首先考虑“我想要什么样的“,而不是“我能做什么样的”。
测试驱动开发需要选择合适的自动化测试框架。 CPPUnit 和 Google Test都可以。
既然定位自己是C++开发者,适当理解C++的常用法还是必要的。如果经常还在语法上打转,那积极性自然会打些折。这里是一些有用的C++参考书。
2. 夯基础 - 测试驱动方法 + 设计模式
练习了一些小例子,学会了基本的步骤后,需要复杂一些的东西来锤炼我们的头脑。一个好的方法是在实际的项目中尝试运用《设计模式》一书中描述的模式。
选择第一个项目很重要。如果第一次取得了明显的收益,就会产生更多的兴趣,并一再尝试。如果没有收益,就可能产生不过如此的情绪,最后的结果就是继续不下去。
首选是逻辑复杂,环境依赖度小的项目,这样的项目特别能体现测试驱动开发的优势。其次是环境容易抽象,适合用Mock Object模拟的项目。
这一阶段的目标是:在测试驱动开发的辅助下,运用设计模式,将复杂的逻辑表达得清楚明白,容易理解。
一部分设计模式容易找到适合运用的场景,如Builder, Template Method, Composite,Decorator等,另外一些则不容易,如Prototype, Mediator等。还有一个是使用很广,却与测试驱动开发不兼容的,就是Singleton,因为它会导致测试用例之间的状态残留,让人厌烦。
设计模式是不是实用,用上了才知道,也只有用上了,解决问题了,才算理解了。没定下来前就需要尝试,很可能走到后面发现走错了,要退回去。所以有个工具必不可少,就是带分支特性的版本控制系统。总是在分支上尝试,随便试。搞砸了,放弃这个分支就是了。
这样的工具以前是CVS,现在GIT替代了它的位置,因为分支是GIT的天性。
DVR-POS库是测试驱动开发的一个实际项目的例子。
3. 晋级 - 从项目需求出发考虑: “需要怎样的?怎样设计能满足?”
将模式运用到项目是一种正向的思考,我们现在需要反向的思考。把模式放一边,专注于项目本身有什么问题,怎样的设计能解决这个问题。
在思考的过程中,模式会自然而然地蹦出来。呀!这个模式就是用来解决这类问题的!或者,这个设计不就是这个模式嘛!这时,就会有一种与大师心心相通的感觉。
这个时候,设计模式已经开始成为我们头脑的一部分了。如果在前一阶段还需要把《设计模式》放在桌面当工具书,那么现在可以把它放回到书架上了。具体的条条框框已经忘掉了,留在脑子里的只是一些模糊的概念。但是当问题冒出来的时候,其中的部分概念就会重新组合起来,成为解决问题的利器。
这个时候,已经不再拘束于模式的已有用法,也不再拘束于已有的模式,甚至也不再拘束于模式了。很多时候,简单的OO就够了,那就OO好了。简单的一击致命才是最漂亮的剑法。
测试驱动开发的基本原则是“大胆设想,小步前进”。“小步前进”已经成为基本的工作节奏了,这个阶段的重点是“大胆设想”。
软件的目标系统有它运行的逻辑,而设计的目标就是寻找最恰当地表达这个逻辑的方式。这里需要各种可能的尝试,包括头脑层面的,稿纸层面的,乃至写代码层面的,直到找到不凑合,不勉强,不留一点别扭的设计。
在这个尝试的过程中,设计和编码会更大幅度地变动。自动化测试框架,和持续运行的测试用例集就更不能缺了。
在实际的项目中,对优美设计的追求,总是与赶进度的压力并存,所以这时做好心理的建设非常重要。除了强大的心理承受能力之外,还得有对项目前景的预见性。经常需要做决定,是奋勇向前,还是暂时妥协?抽点时间读一读这本《OO项目求生法则》可以帮到我们。
JSON过滤库是测试驱动开发的又一个实际项目的例子。
4. 自由王国 - 将设计模式的运用扩展到整个项目
评价一个方法是否适合的一个重要标准,是它是不是能带来远超付出的收益。所以测试驱动开发并不是适合所有的项目。一些对环境高度依赖的代码就是这样,比如socket编程,数据库编程等,这些领域的测试用例都依赖人工执行。Mock Object能用于模拟环境,但并不适合测试环境本身。
测试驱动开发在这里的意义在于,用它训练出来的头脑已经能轻松应对这些挑战了。这个头脑能:
- 敏锐地识别和整理出有条理和完善的测试集。它与自动测试集很类似,区别只是它是手工执行。回归检查慢一点,总还是能做的。
- 灵活自如地找到恰当的设计。恰当地选择设计模式自然也不在话下。
5. 超越 - 从软件项目联想到其他领域
这个阶段是务虚的,但却是有意义的升华。
Alexander在建筑领域总结了模式语言,《设计模式》的作者在软件领域也总结了类似的思想,哲学上这两个领域是相通的。同样的,它们与其他领域也是相通的。
这个世界以某种符合逻辑的方式组织在一起,我们穷尽一生去摸索,试图搞清楚组织的规律,给出它如何运行的解释。想不到困惑,想到了就豁然开朗。这里想到的就是某种模式。但我们接触的范围总在拓展,所以固守有限的模式显然不够。
从模式起步,成长,最终还要超越模式。就如同张无忌练太极剑法一样,从“剑招”开始,还要超越它,悟到“剑意”。
只听张三丰问道:“孩儿,你看清楚了没有?”张无忌道:“看清楚了。”张三丰道:“都记得了没有?”
张无忌道:“已忘记了一小半。”张三丰道:“好,那也难为了你。你自己去想想罢。”张无忌低头默想。
过了一会,张三丰问道:“现下怎样了?”张无忌道:“已忘记了一大半。”
周颠失声叫道:“糟糕!越来越忘记得多了。张真人,你这路剑法是很深奥,看一遍怎能记得?
请你再使一遍给我们教主瞧瞧罢。”张三丰微笑道:“好,我再使一遍。”提剑出招,演将起来。众人只
看了数招,心下大奇,原来第二次所使,和第一次使的竟然没一招相同。周颠叫道:“糟糕,糟糕!
这可更加叫人胡涂啦。”张三丰画剑成圈,问道:“孩儿,怎样啦?”张无忌道:“还有三招没忘记。”
张三丰点点头,放剑归座。张无忌在殿上缓缓踱了一个圈子,沉思半晌,又缓缓踱了半个圈子,抬起
头来,满脸喜色,叫道:“这我可全忘了,忘得乾乾净净的了。”张三丰道:“不坏,不坏!忘得真快,
你这就请八臂神剑指教罢!”说着将手中木剑递了给他。
......
张无忌这一招乃是以己之钝,挡敌之无锋,实已得了太极剑法的精奥。要知张三丰传给他的乃是“剑意”,
而非“剑招”,要他将所见到的剑招忘得半点不剩,才能得其神髓,临敌时以意驭剑,千变万化,无穷无尽。
倘若尚有一两招剑法忘不乾净,心有拘囿,剑法便不能纯。
相关链接
测试驱动开发与设计模式 - 为什么使用测试驱动开发
测试驱动开发与设计模式 - 从入门到精通
测试驱动开发与设计模式 - C++书籍及网站
测试驱动开发与设计模式 - 适应并改进软件设计过程
测试驱动开发与设计模式 - 让“理想结构”与“快速变更”并行
测试驱动开发与设计模式 - 提速 — 在纸上做细节设计
测试驱动开发与设计模式 - 开发实例一 DVR-POS库
测试驱动开发与设计模式 - 开发实例二 JSON过滤库
测试驱动开发与设计模式 - 开发实例三 rs_driver库