As I said in my previous post I preferred using tools (StoryQ and NetSpec) to using descriptive method names (those with long names containing lots of underscores). Well, I lied. At first I was convinced that the tools would be good enough for me but as it usually occurs the truth lies somewhere in between.
Here's what I have in mind.

Let's take as an example the famous sample showing how you could apply BDD when you want to develop the logic for transferring money from a savings account to a cash account. If you use StoryQ you could end up having something like this:

[TestMethod]     
public void TransferToCashAccount()     
{     
    Account savings = null;     
    Account cash = null;     
    
    Story transferToCashAccount = new Story("Transfer to cash account");     
    
    transferToCashAccount.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 in credit").     
            Given("my savings account balance is 100",     
            () => savings = new Account(100)).     
            And("my cash account balance is 10",     
            () => cash = new Account(10)).     
                When("I transfer to cash account 20",     
                () => savings.TransferTo(cash, 20)).     
                    Then("my savings account balance should be 80",     
                    () => savings.Balance.ShouldBe(80)).     
                    And("my cash account balance should be 30",     
                    () => cash.Balance.ShouldBe(30));     
    
    transferToCashAccount.Assert();     
}    
 

 

But what if we add more scenarios to this story:

[TestMethod]     
public void TransferToCashAccount()     
{     
    Account savings = null;     
    Account cash = null;     
    
    Story transferToCashAccount = new Story("Transfer to cash account");     
    
    transferToCashAccount.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 in credit").     
            Given("my savings account balance is 100",     
            () => savings = new Account(100)).     
            And("my cash account balance is 10",     
            () => cash = new Account(10)).     
                When("I transfer to cash account 20",     
                () => savings.TransferTo(cash, 20)).     
                    Then("my savings account balance should be 80",     
                    () => savings.Balance.ShouldBe(80)).     
                    And("my cash account balance should be 30",     
                    () => cash.Balance.ShouldBe(30)).     
    
        WithScenario("savings account balance is insufficient").     
            Given("my savings account balance is 10",     
            () => savings = new Account(10)).     
            And("my cash account balance is 10",     
            () => cash = new Account(10)).     
                When("I transfer to cash account 20",     
                () => savings.TransferTo(cash, 20)).     
                    Then("my savings account balance should be 10",     
                    () => savings.Balance.ShouldBe(10)).     
                    And("my cash account balance should be 10",     
                    () => cash.Balance.ShouldBe(10));     
    
    transferToCashAccount.Assert();     
}    
 

 

Well, it becomes quite a bloated piece of code. We could refactor it and move the behaviors in different methods:

Story transferToCashAccount = new Story("Transfer to cash account");     
    
[TestMethod]     
public void TransferToCashAccount()     
{     
    transferToCashAccount.AsA("savings account holder").     
        IWant("to transfer money from my savings account").     
        SoThat("I can get cash easily from an ATM");     
    
    // Scenarios     
    SavingsAccountIsInCredit();     
    SavingsAccountBalanceIsInsufficient();     
    
    transferToCashAccount.Assert();     
}     
    
private void SavingsAccountIsInCredit()     
{     
    Account savings = null;     
    Account cash = null;     
    
    transferToCashAccount.WithScenario("savings account is in credit").     
            Given("my savings account balance is 100",     
            () => savings = new Account(100)).     
            And("my cash account balance is 10",     
            () => cash = new Account(10)).     
                When("I transfer to cash account 20",     
                () => savings.TransferTo(cash, 20)).     
                    Then("my savings account balance should be 80",     
                    () => savings.Balance.ShouldBe(80)).     
                    And("my cash account balance should be 30",     
                    () => cash.Balance.ShouldBe(30));     
}     
    
private void SavingsAccountBalanceIsInsufficient()     
{     
    Account savings = null;     
    Account cash = null;     
    
    transferToCashAccount.WithScenario("savings account balance is insufficient").     
            Given("my savings account balance is 10",     
            () => savings = new Account(10)).     
            And("my cash account balance is 10",     
            () => cash = new Account(10)).     
                When("I transfer to cash account 20",     
                () => savings.TransferTo(cash, 20)).     
                    Then("my savings account balance should be 10",     
                    () => savings.Balance.ShouldBe(10)).     
                    And("my cash account balance should be 10",     
                    () => cash.Balance.ShouldBe(10));     
}    
 

 

It's better but still it's not perfect and maybe you've noticed the duplication of the scenarios' descriptions. First in the methods' names and second when creating the scenario but in a plain text format. We also have the same duplication for the story name. If you create several user stories you'll notice that you'll repetitively do the same things - instantiate a user story, assert it at the end, etc. How we could possibly optimize this? Of course with a base class:

public class as_a_user_story  
{  
    Story userStory;  
 
    protected GivenFragment Given(string description, Action action)  
    {  
        StackTrace stackTrace = new StackTrace();  
        string scenarioName = stackTrace.GetFrame(1).GetMethod().Name.Replace('_', ' ');  
        Scenario methodBasedScenario = userStory.WithScenario(scenarioName);  
        return methodBasedScenario.Given(description, action);  
    }  
 
    protected AsAFragment AsA(string role)  
    {  
        string storyName = this.GetType().Name.Replace('_', ' ');  
        userStory = new Story(storyName);  
        return userStory.AsA(role);  
    }  
 
    protected void Assert()  
    {  
        userStory.Assert();  
    }  

 

Then you could inherit from this class and your stories could look like the following:

[TestClass]  
public class Transfer_to_cash_account : as_a_user_story  
{  
    [TestMethod]  
    public void Transfer_to_cash_account_story()  
    {  
        AsA("savings account holder").  
        IWant("to transfer money from my savings account").  
        SoThat("I can get cash easily from an ATM");  
 
        // Scenarios  
        Savings_account_is_in_credit();  
        Savings_acccount_balance_is_insufficient();  
 
        Assert();  
    }  
 
    private void Savings_account_is_in_credit()  
    {  
        Account savings = null;  
        Account cash = null;  
 
        Given("my savings account balance is 100",  
        () => savings = new Account(100)).  
        And("my cash account balance is 10",  
        () => cash = new Account(10)).  
            When("I transfer to cash account 20",  
            () => savings.TransferTo(cash, 20)).  
                Then("my savings account balance should be 80",  
                () => savings.Balance.ShouldBe(80)).  
                And("my cash account balance should be 30",  
                () => cash.Balance.ShouldBe(30));  
    }  
 
    private void Savings_acccount_balance_is_insufficient()  
    {  
        Account savings = null;  
        Account cash = null;  
 
        Given("my savings account balance is 10",  
        () => savings = new Account(10)).  
        And("my cash account balance is 10",  
        () => cash = new Account(10)).  
            When("I transfer to cash account 20",  
            () => savings.TransferTo(cash, 20)).  
                Then("my savings account balance should be 10",  
                () => savings.Balance.ShouldBe(10)).  
                And("my cash account balance should be 10",  
                () => cash.Balance.ShouldBe(10));  
    }  

 

And here's the output from the test runner:

To make your life even easier I'm attaching an item template for a user story class. You should just copy the .zip file to "C:\Users\[USER]\Documents\Visual Studio 2008\Templates\ItemTemplates". Then when you want to add a new item to the project (CTRL + Shift + A) you'll be able to select the "User Story" template:

I'm also attaching a code snippet for creating easier a new scenario. From the Tools menu (Visual Studio) select "Code Snippets Manager", then "Import" and select the snippet file. Then when you type "scenario" somewhere in your test class and press TAB, you'll get the code for creating a new scenario:

I hope this information will help you author user stories easier if you decide to try BDD.


About the Author

Hristo Kosev

 

Comments