--- # V1 - Create project TestInfected - Add new TestCase class MoneyTest - package Money - add JUnit 4 to project --- package money; import static org.junit.Assert.*; import org.junit.*; public class MoneyTest { // fixtures private Money f12CHF; private Money f14CHF; @Before public void setUp() { f12CHF = new Money(12, "CHF"); f14CHF = new Money(14, "CHF"); } @Test public void testEquals() { assertNotNull(f12CHF); assertEquals(f12CHF, f12CHF); assertEquals(f12CHF, new Money(12, "CHF")); assertFalse(f12CHF.equals(f14CHF)); } @Test public void testSimpleAdd() { Money expected = new Money(26, "CHF"); Money result = f12CHF.add(f14CHF); assertEquals(expected, result); } } --- - Eclipse suggests to create Money class - Add the class; add methods till the test runs --- package money; public class Money { final int amount; // NB: package scope final String currency; public Money(int amount, String currency) { this.amount = amount; this.currency = currency; assert invariant(); } protected boolean invariant() { return amount > 0; } public Money add(Money other) { assert this.currency.equals(other.currency); return new Money(amount + other.amount, currency); } public boolean equals(Object anObject) { if (anObject instanceof Money) { Money other = (Money) anObject; return (amount == other.amount) && (currency == other.currency); } else { return false; } } public int hashCode() { return currency.hashCode(); } } --- # V2 - Add new MoneyTest tests --- public class MoneyTest { ... private Money f7USD; private Money f21USD; private MoneyBag fMB1; private MoneyBag fMB2; @Before public void setUp() { ... f7USD = new Money( 7, "USD"); f21USD = new Money(21, "USD"); fMB1 = new MoneyBag(f12CHF, f7USD); fMB2 = new MoneyBag(f14CHF, f21USD); } ... @Test public void testBagEquals() { assertNotNull(fMB1); assertEquals(fMB1, fMB1); assertFalse(fMB1.equals(f12CHF)); assertFalse(f12CHF.equals(fMB1)); assertFalse(fMB1.equals(fMB2)); } } --- - Create MoneyBag - Add constructor ... --- package money; import java.util.Hashtable; public class MoneyBag { private Hashtable monies = new Hashtable(5); MoneyBag(Money m1, Money m2) { appendMoney(m1); appendMoney(m2); assert invariant(); } MoneyBag(Money bag[]) { for (Money money : bag) { appendMoney(money); } assert invariant(); } protected boolean invariant() { return monies.size() > 1; } private void appendMoney(Money money) { Money m = (Money) monies.get(money.currency); if (m != null) { m = m.add(money); } else { m = money; } monies.put(money.currency, m); } } --- # V3 - Add new MoneyTest cases --- public class MoneyTest extends TestCase { ... @Test public void testMixedSimpleAdd() { // [12 CHF] + [7 USD] == {[12 CHF][7 USD]} Money bag[] = { f12CHF, f7USD }; MoneyBag expected = new MoneyBag(bag); assertEquals(expected, f12CHF.add(f7USD)); } } --- - Add IMoney interface --- public interface IMoney { public IMoney add(IMoney aMoney); // NB: the following have package scope only IMoney addMoney(Money aMoney); IMoney addMoneyBag(MoneyBag aMoneyBag); } --- - Let Money implement IMoney - Add unimplemented methods --- package money.v3; class Money implements IMoney { ... // Replace: public Money add(...) public IMoney add(IMoney m) { return m.addMoney(this); } public IMoney addMoney(Money m) { if (m.currency.equals(currency) ) { return new Money(amount+m.amount, currency); } return new MoneyBag(this, m); } public IMoney addMoneyBag(MoneyBag s) { return s.addMoney(this); } } --- - Let MoneyBag implement IMoney - Add missing methods --- class MoneyBag implements IMoney { ... private void appendMoney(Money aMoney) { Money m = monies.get(aMoney.currency); if (m != null) { m = (Money) m.add(aMoney); // add downcast } else { m = aMoney; } monies.put(aMoney.currency, m); } MoneyBag(Money m, MoneyBag bag) { appendMoney(m); appendBag(bag); assert invariant(); } MoneyBag(MoneyBag m1, MoneyBag m2) { appendBag(m1); appendBag(m2); assert invariant(); } public IMoney add(IMoney m) { return m.addMoneyBag(this); } public IMoney addMoney(Money m) { return new MoneyBag(m, this); } public IMoney addMoneyBag(MoneyBag s) { return new MoneyBag(s, this); } private void appendBag(MoneyBag aBag) { for (Money m: monies.values()) { appendMoney(m); } } } --- - Test still broken --- # V4 --- - Add MoneyBag.equals --- class MoneyBag implements IMoney { ... public boolean equals(Object anObject) { if (!(anObject instanceof MoneyBag)) { return false; } Set myMoneySet = new HashSet(monies.values()); MoneyBag other = (MoneyBag) anObject; Set otherMoneySet = new HashSet(other.monies.values()); return myMoneySet.equals(otherMoneySet); } public int hashCode() { return monies.hashCode(); } } ---