测试驱动开发-实例
为已有Money类添加一个新的功能——加法!
package cn.elena.test2;
public classMoney {
private int amount;
private String currency;
Money(int amount, Stringcurrency)
{
this.amount=amount;
this.currency=currency;
}
Moneytimes(intmultiplier)
{
return new Money(amount*multiplier, currency);
}
public boolean equals(Object object)
{
Moneymoney = (Money) object;
return amount==money.amount
&¤cy().equals(money.currency());
}
Stringcurrency()
{
return currency;
}
public static Money dollar(int amount)
{
return new Money(amount,"USD");
}
public static Money Franc(int amount)
{
return new Money(amount,"CHF");
}
}
测试驱动开发,关键是要在写代码之前写好测试用例,开发过程中维护两套代码(测试代码和实际代码)
@Test
public void testSimpleAddition()
{
Moneysum= Money.dollar(5).plus(Money.dollar(5));
assertEquals(Money.dollar(10),sum);
}
写好这个单元测试,马上通过编译器报错就可以生成Money的plus函数
public Money plus(Money addend) {
// TODO Auto-generated method stub
return new Money(amount+addend.amount,currency);
}
要是单元测试通过,只需加上一个return语句。这里没有再一小步一小步的进行伪代码到真实代码的实现。
这里的关键,代表所有货币的Money怎样实现多种币种相加。
解决方法有很多种,在这本书中,解决方案是创建一种行为像是Money类的对象,但是代表两个Money的和。一种比喻,这个类就像一个钱包,各种货币放到钱包中。又或者是一个表达式,Money对象是表达式中无法再继续细分的元素。
@Test
public void testSimpleAddition2()
{
Moneyfive = Money.dollar(5);
Expressionsum = five.plus(five);
Bankbank = newBank();
Moneyreduced = bank.reduce(sum,"USD");
assertEquals(Money.dollar(10),reduced);
}
一个新的测试用例诞生。书中它是从assertEquals开始写,倒序写到five的创建的。这里还有两个编译错误:Expression 和Bank。
Expression定义为一个表达式接口(轻量级)。这时候plus函数需要返回一个Expression。这又意味着Money类要实现Expression
package cn.elena.test3;
public interfaceExpression {
}
package cn.elena.test3;
public classBank {
public Money reduce(Expressionsource, String to) {
// TODO Auto-generated method stub
return Money.dollar(10);
}
}
Money:
public Expression plus(Moneyaddend) {
// TODO Auto-generated method stub
return new Money(amount+addend.amount,currency);
}
明显这里的Bank用了快速通过单元测试的第一种方法:伪代码。
首先:Money.plus()需要返回的是一个真正的表达式对象Sum,而不仅仅是一个Money对象。
新的测试用例又来了。。。。。。。
@Test
public void testPlusReturnSum()
{
Moneyfive = Money.dollar(5);
Expressionresult = five.plus(five);
Sumsum=(Sum)result;
assertEquals(five,sum.augend);
assertEquals(five,sum.addend);
}
通过修改编译错误获得Sum类
package cn.elena.test3;
public classSum {
Moneyaugend;
Moneyaddend;
}
运行测试会产生一个ClassCastException,因为plus函数返回的不是一个Sum对象。
Money:
public Expression plus(Money addend) {
// TODO Auto-generated method stub
return new Sum(this,addend);
}
public classSum implementsExpression{
Moneyaugend;
Moneyaddend;
Sum(Moneyaugend,Money addend)
{
this.augend=augend;
this.addend=addend;
}
}
这样这个单元测试就通过了。。。
现在可以向Bank.reduce()传递Sum对象了,如果Sum中的货币和目标货币都一样,那么结果就是一个Money对象,数目就是总和。仍需写好测试再写实现代码。
@Test
public void testReduceSum()
{
Expressionsum = newSum(Money.dollar(3),Money.dollar(4));
Bankbank = newBank();
Moneyresult = bank.reduce(sum, "USD");
assertEquals(Money.dollar(7),result);
}
Bank:
public Money reduce(Expressionsource, String to){
// TODO Auto-generated method stub
Sumsum = (Sum)source;
int amount = sum.augend.amount+sum.addend.amount;
return new Money(amount ,to);
}
两个坏味道:
强制类型转换
公共域以及对它的二级引用
解决方法:把方法主体移至Sum类且去掉某些可见域。
Bank:
public Moneyreduce(Expression source, String to) {
// TODO Auto-generated method stub
Sumsum = (Sum)source;
return sum.reduce(to);
}
Sum:
public Money reduce(Stringto)
{
int amount = augend.amount+addend.amount;
return new Money(amount ,to);
}
这里有个问题,如果参数传进去一个Money怎么办?依旧。。测试先行。。。
@Test
public void testReduceMoney()
{
Bankbank = newBank();
Money result =bank.reduce(Money.dollar(3),"USD");
assertEquals(Money.dollar(3),result);
}
又是类型转换异常,Sum和Money都实现了Expression接口。
Bank:
public Money reduce(Expression source, String to) {
if(source instanceof Money)
{
return (Money) source;
}
Sumsum = (Sum)source;
return sum.reduce(to);
}
尽管这样实现很难看,但是运行通过。。接下来就可以进行重构。
无论何时,当我们需要显示地判断是那种累才能进行下一步工作时,都要使用多态来代替。
因为Sum实现了reduce方法,如果Money也实现,那么就可以将它添加到Expression中。
Money:
public Money reduce(String to) {
// TODO Auto-generated method stub
return this;
}
Bank:
public Money reduce(Expression source, String to) {
if(source instanceof Money)
{
return (Money) source.reduce(to);
}
Sumsum = (Sum)source;
return sum.reduce(to);
}
添加成功后,就可以消除所有烦人的强制类型转换和类判定。
Bank:
public Money reduce(Expression source, String to) {
return source.reduce(to);
}