TDD by Example Chapter 7 and 8

Summary

Chapter 7 is about noticing that the current implementation of Money, Dollar and Franc make it so that new Franc(5) == new Dollar(5). This is obviously wrong, so Beck writes a test to assert that new Franc(5) !== new Dollar(5). He gets this test to pass by changing the implementation of equals in Money to check the class of the object as well as the amount. He notes that checking the class of the object is a smell but there isn't a good enough reason to add more design now.

Chapter 8 is one of the more complicated refactoring that Beck has done. I'm going to try to summarize it here.

He notices that the implementation of the times for Dollar and Franc are very similar.

Franc times(int multiplier) {
        return new Franc(amount * multiplier);
      }

      Dollar times(int multiplier) {
        return new Dollar(amount * multiplier);
      }
              

Following the same tactic mentioned in Chapter 6 of removing duplication by increasing duplication he starts by making both the implementations return Money.

Money times(int multiplier) {
         return new Franc(amount * multiplier);
      }

      Money times(int multiplier) {
         return new Dollar(amount * multiplier);
      }
              

Things get more complicated after this. It would be nice to just move the implementation up to Money but that would break a lot of tests and would not be a good demonstration of how TDD supports refactoring in small safe steps that keep the tests green.

He decides to tackle the problem of the tests first. The tests know the names of Dollar and Franc and that couples them to a concrete implementation of an abstract class. He creates factory methods for Dollar and Franc in the Money and adds a declaration of times in Money.

abstract class Money
      abstract Money times(int multiplier);
      static Money dollar(int amount) {
        return new Dollar(amount);
      }
      static Money franc(int amount) {
        return new Franc(amount);
      }

This means that the tests can be re-written to remove all direct mention of Dollar or Franc.

public void testMultiplication() {
        Money five = Money.dollar(5);
        assertEquals(Money.dollar(10), five.times(2));
        assertEquals(Money.dollar(15), five.times(3));
      }
      public void testEquality() {
        assertTrue(Money.dollar(5).equals(Money.dollar(5)));
        assertFalse(Money.dollar(5).equals(Money.dollar(6)));
        assertTrue(Money.franc(5).equals(Money.franc(5)));
        assertFalse(Money.franc(5).equals(Money.franc(6)));
        assertFalse(Money.franc(5).equals(Money.dollar(5)));
      }

After all of this, we still haven't removed the duplication of times() in Dollar or Franc but it prepares the way for it.

Commentary

It would be fair to ask what's the point of Chapter 7. All Beck does is fix inequality in about two paragraphs. It could easily have been part of chapter 8. Why make a point of showing himself leaving in a code smell?

Checking the class of the object is an implementation detail. It's something that can be changed later with more information and changed confidently because we have a test that tests the desired behavior and not the implementation. I wish he had pointed that out. I don't think it would have hurt to reinforce the idea that when you have tests, good tests, that check the external behavior and isn't tied to an implementation you can confidently put off "clean code" until you know what clean should look like. I had to think about the reason for chapter 7 before it became clear to me. I could be wrong.

Chapter 8 is unfair for a beginner. He sneaks in an important design concept but doesn't take the time to explain why it's important. If I had not come across the idea in other contexts I would not have spotted it.

Chapter 8 is a great example for the experienced. It show how you can test the interface or the role an object plays in the system without your test having knowledge of any concrete implementation. This allows you to add concrete implementations without having to change your tests of the role they are suppose to play. You can decide to just test what's special about that implementation only.

Beck explains the concept here:

No client code knows that there is a subclass called Dollar. By decoupling the tests from the existence of the subclasses, we have given ourselves freedom to change inheritance without affecting any model code.

If you are concerned with the role an object is suppose to play in both your tests and your production code then making changes is a lot easier. You can use polymorphism to create new objects that play the same role(have the same interface) to add new features to your application. This is one of the major benefits of Object-Oriented Design. It's the Liskov substitution principle. Beck introduces it without pointing that out.

Chapter 8 might show an anti-pattern. To remove the knowledge of the subclasses from the tests Beck introduces factory methods in the Money super that create instances of Dollar and Franc. I don't think he mentions how these factory methods are going to be used anywhere else, so it might just be something that makes it easier to test, not something that's going to be used anywhere else. Though it seems plausible that now that the factory method are there, nothing else in the application needs to know about about anything other than Money and that you can get different kinds of Money by using the factory methods on it. Chapter 8 might show a great pattern.