In A Previous Post, I talked about how I'm jumping on board the BDD wagon, and I gave an example of a TDD style Unit Test that was close to BDD style, based on how I worded the test. I've decided to test that theory and follow the progression from a standard Unit Test as I was writing them, into a BDD test system such as SpecUnit.NET.
Let's look at a very basic requirement, derived and stolen from the presentation done by Scott Bellware at the Agile Austin User Group meeting that I attended last week.
If an account holder has multiple accounts and one is low on funds, allow the account holder to transfer funds between accounts. If the amount to transfer is greater than the amount in the originating account, then the transfer should not be allowed and the balance of both accounts should remain the same. This will let the account holder avoid overdraft fees on the account with insufficient funds.
A basic TDD implementation may look like this:
[TestFixture]
public class AccountTransferTestFixture
{
[Test]
public void TransferFailsIfRequestedAmmountIsGreaterThanOriginatingAccountsBalance()
double fromAccountStartingBalance = 100;
double toAccountStartingBalance = 0;
double requestedTransferAmmount = 125.95;
Account originatingAccount = new Account(fromAccountStartingBalance);
Account receivingAccount = new Account(toAccountStartingBalance);
TransferStatus transferStatus = AccountTransfer.Transfer(originatingAccount, receivingAccount, requestedTransferAmmount);
Assert.AreEqual(transferStatus.Failed, transferStatus);
Assert.AreEqual(fromAccountStartingBalance, originatingAccount.Balance);
Assert.AreEqual(toAccountStartingBalance, receivingAccount.Balance);
}
There is nothing particularly wrong with this unit test. It looks like decent code - appears to be well organized, makes good use of separated objects, and checks to see that everything is as it should be.
Now let's change up the test a bit and move it towards BDD style, the way I have been writing my unit tests recently. The first thing we'll need to do is change the name of the test. Secondly, let's rewrite the object model to be a bit more clear on what's really going on with the transfer.
public void WhenARequestedTransferAmmountIsGreaterThanTheOriginatingAccountBalance_ThenTheTransferShouldFailAndTheAccountBalancesShouldNotBeChanged()
double originatingAccountBalance = 100;
double receivingAccountBalance = 0;
Account originatingAccount = new Account(originatingAccountBalance);
Account receivingAccount = new Account(receivingAccountBalance);
AccountTransfer accountTransfer = originatingAccount.Transfer(requestedTransferAmmount).To(receivingAccount);
Assert.AreEqual(TransferStatus.Failed, accountTransfer.Status);
Assert.AreEqual(originatingAccountBalance, originatingAcount.Balance);
Assert.AreEqual(receivingAccountBalance, receivingAccount.Balance);
The first thing you'll notice is that the Unit Test's name is significantly closer to the business requirement. This is desired, because it allows a develop and a non-developer to both see what this unit test is really supposed to be doing - how the system should behave when something happens. Secondly, notice the differences in the AccountTransfer object. In the first UnitTest, AccountTransfer was used to attempt the actual transfer and return the status of that transfer. In this test, though, AccountTransfer is an object that is produced by the Account.Transfer method. And lastly, notice the change in APIs to do the actual transfer. Remember that in Test Driven Development, you should be designing the API that you want to call - and then you implement it. I want my API to be very expressive - almost english. This particular change is really a matter of preference, though.
The one thing that has not changed is the list of Asserts. We are still checkign to make sure that the transfer status is "Failed" and that the balance of the originating and receiving accounts is unchanged.
Now let's take this example into BDD land.
The first thing we need to do is make sure we have enough information to actually test the behavior of the system. This involves creating a User Story from the requirement that we were given. I won't go into depth on User Stories, here. Just keep the basic format of a User Story in mind, borrowed from Dan North:
As a [role], I want [Feature], so that [benefit]
Let's apply this to the requirement, above:
As an account holder,I want to transfer money between accountsSo that I can avoid overdraft fees
We can't work on user stores alone, though. The only way a User Story can have any value to a software developer, is when we add Acceptance Criteria to the story. Again, I won't go into detail. Here is the basic format that Acceptance Criteria should follow, borrowed from Dan North, again:
Given [context] [and [some more context], …], When [event], Then [outcome] [and [another outcome], …]
Now let's apply the acceptance criteria to our story
Given a transfer between accounts,When the requested transfer amount is greater than the originating account's balanceThen the transfer should fail and the balance of the originating and receiving accounts should not change
Notice what we have done with the original requirements, compared to the User Story and Acceptance Criteria. Essentially, we have broken the requirement out into two separate parts that provide more value to the business and to the developer. The User Story is the real business value - what do you want to do, and why. The Acceptance Criteria is where the Developer receives the knowledge of how the system should actually work. Both of these are english statements, though. Nowhere in the Story or Criteria, have we specified any language that deals with a computer, software, or any other technical or implementation detail.
With the User Story and Acceptance Criteria separated now, we can write the software and test it. Rather than doing standard Test Driven Development and writing a Unit Test, though; let's focus on Behavior Driven Development and test the behavior of this software using the specified behavior.
This example code is still using NUnit as the test framework, to illustrate that point that you don't need a specific BDD test framework. You can fit BDD style tests into a standard xUnit framework. However, you might feel that it is a little mashed-in and not quite the fit that you want.
namespace Given.A.Transfer.Between.Accounts
public class When_The_Requested_Transfer_Amount_is_Greater_Than_The_Originating_Accounts_Balance
private Account originatingAccount;
private Account receivingAccount;
private AccountTransfer accountTransfer;
[SetUp]
public void ContextSpecification()
originatingAccount = new Account(originatingAccountBalance);
receivingAccount = new Account(receivingAccountBalance);
accountTransfer = originatingAccount.Transfer(requestedTransferAmmount).To(receivingAccount);
public void The_Transfer_Should_Fail()
public void The_Originating_Account_Balance_Should_Not_Be_Changed()
Assert.AreEqual(originatingAccountBalance, originatingAccount.Balance);
public void The_Receiving_Account_Balance_Should_Not_Be_Changed()
Notice what we have done with the Acceptance Critiera - we've directly turned it into the tests. We have specified how the system will behave with the various part of the criteria being mapped directly to the various parts of the tests. The [context] has been turned into a namespace; the [event] has been turned into the text fixture's class name; and each of the [outcome] has become a test.
But, why bother splitting all of the parts of the behavior specification out like this? Why not leave it as the unit test was in the previous example? The answer is really the business value of the test and the readability / grok-ability of the tests for developers. I can show anyone on the team - a business analyst, the customer, or a developer - the overview of this specification, and it should be easily understood. A business analyst or customer may not understand the technical details of the Assert statement, or the reason that we have variables declared. However, if they see the collapsed code, it becomes very obvious that the developer has implemented the requested Acceptance Criteria for the user story.
Now that we've seen how to stuff a behavior's specification into an xUnit testing framework, let's look at how to implement it in a testing framework that is designed for BDD: SpecUnit.NET.
namespace AccountTransferSpecifications
[Concern("Account Transfers")]
public class When_The_Requested_Transfer_Amount_is_Greater_Than_The_Originating_Accounts_Balance: ContextSpecification
protected override void Context()
[Observation]
accountTransfer.Status.ShouldEqual(TransferStatus.Failed);
originatingAccount.Balance.ShouldEqual(originatingAccountBalance);
receivingAccount.Balance.ShouldEqual(receivingAccountBalance);
The major differences between an xUnit framework as a SpecUnit framework, is that the idea of Context is a first class citizen. Aside from that and a few changes to the attribute names used, a SpecUnit test class looks surprisingly similar to the behavior test in NUnit. You'll also notice that we're not using Assert.Equals() anymore. Rather, we're taking advantage of .NET 3.5's Extension Methods and providing a expressive statement. This is somewhat of a syntax preference, though.
We can still compress this code and show it to a business analyst or customer. Even better - SpecUnit.NET comes with a simple report generator that produces an HTML report document based on the specification tests, themselves. The output is essentially a cleaned up version of the collapsed code in an HTML format that anyone can read.
The real difference between an xUnit Unit Test and a SpecUnit Specification Test is semantics and a few mechanics. Whether you are using an xUnit framework or a SpecUnit framework, Specification Testing and Behavior Driven Development have a significant benefit to the business when compared to Unit Testing and Test Driven Development alone. As I've said before, this does not mean that TDD is dead or irrelevant. This is only to say that TDD has evolved - it has grown an attachment to Domain Driven Design and Scrum project management, giving it a new home and a new life on it's own. Consider the following:
A Unit Test has the connotation of testing an individual unit of code - a System Under Test as some have called it.
When you pass the value "123" into object XYZ's, ABC method, the result is a change to Foo's Bar property and Widget's Digitize method was called.
Specification Testing, and Behavior Driven Development, on the other hand are all about behavior.
In the context of XYZ, when ABC is done, then the Foo's Bar has been Digitized.
Which would you rather show a business analyst, or the customer? Chances are you could explain the details of the first so that they would understand it... and chances are, that they would understand the second without any need for explanation.
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.