第一次听说测试驱动开发,是从公司同事的口中。据说:他从来都是先写测试用例,而后再写实际代码的。结果是他的代码Bug很少。我听到的第一印象是:这是一个对代码有高度责任心的人,和代码重构一样,这显然是一个态度问题,有责任心的代码自然会质量很高。
再次接触到测试驱动开发,是开始写Java代码了,因为第一次开始写XUnit代码(以前写Windows程序时,从未写过),发现JUnit似乎没啥内容,再想到驱动二字,似乎牵扯到软件设计。于是,很有名气的这本书就上了我的案头。
这本书不厚,共160页,我估计花了有总计10小时,我翻完了,为啥说翻,因为可能我的底子太差(本书实际上是一本软件工程相关的书,而这方面我很差),也有可能存在一些翻译上的问题,让我只能走马观花了一下。我想,等实践几个月,我会再重看一遍。
当然,也有一些收获,下面零乱的记下了一些:
一:看看什么是TDD(测试驱动开发):
1.1:TDD的定义:
T(Test测试):注意,这里指的是自动测试,也就是XUnit的代码。并不是一些Demo程序。至于一些应用级的测试我认为并不是用来驱动开发的。
D(Driven驱动):简单的讲,首先是个时间先后顺序,先测试再开发。更进一层讲,就是不断的测试可以让你的开发持继进行,直至完成。对于这种说法,有点神话,信不信在个人,反正我觉得是吹牛。在你确定测试用例时,实际上一些其它的相关知识是必需的,并非完全是测试的功劳。
D(Development开发):这里的开发,肯定不是指的整个项目。按我的理解,它只是开发中的一个具体的模块,但它不具备传统开发的阶段性思考方式,它象是A&Q中的Q。
1.2:TDD带来了什么?
它会带来整个开发的成功吗?不是,其实它带来的是一份整洁的解决方案,更具体一点,就是一份整洁(易读,少虫)的代码,从长远来讲,它降低了整体的开发成本(想想现在每个项目的维护周期吧)。
说到TDD的作用,其实全书对我感触最大的是下面一段话(当然,它看起来仅仅象是广告):
年轻的工程师们总是满怀热情的开始一个项目,然后看着代码随着时间的流失而腐烂,一年后,他们一心只想丢掉自已变味儿的代码,转道开发别的项目。TDD可以让你随着时间的流逝对代码依然信心依旧……
1.3:什么场合使用TDD?
我的开发进行了一半,或者我的工作是在原有代码基础上进行,这时,我知道了TDD,现在可以吗?书中讲到了一个重要的原则:如果不是必须要修改的部分,你最好相信它的正确性,而尽量不要去动原有的代码。
1.4:似乎漏了一点,也是最神秘的一点,设计在哪里?
书上讲了:设计往往让你意想不到,合情合理的设计思想往往最终被证明是错误的,最好是只考虑你希望系统所完成的功能,让好的设计逐渐自已“浮出水面”。
书上还讲了:传统的开发方式是:“编码为今天,设计为明天”,TDD则是:“为明天编码,为今天设计”。
嘿,你相信吗?说得很玄,并且很煸情,不管怎样,我决定试试先。
二:如何进行TDD?
现在在设计领域推崇模式,同样,TDD也有模式。但就象四人帮提供的代码设计模式一样,不实践,它永远是别人的,所以,书上讲的很多模式,我并不能理解。但我也相信,一本大奖作品不会浪得虚名,因此,我估且将它记下来,慢慢再去体会。
2.1:测试的准备:
A:测试用例相互之间要独立,不互相影响。
B:建立一个测试列表,它会使工作不那么乱七八糟的。
C:先写测试代码,再写被测试的代码。
D:写测试代码时,一开始就写断言。
E:测试数据的选择:要易懂,一目了然。魔数,不要紧,因为它只存在于单独测试中,只要能简单体现数字间的关系,用吧。
2.2:测试处于不可运行期:
书中在这部分是讲如何开始测试,比如如何开始第一步,如何让测试一断进行,什么时候该休息。这些都有些平淡,其中,我认为最关键一个建议是:
回归测试:当一个错误被发现时,你首先应该写一个尽可能小的会失败的测试,然后,再开始修改错误。
OK,如果按上面的要求办了,那你的测试案例才真的可以进行回归测试。
2.3:好了,该说测试的模式了:
这些模式没啥逻辑关系,列举一下吧:
A:子测试——如何将大测试分解成多个小测试,太大不是一个好现象。
B:模拟对象——这是一个捷径,它能使测试代码更易懂,但要注意确认它是否真的适用。前阵子,我在写一个调度程序时,用Mock来测试它的可行。
C:自分流——通过使用测试用例自身的接口实现来保证用例的简单化和可读性。
D:日志字符串——用来验证方法的执行情况(序列的通知)。
E:清扫测试死角——也就是对异常的测试方式,这在XUnit的相关介结里有。
F:不完整测试——如果要离开,先使剩下的测试不完整。
这个我想多说两句,实际上这也是一个工作方法问题,有时,你想抛开一切去休假,但注意:最好留下一些休假前工作状态的痕迹,以便休假后能顺利接轨。
G:提交前保证所有测试都通过——这个不用多说。
2.4:好了,如何让测试到达可运行状态:
对于这个,书中讲了一些方法,
伪实现法:用伪实现来快速达到可运行状态。一句很好的比喻:
伪实现就象登山时在头的上方钉一个登山用的钢锥,实际上你没有到达那儿(测试到了那个地方,但代码是错误的),但是,当你到达那儿时,你就知道你是安全的(测试还将继续进行下去)。 三角法:用多个例子来推动,抽象出正确的实现。 显明实现:对于简单的,直接实现。唉,这不废话嘛。 从一到多:对于对象集合体,可以先在非集合中实现,然后使之作用于集合体。 2.5:至于xUnit的测试模式,很简单,不列举了。 2.6:写测试也是编码,当然会用到设计模式。 下面只说说其中几个比较特殊的。 值对象:一种类似值的不可变对象,以保证测试的独立性,以及解决别名问题。 空对象:用空实现的对象来代替null,减少 != null 的判定。 插入式选择器:这个利用反射来进行动态调用,取代多态,来减少Switch case。 收集参数:用来收集散布于若干对象中的操作结果。 2.7:TDD的过程是一个 不可运行/可运行/重构的过程,所以,最后我们说重构。 针对测试代码,书中提出几个会常用到的重构方法: A:调和差异——也就是消除看起来相似代码的重复。 B:隔离变化——对要修改的地方进行隔离。 C:数据迁移——临时制作冗余数据,一步一步实现迁移。 D:提取方法——通过提取方法,使代码更易读。 E:内联方法——把方法本身内联起来,简化繁琐的控制流程。 F:提取接口——要添加新的实现时,创建新的接口。 G:转移方法——把方法放到它最合适的类里。 H:方法对象——将拥用复杂参数的方法变为一个对象。 I:添加参数——这也讲? J:将方法中的参数移到构造函数中。 不容易,总算把书从头再翻了一次。说实话,感觉收获仍然不多,不过,这种软工的东西,确实需要在实践中去领悟。