读
TDD
的总结
读完TDD(测试驱动开发),发现其中主要就是在反复说了这么两件事情(也就是两个简单的规则):一、在写任何代码之前,写一个会失败的自动测试程序,即单元测试;二、消除重复设计,优化设计结构,即重构。整本书都围绕着这两个规则来进行说明,告诉读者到底要如何这样做?又如何分阶段应用这些规则?这两条简单的规则可以运用多深?
第一部分通过一个简单的实例来告诉读者如何使用TDD,如何反复通过“不可运行/可运行/重构/不可运行/可运行/重构”来进行开发;第二部分告诉读者如何使用单元测试,怎样组织单元测试;第三部分则是将TDD分解成较小的步骤来进行详细说明,根据我对本书的理解,我认为需要完全将TDD运用到日常的开发行为中,则要按照以下非常细小的步骤进行:
1.
获得任务。即项目经理安排的任务。任务往往不止一项。将它们写入周工作计划表或者月工作计划表。
2.
选择任务。每天开始工作之前,将今天将要解决的问题写入工作计划列表。并选择自己最有把握完成的任务作为当前任务。
3.
分析并分解任务。将当前任务分解成一个个相对简单的问题,分解的问题最好是能在十多分钟之内能完成,并将它们写入工作列表。如:若要实现多币种相加,则可以分解为:实现相同币种之间的相加,不同币种之间的互换,最后才能实现不同币种之间的相加。
4.
写测试列表。将分解后的问题所对应的测试写入测试列表。
5.
选择测试。从测试列表中选择自己认为最有把握实现的一项。如:“同币种相加”对我来说是最有把握实现的一项,因此我最先来实现它,即先从它的测试程序开始编写。
6.
编写测试数据。写一个容易让人理解的必须实现的测试数据列表,尽量使用显然数据。如:用
String
表示
IP
地址
"255.255.255.255"
转换为
int
,在我们写测试数据时应这样写:
(255 << 24) + (255 << 16) + (255 << 8) + 255
,而不是写“
-1
”。
7.
编写测试。选择一项最容易让测试通过的测试数据加入测试方法。断言优先,然后加入为了让断言通过编译的一切准备条件。
8.
运行测试,不可运行状态。
9.
编写方法注释,把所有想到的该方法要实现的功能写上。
10.
编写功能代码,使之达到可运行状态。
11.
重构,消除重复设计。
12.
往测试方法中增加一个新的小测试。
13.
运行测试,不可运行状态。
14.
修改功能代码,使之达到可运行状态。
15.
重构。消除重复设计。
16.
重复
12-15
。
17.
当无论如何也不能让该测试通过的时候,认真想一想是不是哪里出大问题了,如果实在想不出来的话,就将现有代码扔掉,重新开始。
18.
所有测试都运行通过之后,仔细检查所有代码,看是否还有值得重构的地方,并重构。
19.
提交
(check in)
。
20.
再选择测试列表中的下一个测试。重复以上步骤。
21.
当一天的工作结束时,若有某个任务未完成,则留下一个不完整测试,以便于次日能迅速回忆起当时写该代码时的想法,并接着写下去。
以下是我对本书中某些观点的看法:
1.
本书旨在培训我们具有按照极小的步骤进行开发,并寻找Bug的能力,并不是说一定要按如此小的步骤进行。
2.
书中所反复强调的“测试优先”,“断言优先”是说在功能代码之前先写测试,先写断言,可是反过头来,以第一个测试来说:
public void testMutiplication()
{
Dollar five = new Dollar(5);
five.times(2);
assertEquals(10,five.amount);
}
就这个测试来说,其实作者在写测试的时候也想了很多的问题:把这个测试放哪?总不可能拿记事本来写吧!至少该给这个测试方法先建立一个测试类吧!同样在建立这个测试类的时候,是不是也在想着为该类命什么样的名?再进一步,我要写测试方法,在为测试方法命名的时候实际上也是在为我们所要写的方法命名,不是么?因此,我觉得,在我还没有达到Kent Beck的水平之前,我还是会在写测试之前先想一想即将要编写的方法应归于哪个类,并先将该类创建好,再把下一步要编写的方法写好(当然此时肯定也是一个空方法),同时把自己所能想到的方法将要实现的功能(可以是比较粗略的)全部写入方法注释中,此时的注释可以是比较随意的,因为在把方法写完之后还要进行重构嘛!然后按照注释中的功能来编写一个一个的测试方法并完善功能代码。
3.
如果按照本书所说的,开发过程中几乎没有事先的设计,因为作者认为良好的设计会在重构中逐渐浮出水面的。但是我很清楚,我不是Kent Beck,如果没有事先设计的话,以我现在的水平,重构时的工作量一定会非常大;因此如果在写代码之前花一定的时间设计一下代码结构,可以在一定程度上减小重构时的工作量。并且我认为良好的事先设计会为重构工作减少很大的工作量,当然事先设计也不应该占有太多的时间,否则,还不如先写完之后慢慢来重构。
4.
书中所说的关注当前工作这一点非常好,因为在现实工作中,常常会因为想到与当前工作相关的事情可能会比较多,而往往我们会丢下手上的工作,先去完成另外一项工作,可是等回到刚才丢下的工作前时又要花很长的一段时间来回忆当时的情形。因此,我们应该学习书中的方法,把新想法记录下来,等完成当前工作后再去考虑。也许有些时候会出现这种情况,如果另外一件事情未完成,当前工作无法进行的情况不得不打断当前工作,那就不得不将注意力全部转移到另外一件事情上去。但是,如果这种打断出现多次的时候,就应该认真思考自己的分析问题的粒度是不是太大了,然后重新去分解问题。等问题分析到可以不依赖未编写过的方法时再继续工作也不迟。这样只会提高工作效率和自信心。否则,不停地从当前工作中跳出,不仅为影响工作效率,而且会影响自信心,为什么还有这么多没有完成呢?因为在你不断地跳出当前工作后,你就会留下一个个未完成的工作。你的脑子里就会想着还有多少个未完成的工作在等着自己去完成。
5.
留下一个不完整测试是在编程期间结束工作的最好办法。因为当测试运行时没出现绿色状态条时,会很容易找到明显的地方重新开始。这一点实现起来可能有点困难,但是却是一个非常有效的办法,当工作任务繁多的时候常常会忘记昨天做到哪了,今天该从哪做起。
6.
当编写功能代码时,为了让测试通过可以采取的措施有:显明实现,伪实现,三角法,从一到多。当自己让该测试通过有把握的时候,即使用显明实现;若在显明实现的过程中,无法达到可运行状态,则返回到之前的可运行状态,采用伪实现的方法使测试达到可运行状态;当需要为集合编写测试的时候,采用从一到多的方法。如:数组求和则可以先测试该数组中只有一个元素,再逐渐增加元素进行测试。
7.
相互独立的测试。即每个测试之间必须互不干扰。如果一个测试失败必须只对应一个问题;两个失败就对应两个问题。测试之间都不依赖与运行顺序,一个测试不要因为前面的测试不存在而失败。这一点实现起来其实也相当的困难,因为当一个底层函数的测试失败时,其上层函数(即调用了它的那些函数)的测试程序都可能会失败,因此很难做到一个失败的测试对应一个问题。
8.
当在编写代码时,当第一次准备使用某个包内的某项新功能时,为其编写测试,即学习测试。看它是否满足我们期望的要求。并且必须在其符合我们的期望要求时我们才可以在程序中使用它。
9.
当每新建一个包时,即新建一个AllTests类用于测试该包中所有测试类。在每写一个测试类时,先将public static Test suite()写好,并把该测试类加入到包中的AllTests中,随时确保包AllTests中包含了包中的所有测试程序。
10.
需要重点掌握的重构方法(即常用的重构)有:重命名,提取方法,添加参数,删除参数,方法内联,提炼方法对象,消除临时变量,提取匿名类,分离内部类等等。
11.
在我们工作的过程中,很多时候会遇到一些很困难的问题,在反反复复思考许多遍还是想不到解决的办法,往往这样的情况是我们的思维陷入了错误的陷阱里,在这个时候我们可以选择休息一会,喝一杯茶,走动一下。抛开那些刚才所做的事情。如果在休息了多次之后还没有想到解决方案,就扔掉这些代码,重新开始。
|