TDD 学习笔记(一)

以前看过《TDD by example》,现在基本都忘记光了,大致记得一切从测试开始,然后不断重构,最后不断的迭代就能得到一个优雅简洁的设计。
关于TDD的很多讨论,我这里就不说了。对于写了那么多年代码的人来说,要想转变观念,还是需要更加冷静,多思考。
TDD 中 kent Beck大师教给我们的是一门内功心法,交给我们的是如何把预想的结果转化为设计。

TDD = Test-Driven Development
很多人以为是讲测试的,其实醉翁之意不在测试,而是开发,如何使代码clean
"TDD by example”这本书的风格我很喜欢,直接从例子开始,让然一看就被吸引住。
书中的例子是一个 货币统计的例子。需要有不同的货币,相加等等。
这时,大家肯定就有一个大概在脑子中浮现。然后很快变开始写代码了。
别急,测试驱动开发中,最重要的就是测试清单了。
测试清单,可以说是半需求吧,我们写一个测试清单,这样,我们要做的事情的目的就有了。
Kent Beck建议我们把清单写出来,然后一个一个考虑。这就是to-do lists
[quote]
5美元 * 2 = 10 美元
5美元 等于 5美元
[/quote]

单完成一件事,就用横线划掉。

假设上面的清单,第一项,使用测试的风格写出来吧。

@Test public void testTimes(){
Dollar five = new Dollar(5);
five.times(2);
assertEquals(five.amount,10);
}


这个测试显然通不过,因为很多东西都没有,比如Dollar类,times方法
于是我们一一建立起来。直到没有编译错误。


public class Dollar{
public amount;
public Dollar(int amount){
this.amount=amount;
}
public void times(int multipler){
amount*=multipler;
}


上面的代码不是书中第一步的代码,但是我还是走了这么一大步。所以,这里的问题是,如何衡量每一步我们做的改动。在一个实际的或者很复杂的系统中,我们一般是不可能一步到位的,这也是为什么需要测试驱动开发的一个原因吧。但是这里,我真的觉得自己能写到这里。
我还是shamelessly的表现自己很聪明吧。
OK,一切顺利。

接下来我们看看是否可以重构,消除重复,这里我觉得差不多了。那就多些测试吧。

@Test public void testTimes(){
Dollar five = new Dollar(5);
five.times(2);
assertEquals(five.amount,10);
five.times(3);
assertEquals(five.amount,15);
}


这个测试,连续乘,看看是不是会有意想不到的结果呢?实际上,这种测试是常见的边效应测试(side-effect)。
很好,一切与测试驱动开发的教条很符合,我们找到一个缺陷,并编写测试代码使之不通过测试。接下来我们改做些小改动。这里很明显,我们希望美元是一种独立的数字,也就是说它是不可变的。你不可能把币值为5美元变成15美元吧。那么我们在times的时候,就需要重新生产出来一张新的币值为15的美元(可以理解为电子货币,或者支票)。
好吧,先改动测试。


@Test public void testTimes(){
Dollar five = new Dollar(5);
Dollar tenDollar = five.times(2);
assertEquals(tenDollar.amount,10);
Dollar fifteenDollar = five.times(3);
assertEquals(fifteenDollar.amount,15);
}


这时你发现,times返回值是void的,我们修改。

public class Dollar{
public amount;
public Dollar(int amount){
this.amount=amount;
}
public Dollar times(int multipler){
return new Dollar(amount*multipler);
}
}

这下测试通过。然后来看看,我们有没有什么重复的需要重构的。很多时候,我们可能这样写times方法

public Dollar times(int multipler){
int new_amount = amount*multipler);
return new Dollar(new_amount);
}

如果这样的话,我们就可以使用重构中的Inline Method消除这个临时变量。得到之前我们写的times方法。这就是消除重复。
好了,我们终于划掉清单中的第一项了,接下来看第二项:
5美元 确实 应该等于5美元。那就测试测试:


@Test public void testEqualitiy(){
assertTrue((new Dollar(5)).equals((new Dollar(5))));
}

很遗憾,我们失败了。实际上这比较的是两张5美元,虽然币值一样,但是他们确实不同。这不符合我们的意图。于是想起来了,java中需要重写equals方法。但是我们记不清了,重写equals方法有一些需要注意的。所以这里,我先做小的改动,以通过测试。

public class Dollar{
public amount;
public Dollar(int amount){
this.amount=amount;
}
public Dollar times(int multipler){
return new Dollar(amount*multipler);
}
/*
*I recommand you add @Override if you can't
*remember the method clearly
*/
@Override
public boolean equals(Object obj){
return true;
}
}

这里直接返回ture了。肯定很多人要说了,这肯定是有问题嘛。的确如此,但是别急,我没有你那么有经验,问题只能通过测试来反映。测试通过了。看,没问题吧。哈哈。
书中提到一种triangulation。也就是对一个一眼不能看出的问题,可以使用这种方法。
1,找到一种导致测试失败的用例
2,使用stub来实现,也就是一个占位操作,直接返回或是常量。使测试通过。
3,重复1,但是这次可以用真正的写实现代码。似的通过测试。

这里,我们是在3,重复1,找到一个导致这儿假实现的失败。很容易。我们马上就找到了。

@Test public void testEqualitiy(){
assertTrue((new Dollar(5)).equals((new Dollar(5))));
assertFalse((new Dollar(5)).equals((new Dollar(7))));
}


这下,我们不能使用stub来实现了,只能硬着头皮写代码吧。

@Override
public boolean equals(Object obj){
return this.amount == ((Dollar)obj).amount;
}


测试,通过。很好。第二项也就完了,划掉。不过。一切还远远没有结束。
我们考虑下,amount是公有的,这一点看着很不爽,不符合OO的封装性。
测试代码也很乱,很多重复,由于相等性已经测试完成,我们可以重构下测试代码。
再次使用Inline Method方法,重构如下:

@Test public void testTimes(){
Dollar five = new Dollar(5);
assertEquals(five.times(2),new Dollar(10));
assertEquals(five.times(3),new Dollar(15));
}

清爽多了。接下来,把amount私有化改造添加的to-do lists吧。
还有,另外一种货币,Franc来了。我们也也对他像dollar一样测试。

这里,对amount的私有化,似乎很简单了,因为,由于我们在testTimes方法中,使用了对象的比较。所以,当我们修改amount作为私有的时候,似乎一切都没问题了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值