Essay : Extending BDD stories ; the Given-I-did clause - including flow in user stories

Introduction

In this essay I am going to reflect my personal ideas and opinions on the DSL syntax used for BDD, and I will propose a new concept for the common BDD language.

I am going to show you how I really grasped what I was doing wrong with my stories, and how the syntax should be extended in order to contain extra flow information in a really easy way.
This new concept adds extra conceptual information to the stories, and also reduces the work the developer has to do to implement the DSL.

Enjoy !

 

 

In the beginning, there was nothing !!!

I got started on this idea while I was replying to an email about the limitations of current BDD stories. Since my conversation partner said he had some issues on the current BDD syntax used, I asked him wether he could clarify on the issues.

While I was replying to the response he sent, I was trying to explain my thoughts by giving an example :

As a customer
I want to place orders
So that I can get the products where and when I want them

Scenario Place an order
When I want to place an order
Then I should be able to select products in a catalog

Scenario Order products
Given I have selected on or more products in a catalog
When I add it to my shopping cart
Then the shopping cart should contain the selected product

Scenario Modify a product in the shopping cart
Given I have some products in my shopping cart
When I edit a product order
Then the edited product order should have changed

 

 

Eureka !!

This was the moment I noticed that something was wrong. Let us take a good look at the last Scenario : "Modify a product in the shopping cart" . What is wrong with it, one might ask ? While I was writing this story I was thinking the following :
- What if the shopping cart is empty ?
- Hmmz I don't know, I suppose we need another scenario for that
- Another scenario for what ?
- We define two scenarios : one for a shopping carts with products in it, and one for the case where it's empty, and we add "Given the shopping cart is (not) empty"
- But hey, why ? I'm not interested in the "Modify the shopping cart if the shopping cart is empty" because it is just litter, it doesn't really add to the conceptual story.
- Yeah, but as a developer I need to know what to do if it is empty and you try to modify it.
- Are you stupid ?  Ok, maybe you need to say that it is not possible, but it adds no conceptual info at all to the story !!
- Oh, you mean that it is actually some extra information on the scenario; the "shopping cart is not empty" is not just a given for this example; it is a requirement ???
- Yes, it's not a given, it is a requirement that should say that this story is only valid in this particular case
- Hmmz interesting concept, let's try :

Scenario Modify a product in the shopping cart
If I have some products in my shopping cart
When I edit an ordered product
Then the edited product should have changed

- Whoa ! that looks nice; If we have this scenario, we do not need an extra scenario for the empty shopping cart
- But wait, we could also say it like this

Scenario Modify a product in the shopping cart
Given I have some products in my shopping cart
When I edit an ordered product
Then the edited product should have changed

- What is the difference ?
- Well, it is really a conceptual thing; I'll try to think about another example

Scenario Place the order
If I have identified myself as a valid customer
When I edit an ordered product
Then the edited product should have changed

- Hmmz something is wrong with it ?
- What ?

 

Eureka : Part deux

- EUREKA !!!
- What ? Again ???
- I found it ; the "If" clause is actually describing something you did previously.
- In the example the "If I have identified myself as a valid customer" actually means we should have some kind of a scenario called "Identify myself as a valid customer"
- Oh, you are describing the flow here ?
- Yups, that is it; it is extra information you are providing....
- Wow, that is interesting !!
- But, I see a small problem; the name of the scenario does not match the "If "clause
- .... let's see.... AHA !!
- Yes ?
- Here is the example :

Scenario Identify myself as a valid customer
Given  I have some valid id method
When  I identify myself
Then   it should state me as a valid customer

Scenario Place the order
Given I did identify myself as a valid customer
  And I did order products
When place the order
Then order should be confirmed

Scenario Order products
Given I did select one or more products in a catalog
When I add it to my shopping cart
Then the shopping cart should contain the selected product

- Whoa, that looks nice ! So what you are actually doing is extending the story the whole time ?
- Yes, that's it.
- So we are gradually replacing all the "Given"s by the "Given I did" and thus including extra flow in the context ?
- Yes !! And in theory we could go all the way until we get to the very bottom of every single implemented line of code...
- Man ! So the developer only has to implement the "Given"s , and not the "Given I did" ?
- Yups !!
- That should save a lot of work !! nice !!



In conclusion

Using the "Given I did" clause, we can include extra conceptual information in the BDD stories; the stories become tighter, but they contain even more information then before.

By analyzing the "Given I did" specs, one can deduct the flow an application should allow. In the example stated above, the option of ordering with an empty shopping cart should not be available since it is not mentioned in the stories; the developer should only allow state transitions mentioned in the stories....

I truly think that we have something new here, and hope that this essay is a viable contribution to the BDD community on it's own !!

Kind Regards,

Tom Janssens

 

PS : I'm not really schizophrenic; I was just looking for a way to reflect my thaughts ;)

Appendix A : a working example story :



Context      Be.Corebvba.Aubergine.Examples.Accounts.Contexts.AccountContext
ContextDLL   Be.Corebvba.Aubergine.Examples.DLL

Story Transfer money between accounts

    As a user
    I want to transfer money between accounts
    So that I can have real use for my money

    Given the balance of AccountA is 3m
    Given the balance of AccountB is 2m
    Given the owner of AccountA is the current user
   
    Scenario Authenticate the current user as a 'valid or invalid' user
        Given the name of the current user is 'username'
          And the password of the current user is 'password'
        When I request authentication for the current user
        Then the process should 'fail or succeed'
       
        Example
            +--------------------+------------+------------+------------------+
            | 'valid or invalid' | 'username' | 'password' | 'fail or succeed'|
            +--------------------+------------+------------+------------------+
            | valid              | Neo        | Red pill   | succeed          |
            | invalid            | Neo        | Blue pill  | fail             |
            +--------------------+------------+------------+------------------+
               
    Scenario Authenticate the current user for 'an account'
        Given I did authenticate the current user as a valid user
        When I request authentication for 'an account' with the current user
        Then the process should 'fail or succeed'
       
        Example accounts :
            +--------------+-------------------+
            | 'an account' | 'fail or succeed' |
            +--------------+-------------------+
            |  AccountA    | succeed           |
            |  AccountB    | fail              |
            +--------------+-------------------+
       

    Scenario Transfer AAm between 2 accounts
        Given I did authenticate the current user for AccountA
        When I transfer AAm from AccountA to AccountB with the current user
        Then the balance of AccountA should be BBm 
        Then the balance of AccountB should be CCm 
        Then the process should 'fail or succeed'
       
        Example for the transfer

            +----+----+----+-------------------+
            | AA | BB | CC | 'fail or succeed' |
            +----+----+----+-------------------+
            |  1 |  2 |  3 | succeed           |
            |  2 |  1 |  4 | succeed           |
            |  3 |  0 |  5 | succeed           |
            |  4 |  3 |  2 | fail              |
            +----+----+----+-------------------+

Appendix B : the resulting output of my runner:

Aubergine Console Runner - Core bvba - Tom Janssens 2009

Processing file(s) : accounts\stories\*.txt
  Processing file : C:\Projecten\Be.Corebvba.Aubergine\Be.Corebvba.Aubergine.ConsoleRunner\bin\Debug\accounts\stories\Transfer_money_between_accounts.txt

    Story Transfer money between accounts =>OK
        Given the balance of AccountA is 3m =>OK
        Given the balance of AccountB is 2m =>OK
        Given the owner of AccountA is the current user =>OK
        Scenario Authenticate the current user as a valid user =>OK
            Given the name of the current user is Neo =>OK
            Given the password of the current user is Red pill =>OK
            When I request authentication for the current user =>OK
            Then the process should succeed =>OK

        Scenario Authenticate the current user as a invalid user =>OK
            Given the name of the current user is Neo =>OK
            Given the password of the current user is Blue pill =>OK
            When I request authentication for the current user =>OK
            Then the process should fail =>OK

        Scenario Authenticate the current user for AccountA =>OK
            Given I did authenticate the current user as a valid user =>OK
            When I request authentication for AccountA with the current user =>OK
            Then the process should succeed =>OK

        Scenario Authenticate the current user for AccountB =>OK
            Given I did authenticate the current user as a valid user =>OK
            When I request authentication for AccountB with the current user =>OK
            Then the process should fail =>OK

        Scenario Transfer 1m between 2 accounts =>OK
            Given I did authenticate the current user for AccountA =>OK
            When I transfer 1m from AccountA to AccountB with the current user =>OK
            Then the balance of AccountA should be 2m =>OK
            Then the balance of AccountB should be 3m =>OK
            Then the process should succeed =>OK

        Scenario Transfer 2m between 2 accounts =>OK
            Given I did authenticate the current user for AccountA =>OK
            When I transfer 2m from AccountA to AccountB with the current user =>OK
            Then the balance of AccountA should be 1m =>OK
            Then the balance of AccountB should be 4m =>OK
            Then the process should succeed =>OK

        Scenario Transfer 3m between 2 accounts =>OK
            Given I did authenticate the current user for AccountA =>OK
            When I transfer 3m from AccountA to AccountB with the current user =>OK
            Then the balance of AccountA should be 0m =>OK
            Then the balance of AccountB should be 5m =>OK
            Then the process should succeed =>OK

        Scenario Transfer 4m between 2 accounts =>OK
            Given I did authenticate the current user for AccountA =>OK
            When I transfer 4m from AccountA to AccountB with the current user =>OK
            Then the balance of AccountA should be 3m =>OK
            Then the balance of AccountB should be 2m =>OK
            Then the process should fail =>OK

 

Appendix C : The context + DSL

    internal class AccountContext
    {
        public User currentUser = new User();
        public Account AccountA = new Account();
        public Account AccountB = new Account();
        public AccountService AccountService = new AccountService();
        public ProcessStatus LastStatus;

        [DSL(@"the (?<member>.+) of (?<instance>.+) is (?<value>.+)")]
        void assignfield(object instance,string member,object value)
        {
            instance.Set(member, value);
        }

        [DSL(@"the (?<member>.+) of (?<instance>.+) should be (?<value>.*)")]
        bool shouldbefield(object instance, string member, object value)
        {
            var obj = instance.Get<object>(member);
            return Convert.ChangeType(value, obj.GetType()).Equals(obj);
        }

        [DSL(@"I request authentication for (?<user>.+)")]
        void authenticate_for_account_x(User user)
        {
            LastStatus = AccountService.AuthenticateUser(user);
        }

        [DSL(@"I request authentication for (?<account>.+) with (?<user>.+)")]
        void authenticate_for_account_x(User user, Account account)
        {
            LastStatus =  AccountService.AuthenticateUserForAccount(account,user);
        }
       
        [DSL(@"I transfer (?<amount>.+) from (?<from>.+) to (?<to>.+) with (?<user>.+)")]
        void transfering_xm_from_a_to_b(decimal amount, Account from, Account to,User user)
        {
            LastStatus = AccountService.Transfer(user,amount, from,to);
        }

        [DSL]
        bool The_process_should_fail()
        {
            return LastStatus.Success == false;
        }

        [DSL]
        bool The_process_should_succeed()
        {
            return LastStatus.Success == true;
        }

        [DSL("(?<name>Account[AB])")]
        Account getaccountAB(string name)
        {
            return this.Get<Account>(name);
        }

        [DSL(@"(?<amount>\d+)(?<ismillion>m?)")]
        decimal getmillion(decimal amount,string  ismillon)
        {
            return amount*(ismillon=="m"?1m:1);
        }

        [DSL]
        User the_current_user()
        {
            return currentUser;
        }
    }

About Tom

     Tom Janssens op LinkedIn    Tom Janssens op twitter   Core bvba RSS

I build software and help organisations to get better at building software.

If you would like to know more or meet up, just give me a call at
+32 478 336 376.

More info about Tom and his company...

Tom's resume


Calendar

<<  April 2014  >>
MoTuWeThFrSaSu
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar