BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Programming Concepts
.NET 3.5+

Test-Driven Development

Test-driven development is a process that assists in creating high quality, well designed, loosely coupled and maintainable code that can be refactored with confidence. The process relies on writing unit tests before creating the code that they validate.

Iteration Three

For the next test we'll add withdrawals. The test is almost identical to that of deposits. Within the test we'll perform a deposit, then make a smaller withdrawal and check the balance is as expected.

[Test]
public void WithdrawingDecreasesTheBalance()
{
    Account account = new Account();
    Deposit deposit = new Deposit(100M, "Opening Balance");
    Withdrawal withdrawal = new Withdrawal(23.45M, "Withdrawal");

    account.Deposit(deposit);
    account.Withdraw(withdrawal);

    Assert.AreEqual(76.55M, account.Balance);
}

As in the previous iteration, we need a new type (Withdrawal) and a new method in the Account class. The Withdrawal class is as follows:

public class Withdrawal
{
    public Withdrawal(decimal value, string description)
    {
        Value = value;
        Description = description;
    }

    public string Description { get; private set; }

    public decimal Value { get; private set; }
}

The Withdraw method has no code, as we need the test to fail:

public void Withdraw(Withdrawal withdrawal)
{
}

We can make the test pass by adding the code to decrease the balance:

public void Withdraw(Withdrawal withdrawal)
{
    Balance -= withdrawal.Value;
}

We now have an opportunity for refactoring the code to make it cleaner and more readable. Firstly, the Deposit and Withdrawal classes are almost identical. We could introduce a base class for these that represents any transaction.

public abstract class Transaction
{
    public Transaction(decimal value, string description)
    {
        Value = value;
        Description = description;
    }

    public string Description { get; private set; }

    public decimal Value { get; private set; }
}

We can now modify the Deposit and Withdrawal classes to inherit from the Transaction type, as follows:

public class Deposit : Transaction
{
    public Deposit(decimal value, string description)
        : base(value, description) { }
}

public class Withdrawal : Transaction
{
    public Withdrawal(decimal value, string description)
        : base(value, description) { }
}

After these changes we can run the tests as a small regression test suite. All three tests should still pass, showing that we have not introduced any bugs.

One of the problems with the Transaction base class is that the value will always be positive. It would be useful if deposits were positive, as they increase balances, and withdrawals were negative. To achieve this we'll modify the Withdrawal class's constructor, as follows:

public class Withdrawal : Transaction
{
    public Withdrawal(decimal value, string description)
        : base(-value, description) { }
}

Running the tests will now show a failure because the negative value of a withdrawal is subtracted from the balance, increasing the balance rather than reducing it. This can be fixed by modifying the Withdraw method to add, rather than subtract, the value:

public void Withdraw(Withdrawal withdrawal)
{
    Balance += withdrawal.Value;
}

The tests now all pass again. However, making these changes has led to duplication; the contents of the Deposit and Withdraw methods of the Account class are now identical. This duplication could be removed by replacing the two methods with a single operation that can process any transaction. Let's call this method, "RecordTransaction". We'll start by modifying the two existing tests that cover the calls to Deposit and Withdraw, as follows:

[Test]
public void DepositingIncreasesTheBalance()
{
    Account account = new Account();
    Deposit deposit1 = new Deposit(100M, "Opening Balance");
    Deposit deposit2 = new Deposit(23.45M, "Deposit");

    account.RecordTransaction(deposit1);
    account.RecordTransaction(deposit2);

    Assert.AreEqual(123.45M, account.Balance);
}

[Test]
public void WithdrawingDecreasesTheBalance()
{
    Account account = new Account();
    Deposit deposit = new Deposit(100M, "Opening Balance");
    Withdrawal withdrawal = new Withdrawal(23.45M, "Withdrawal");

    account.RecordTransaction(deposit);
    account.RecordTransaction(withdrawal);

    Assert.AreEqual(76.55M, account.Balance);
}

To make the code compile, remove the Deposit and Withdraw methods and add the new method's signature.

public void RecordTransaction(Transaction trx)
{
}

We can make the two failing tests pass by writing code that adds the transaction value to the current balance.

public void RecordTransaction(Transaction trx)
{
    Balance += trx.Value;
}

The code here is not as clean as I would like. For example, it would be better if the value passed into the Withdrawal constructor could be obtained from the created object directly, rather than just being able to get the negated value. Alternatively we might rename the Value property of Transaction and its subclasses to a more appropriate name, such as BalanceAdjustmentValue. For the purposes of this article the current code will suffice.

19 November 2012