I really like the idea of executable requirements. Executable requirements is the idea that I can express requirements from the business in a human-readable, executable format. Part of fulfilling the requirement is that the "executable requirement" part can execute successfully in the form of acceptance tests.
Recently, I checked out a couple of API's for executable requirements in the form of behavior-driven design (BDD). For more information on BDD, check out these articles from Dan North's blog:
The API's I checked out were NBehave and Joe Ocampo's NUnit.Behave. NBehave was rather cumbersome to set up, and didn't have much of a fluent interface. NUnit.Behave was much easier to use, but forced a dependency on NUnit. I didn't want to be forced to use NUnit, nor did I want to inherit from certain classes or implement certain interfaces. Heavily influenced by NUnit.Behave, I created Behave#.
What is Behave#?
Behave# (http://www.codeplex.com/BehaveSharp) is a fluent interface to express executable requirements in the form of acceptance tests with a distinct BDD grammar. Stories follow the "As a <role> I want <feature> so that <benefit>" template. Stories are made up of scenarios, which follow the "Given When Then" template. Through a fluent interface, Behave# shapes your acceptance tests to closely match the original user story.
What would a user story look like in code?
The fluent interface I settled on looks like:
new Story("Transfer to cash account") .AsA("savings account holder") .IWant("to transfer money from my savings account") .SoThat("I can get cash easily from an ATM") .WithScenario("Savings account is overdrawn") .Given("my savings account balance is", -20) .And("my cash account balance is", 10) .When("I transfer to cash account", 20) .Then("my savings account balance should be", -20) .And("my cash account balance should be", 10);
I really like this syntax as it can be read very easily and there aren't a lot of other objects or frameworks getting in the way. So how do we actually execute custom actions in our story definition? Here's a full-fledged example, using NUnit to provide assertions:
[Test] public void Transfer_to_cash_account() { Account savings = null; Account cash = null; Story transferStory = new Story("Transfer to cash account"); transferStory .AsA("savings account holder") .IWant("to transfer money from my savings account") .SoThat("I can get cash easily from an ATM"); transferStory .WithScenario("Savings account is in credit") .Given("my savings account balance is", 100, delegate(int accountBalance) { savings = new Account(accountBalance); }) .And("my cash account balance is", 10, delegate(int accountBalance) { cash = new Account(accountBalance); }) .When("I transfer to cash account", 20, delegate(int transferAmount) { savings.TransferTo(cash, transferAmount); }) .Then("my savings account balance should be", 80, delegate(int expectedBalance) { Assert.AreEqual(expectedBalance, savings.Balance); }) .And("my cash account balance should be", 30, delegate(int expectedBalance) { Assert.AreEqual(expectedBalance, cash.Balance); }) .Given("my savings account balance is", 400) .And("my cash account balance is", 100) .When("I transfer to cash account", 100) .Then("my savings account balance should be", 300) .And("my cash account balance should be", 200) .Given("my savings account balance is", 500) .And("my cash account balance is", 20) .When("I transfer to cash account", 30) .Then("my savings account balance should be", 470) .And("my cash account balance should be", 50); transferStory .WithScenario("Savings account is overdrawn") .Given("my savings account balance is", -20) .And("my cash account balance is", 10) .When("I transfer to cash account", 20) .Then("my savings account balance should be", -20) .And("my cash account balance should be", 10); }
To perform actions with each "Given When Then" fragment, I pass in a delegate (anonymous in this case, but much prettier with lambda expressions in C# 3.0). This delegate is executed as the story execution proceeds, and is cached by name, so each time the story sees "my cash account balance is", it will execute the appropriate delegate. I don't need to define the custom action every time.
The result of the execution is the story results outputted in a human readable format (by default, to Debug):
Story: Transfer to cash account Narrative: As a savings account holder I want to transfer money from my savings account So that I can get cash easily from an ATM Scenario 1: Savings account is in credit Given my savings account balance is: 100 And my cash account balance is: 10 When I transfer to cash account: 20 Then my savings account balance should be: 80 And my cash account balance should be: 30 Given my savings account balance is: 400 And my cash account balance is: 100 When I transfer to cash account: 100 Then my savings account balance should be: 300 And my cash account balance should be: 200 Given my savings account balance is: 500 And my cash account balance is: 20 When I transfer to cash account: 30 Then my savings account balance should be: 470 And my cash account balance should be: 50 Scenario 2: Savings account is overdrawn Given my savings account balance is: -20 And my cash account balance is: 10 When I transfer to cash account: 20 Then my savings account balance should be: -20 And my cash account balance should be: 10
This format closely resembles the original user story I received from the business.
Conclusion
I like being able to verify deliverables in the form of automated tests. When these tests closely resemble the original requirements as user stories, I'm more confident in the deliverables. Having executable requirements in the form of acceptance tests in a fluent interface that matches the original requirements can bridge the gap between what the business asks for and what gets delivered. Behave# gets us one step closer to bridging that gap.
No comments:
Post a Comment