TDD by Example: Chapter 13 Make it

Summary

While introducing Expression in the last chapter Beck hardcoded an implementation to get the tests to pass. In the chapter he works toward putting in a real implementation. With a passing test he normally would refactor by replacing constants with variables but he doesn't see how to do that with the current implementation of Bank.reduce.

class Bank {
	Money reduce(Expression source, String to) {
	   return Money.dollar(10);
	}
}
				

He decides to "move forward". This means that Money.plus() should return something that implements Expression. He calls that something Sum. He adds a test testPlusReturnSum():

void testPlusReturnsSum() {
	Money five= Money.dollar(5);
	Expression result= five.plus(five);
	Sum sum= (Sum) result;
	assertEquals(five, sum.augend);
	assertEquals(five, sum.addend);
}

He mentions that this test is not suppose to live long because it is concerned with what kind of class the Money.plus returns instead of what it does. The kind of class is an implementation detail.

He gets testPlusReturnSum() to pass by implementing Sum as an Expression and have Money.plus return Sum.

class Sum implements Expression {
	Money augend;
	Money addend;
    Sum(Money augend, Money addend) {
	   this.augend= augend;
	   this.addend= addend;
	}
}
class Money implements Expression {
	Expression plus(Money addend) {
  	 return new Sum(this, addend);
	}
}

He then keeps moving forward to add to work on Bank.reduce. He adds a test that checks that given a Sum where both the addend and the augend are the same currency the Bank will just do simple addition without any currency conversion.

public void testReduceSum() {
	Expression sum= new Sum(Money.dollar(3), Money.dollar(4));
	Bank bank= new Bank();
	Money result= bank.reduce(sum, "USD");
	assertEquals(Money.dollar(7), result);
}

He gets this test to pass by writing some "ugly" code.

class Bank {
	Money reduce(Expression source, String to) {
		Sum sum= (Sum) source;
   		int amount= sum.augend.amount + sum.addend.amount;
	 	return new Money(amount, to);
	}
}

This code is considered "ugly" because of the input Expression is cast into a Sum and calculation is has to dig through two class to instance variables to happen, sum.augend.amount.

He has a green test so he refactors. He refactors by moving the body of the method toSum.

class Bank {
	Money reduce(Expression source, String to) {
		Sum sum= (Sum) source;
		return sum.reduce(to);
	}

}

class Sum implements Expression {
	Money augend;
	Money addend;
    Sum(Money augend, Money addend) {
	   this.augend= augend;
	   this.addend= addend;
	}
	public Money reduce(String to) {
		int amount= augend.amount + addend.amount;
		return new Money(amount, to);
	}
}

He wants to remove the type cast in Bank.reduce but doesn't have a reason too since the tests are green. He adds a test that he thinks will force him to remove the casting.

public void testReduceMoney() {
	Bank bank= new Bank();
	Money result= bank.reduce(Money.dollar(1), "USD");
	assertEquals(Money.dollar(1), result);
}

He gets the test to pass by checking the type of the class passed into reduce.

class Bank {
	Money reduce(Expression source, String to) {
		if (source instanceof Money) return (Money) source;
		Sum sum= (Sum) source;
		return sum.reduce(to);
	}
}

This is even worse than the casting so he introduces polymorphism to avoid having to do that. He adds reduce to the Expression interface and implements reduce in Money. This means Bank.reduce can just call reduce on whatever is passed in.

interface Expression {
	Money reduce(String to);
}
class Money implements Expression {
	public Money reduce(String to) {
		return this;
	}
}
class Bank {
	Money reduce(Expression source, String to) {
		return source.reduce(to);
	}
}

This prepares the way to change one currency to another.