Reading Kent Beck’s “Test-Driven Development By Example”

This is my third time read. When I read it first time, I didn’t know much of TDD and wanted to grasp its concept. Now, it’s been several years since I learned and practised TDD. TDD is the part of development culture and mandatory pactices. I am thrilled what this book can give me this time. I hope I would catch everything I missed years ago.

“Clean code that works”

This is the goal of Test-Driven Development (TDD). He suggests two very short but practical ways (as usual) to achieve it.

  • Write new code only if an automated test has failed
  • Eliminate duplication

“Test infested” means you learn TDD and find your programming changed for good. This is true. Since I learned TDD, I made it a habit to write small and manageable chunk of code and test it, even when I am not writing any unit test. I love the idea of “baby step” and actually apply it to everywhere possible in my life, even when I do any DIY.

Another thing I like about this book is it is an easy read. It is pleasant to read not because it is simple, but he writes as if he talks to a friend next him. I don’t like manual-style book that tries to cram knowledge in an ordered list where context is lost. Kent Beck gives plenty of context each time.

dollar class

The first test in the first chapter is this.

[TestFixture]
public class TestMultiplication
{
	[Test]
	public void Test_Multiplication()
	{
		var five = new Dollar(5);
		five.Times(2);

		Assert.AreEqual(10, five.Amount);
	}
}

Surprisingly (actually, you wouldn’t be surprised if you are good at DDD), he makes a class of “Dollar”. Dollar is a domain class here. If I write code, usually I would go for “Currency” class, making it more generic. But “Dollar” makes more sense, as it is a domain class. It also has nice Times method and Amount property and it makes the code easier to understand. Business or product people would also use terms like dollar, not currency.

Coding in java

The code example in this book is written in Java. Most of time, I use C# and javascript and C# and java are not very different from each other, so it is not a problem for me to convert the example to C#. But, maybe it’s better to practice coding java. I can re-learn java (I worked hard on Java at Uni, though didn’t have chance to use it commercially) and TDD. It’ll be fun to have a break from a language I use everyday at work.

I know JetBrains make good tools. I use two of their products everyday, ReSharper and TeamCity, so I downloaded IntelliJ IDEA (community edition). Also, I use IntelliJ Resharper mapping, so it wouldn’t be difficult to use it.

.. a few days later

I managed to run the first test in java and junit. Thank God! So, all the examples from now would be in java. This is my recounter with java after 10 years of breakup.

TDD Cycle

  1. Write a test
  2. Make it compile
  3. Run it to see that it fails
  4. Make it run
  5. Remove duplication

Regarding 4. Make it run, you can use two methods.

  • Fake It – Return a constant and gradually replace constants with variables until you have the real code
  • Use Obvious Implementation – Type in the real implementation

This is his regular rhythm of TDD. Fake it with constants if you are not sure. If you are relatively confident, just do the obvious implementation. In fact, I benefited from “Fake It” a lot. Quite often, when I fake it, I discover a kind of pattern in the code and then can replace with variables or methods.

Value object

Value objects refers to a value, like 5 dollar. Once the value is set, it shouldn’t change. So passing the value into constructor makes sense.

  • value is set from constructor
  • all operations return a new object
  • should implement equals()
public class Dollar {
    public int amount;

    public Dollar(int amount) {
        this.amount = amount;
    }

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

    public boolean equals(Object object) {

        Dollar dollar =  (Dollar)object;
        return amount == dollar.amount;
    }
}

Triangulation

Generalise the code when we have two examples or more, ignoring the duplication between test and model code. “If the second example demands a more general solution, then and only then do we generalise.”

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

Test refactorings

Quite often, Kent didn’t plan it but when he refactored the tests and simplified them, it became easier to reuse them. He cleaned up Dollar tests and now he creates Franc mulitiplication tests and it is much easier.

// old test
@Test
public void testMultiplication() {
	Dollar five = new Dollar(5);
	Dollar product = five.times(2);
	
	assertEquals(10, product.amount);

	product = five.times(3);
	assertEquals(15), product.amount);
}

//simplified test
@Test
public void testMultiplication() {
	Dollar five = new Dollar(5);

	assertEquals(new Dollar(10), five.times(2));
	assertEquals(new Dollar(15), five.times(3));
}

// new franc test based on the simplified ones.
@Test
public void testFrancMultiplication() {
	Franc five = new Franc(5);

	assertEquals(new Franc(10), five.times(2));
	assertEquals(new Franc(15), five.times(3));
}

So, refactoring test is as important as cleaning up model code.

Domain-centered code

Here is a test that compares Franc to Dollar.


@Test
public void testEquality() {
    assertTrue(new Dollar(5).equals(new Dollar(5)));
    assertFalse(new Dollar(6).equals(new Dollar(5)));

    assertTrue(new Franc(5).equals(new Franc(5)));
    assertFalse(new Franc(6).equals(new Franc(5)));
    
    assertFalse(new Franc(6).equals(new Dollar(6)));

}

The test fails, as we just compare the amount. It things Franc(6) is the same with Dollar(6), which is obviously wrong. The simplest thing to fix it is to check the class type like this.

public boolean equals(Object object) {

	Money money =  (Money)object;
	return amount == money.amount && getClass().equals(money.getClass());
}

This works, but not elegant. Why? It’s because we would like to use “a criterion that makes sense in the domain of finance, not in the domain of Java objects” (bold by me, not by the author of the book)

Use factory method to hide class in test code

By hiding a class in the test code, you have more flexibility to do anything with that class. You can avoid referencing a concrete class by using a factory method.


// before
@Test
public void testEquality() {
    assertTrue(new Dollar(5).equals(new Dollar(5)));
    assertFalse(new Dollar(6).equals(new Dollar(5)));

    assertTrue(new Franc(5).equals(new Franc(5)));
    assertFalse(new Franc(6).equals(new Franc(5)));
    
    assertFalse(new Franc(6).equals(new Dollar(6)));

}

// after
    @Test
    public void testEquality() {
        assertTrue(Money.dollar(5).equals(Money.dollar(5)));
        assertFalse(Money.dollar(6).equals(Money.dollar(5)));

        assertTrue(Money.franc(5).equals(Money.franc(5)));
        assertFalse(Money.franc(6).equals(Money.franc(5)));

        assertFalse(Money.franc(6).equals(Money.dollar(6)));

    }


Start from concrete classes and abstract them over time

“Start small and simple” is a maxim you often hear in life, and Kent begins with simple classes like Dollar and Franc. He doesn’t try to design Money class from the beginning. They have different state (like rate and currency), though share the same behaviour (times). Over a period of time, he abstract them into one Money class. The resulting class looks like the perfect “Money” class to me, I mean, you can subtract any from it any more. It doesn’t have anything unnecessary. During the process, he employs a few techniques like Factory method. This is inspirational to me. How often, I struggle with big idea up front, and coding a complex class is a painful process, as you have to go at lengths without tests. Often, the class turns out to have more things stuffed than it requires.

Gradually removing duplication in written code over time guided and verified by tests is much simpler and more pleasant than trying to implement a class that handles different states and behaviour, constantly analysing in your head.

“confidence-giving tests and carefully factored code give us preparation for insight, and preparation for applying that insight when it comes”

The last responsible moment in code

Concurrent software development means starting development when only partial requirements are known and developing in short iterations that provide the feedback that causes the system to emerge. Concurrent development makes it possible to delay commitment until the last responsible moment, that is, the moment at which failing to make a decision eliminates an important alternative. If commitments are delayed beyond the last responsible moment, then decisions are made by default, which is generally not a good approach to making decisions.

from Lean Software Development

You can make a better decision by delaying the decision. This is now well known practice in agile world. In my opinion, Kent does a similar thing in his code. The code never does calculation until it is absolutely necessary. Let’s have a look at the example of “reduce”, which is a method that converts an amount to the amount of the target currency.

This is the test code.

@Test
public void testMixedAddition() {
	Money fiveBucks = Money.dollar(5);
	Money tenFrancs = Money.franc(10);

	Bank bank = new Bank();
	bank.addRate("CHF", "USD", 2);

	Money result = bank.reduce(fiveBucks.plus(tenFrancs), "USD");

	assertEquals(Money.dollar(10), result);

}

fiveBucks.plus(tenFrancs) should be Money.dollar(10). Usually, I expect “plus” method will do the magic, or job. But it doesn’t. Look at the method body.

public Expression plus(Money addend) {
	return new Sum(this, addend);
}

It just passes the parameters over into “Sum” object. The constructor of the “Sum” only stored those values.

public class Sum implements Expression{
    Money augend;
    Money addend;

    public Sum(Money augend, Money addend) {
        this.augend = augend;
        this.addend = addend;
    }
    ....

Then later, only when “reduce” is called, it actually process the amounts of the currencies and return the value in the target currency.

public class Sum implements Expression{
    ....

    public Money reduce(Bank bank, String to) {
        int amount = augend.reduce(bank, to).amount + addend.reduce(bank, to).amount;
        return new Money(amount, to);
    }
}

So, by delaying the actual calculation, relevant objects store state. It just stores the currencies and amount of the “from” and “to” money. The state never changes, so you can do other operations easily. Those objects are immutable. In Kent’s code, most of the objects are immutable entity. The entity object has methods that emits value, upon request. This is a learning for me. When I design an object, I often hastily change the status, by performing the operation in the method. If I wrote the above code, probably, I would have performed the sum in Money object, returning Money.dollar(10). But it is more useful to have Sum object, as it knows about from and to currencies and amount.

Domain objects in money example

  • Bank: add exchange rates and return a rate of a conversion. “reduce” delegates its real operation to other objects like Money.
  • Money:
Reading Kent Beck’s “Test-Driven Development By Example”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s