Say Griphook the Goblin, the head teller at Gringgots Wizard Bank, wants to bring Gringgots into the modern world using the magic of technology. He first thinks about how to model a transaction between two different individuals, and wants to use a test driven approach in order to do so. (He’s heard under the influence of Veritaserum – the truth potion – that it’s the best way to do things)

Step 1

Following the golden rule, we cannot write a single line of code without first having a failing test that requires it, so…

…we write the test first:

public class GringgotsTransactionTest {
@Test
public void transferring50GalleonsResultsInBalancesChangingCorrectly() {
    int galleonAmount = 50;
    int payerBalance = 0;
    int receiverBalance = 0;
    payerBalance += 100;

    payerBalance -= galleonAmount;
    receiverBalance += galleonAmount;

    assertEquals(50, payerBalance);
    assertEquals(50, receiverBalance);
    }
}

Here, we’ve simply performed the entire implementation inside the test! We’ve broken one of the commandments, so let’s get to the refactoring stage (we’ve not had a failing test to make green yet, so no code at this stage)

Step 2

If we look closely, we see that actually we are just crediting an account with different amounts each time:

  • payer credited by 100
  • payer credited by -50
  • receiver credited by 50

This is duplication – an instant code smell – so let’s refactor to extract a credit method out.

public class GringgotsTransactionTest {
    @Test
    public void transferring50GalleonsResultsInBalancesChangingCorrectly() {
        int galleonAmount = 50;
        int payerBalance = 0;
        int receiverBalance = 0;
        payerBalance = creditAccount(payerBalance, 100);

        payerBalance = creditAccount(payerBalance, -galleonAmount);
        receiverBalance = creditAccount(receiverBalance, galleonAmount);

        assertEquals(50, payerBalance);
        assertEquals(50, receiverBalance);

    }

    private int creditAccount(int balance, int galleonAmount) {
        balance += galleonAmount;
        return balance;
    }
}

Here we find another code smell – primitive obsession – or the use of primitives (the integer balances) being used and passed into a method when we should be using small objects (like vaults/accounts). Let’s extract the method out into a separate Vault class:

public class Vault {

    private int balance;

    int creditAccount(int balance, int galleonAmount) {
        balance += galleonAmount;
        return balance;
    }
}

public class GringgotsTransactionTest {
    @Test
    public void transferring50GalleonsResultsInBalancesChangingCorrectly() {
        int galleonAmount = 50;
        int payerBalance = 0;
        int receiverBalance = 0;
        payerBalance = new Vault().creditAccount(payerBalance, 100);

        payerBalance = new Vault().creditAccount(payerBalance, -galleonAmount);
        receiverBalance = new Vault().creditAccount(receiverBalance, galleonAmount);

        assertEquals(50, payerBalance);
        assertEquals(50, receiverBalance);
    }
}
Step 4

Now we make “balance” a field in the new class, and write a getter for it to encapsulate it in the class. Also, extract the new Vault objects into variables in the test:

public class Vault {
    private int balance;

    int creditAccount(int balance, int galleonAmount) {
        this.balance = balance;
        this.balance += galleonAmount;
        return this.balance;
    }

    int getBalance() {
        return this.balance;
    }
}

public class GringgotsTransactionTest {
    @Test
    public void transferring50GalleonsResultsInBalancesChangingCorrectly() {
        int amount = 50;
        int payerBalance = 0;
        int receiverBalance = 0;
        Vault payer = new Vault();
        Vault receiver = new Vault();

        payerBalance = payer.creditAccount(payerBalance, 100);
        payerBalance = payer.creditAccount(payerBalance, -amount);
        receiverBalance = receiver.creditAccount(receiverBalance, amount);

        assertEquals(50, payerBalance);
        assertEquals(50, receiverBalance);
    }

}
Step 6

We are no longer using the balance passed in as it’s encapsulated in the object, so we can remove this from the method signature:

public class Vault {
    private int balance;

    void creditAccount(int galleonAmount) {
        this.balance += galleonAmount;
    }

    int getBalance() {
        return balance;
    }
}

public class GringgotsTransactionTest {
    @Test
    public void transferring50GalleonsResultsInBalancesChangingCorrectly() {
        int amount = 50;
        Vault payer = new Vault();
        Vault receiver = new Vault();

        payer.creditAccount(100);
        payer.creditAccount(-amount);
        receiver.creditAccount(amount);

        assertEquals(50, payer.getBalance);
        assertEquals(50, receiver.getBalance);
    }
}
Step 7

The two steps below must be done together – we can’t do one without the other, else we don’t have a full transfer.

        payer.creditAccount(-amount);
        receiver.creditAccount(amount);

We can extract this into a “transfer” method, but let’s think carefully about where that method should live.

  • We know that the code should live separately from the tests, so it must go into the new class
  • Does it make more sense for the payer or the receiver to house this method? Technically, it could live in either (either a transferFrom method or a transferTo method), but we need to think about where it is most readable/sensible.
  • Since it’s the payer that initialises the transaction, let’s go with the payer.
public class Vault {

    private int balance;

    void creditAccount(int galleonAmount) {
        this.balance += galleonAmount;
    }

    public int getBalance() {
        return balance;
    }

    void transfer(int galleonAmount, Vault receiver) {
        creditAccount(-galleonAmount);
        receiver.creditAccount(galleonAmount);
    }
}

public class GringgotsTransactionTest {
    @Test
    public void transferring50GalleonsResultsInBalancesChangingCorrectly() {
        int galleonAmount = 50;
        Vault payer = new Vault();
        Vault receiver = new Vault();

        payer.creditAccount(100);
        payer.transfer(galleonAmount, receiver);

        assertEquals(50, payer.getBalance());
        assertEquals(50, receiver.getBalance());
    }
}

There are obviously ways to enhance the implementation, we could for instance add a method making it possible to withdraw money from your Vault, but this is above and beyond the scope of this individual test. Test Driven Design is all about doing just enough to make your tests (i.e. specification) pass. To add the new functionality, write another failing test and go through a similar process to the above.

For now though, Griphook is a very happy customer (or, at least, as happy as a goblin can be!)

<<<< TDD: The Ten Commandments  >>>> TDD: Making The Case for TDD

Advertisements

2 thoughts on “TDD: The Way To Go (An Example Refactor)

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