Changing Behaviour, leaving out the detail

It took me a while to change my behaviour when trying to do BDD in code. At first, like most of us, we try to do too much and end up writing tests that are too granular where each BDD statement would detail specific user input or navigation. I’ll try to describe my journey here.

This post refers to BDD in the domain of User Acceptance Testing and not Unit Testing. I’ll blog about that later.

Act 1

At first, I used Arrange, Act, Assert comments to help separate a test into sections to help readability. Later I changed to Given, When, Then terminology. This worked for quite a while and the level of detail for each section and test would vary but probably quite apt at the time. No real structure or patterns appeared for me so maintenance and readability would be limited.

What’s available

At this time, I naturally looked for inspiration and googled BDD frameworks. I didn’t like the thought of the spec code generated ones again due to maintainability fears and gut intuition.

This one felt useful

One that took my fancy was called StoryQ.  I liked this one because you could maintain behaviour specs in a code-first approach. It was a fluent interface of given, when and then methods each taking delegate methods references followed by param array of objects that are your test inputs. Delegate method names are done in camel case with underscore placeholders for the test input variables. When reading your code you’d have to mentally insert each variable into the test but the test run output was very readable when the underscores are replaced by the test input values. Unfortunately it could not handle outputting complex types.  This limitation was eventually its downfall for me, as I suspect any other BDD test framework would also suffer from as this means you are forced to a high level of specification detail and granularity. After a while it was difficult to find the correct method to reuse in a long list of inconsistently named methods, and when located, a method was often was not ideal for re-usability without breaking changes.  As time went on more and more I felt uneasy with the level of detail it was forcing me down each time I wrote a test.  There was an increasing smell in the air.

So what was wrong?

In short; Too Fine Granularity, Test Detail, Unstructured Implementation

Too Fine Granularity

Not being able to serialize complex types meant you are forced to use simple values. This forces your behaviour sentences to be wordy needing to include the value units and more importantly restricted you to a certain level of over specification meaning your tests are too granular providing less reuse and therefore less benefit.  I did create extension methods to better serialize complex types to produce more courser grained tests and this helped a little.

Test Detail

The test run output would include the test values. i.e. fake names and numbers like email addresses or product costs. I personally think the end user in the story, your customer you are developing for, would prefer not to be distracted by the test detail. For example when reading the behaviour specification output they would see ‘mytestemail@somewhere.com’ when I think they would simply prefer to read ‘the customer email’ or seeing ‘9.99’ instead of ‘item cost’ for instance.  I am sure that during a conversation with them they would simply specify in terms of  ‘the customer checks the item cost before buying’.  I think testing test boundaries, edge cases, or even typical test values is not really the concern of the user.  These things can be discovered by conversation followed by concentrated testing but not at UAT level.

Unstructured Implementation

The ability to freely create sentences and somehow follow a consistent convention that help discoverability and reuse is not trivial.  It does not lend very well to reusable structure and is an eventual limitation.  I did eventually break the static classes holding the method into class hierarchies to enable grouping into product screens/pages which helped with this issue but now this felt wrong as I was now tightly coupling the behaviour with implementation layout.  I really would like the behaviour to be implementation agnostic.

Other

Having to duplicate some part of the behaviour specification to provide a test method name was a constant annoyance, and not knowing how and when to combine User Story and BDD syntaxes.

So what did I want?

  • A useful level of granularity
  • Give context to detail
  • Reusable structure and simpler naming of statements
  • Combining syntaxes; User Story, BDD, and Test classes
  • Keep fluent interface

Also at this time,  I was embarking on implementing a CQRS architecture and knew I had to look into Domain Driven Design DDD which I knew of but knew little about. I thought, I wonder if TDD can be used to help eke out the complexity of a domain and ubiquitous language.  I still do not know enough about DDD and am not entirely sure this framework will help, but my gut intuition believes it will.

  • So, also DDD guidance from the framework

What Resulted

I started to write my own BDD framework. Here’s how it worked out.

I looked at an empty test method and the User Story syntax and waited for inspiration.   I decided to make the test method name be the ‘I want…’ part of a User Story, instead of the popular use of ‘Should…’.  It was the closest match for a test name, and the part I used to use for naming test methods when I wrote tests before.  ‘Should’ is better than ‘Test’ because it does indicate assertion and starts a meaningful sentence off. ‘I Want’ also includes the intention part of Should but also directly meets the user needs of the Story. This felt very correct to me, and solved the duplicated naming conundrum.

[Test]
public void IWantToRegisterANewCustomer()

This just leaves the ‘So That’ and ‘As A’ parts of a User Story. Next I though I’d start with ‘So That‘ part, the business value. This is simply text but I thought I’d build a way to force you to use an enumerator. That way you can build up and reuse a list of business values, which to be honest are the hardest bits to define in a User Story, and the most important. Actually, I think I’ll tackle how to build in a way to get more value and structural guidance for this later.

public enum MyBusinessValues
{
    WeIncreaseMarketablePotential
}

[TestFixture]
public class MyFeature : StoryFeatureBase<MyBusinessValues>
{
    [Test]
    public void IWantToRegisterANewCustomer()
    {
        SoThat(MyBusinessValues.WeIncreaseMarketablePotential)

The last bit for a User Story is ‘As A‘. This needs to be the name of someone, a Persona which also lends nicely from Lean learning. You simply create a class that inherits IPersona so the intellisense helps here. This tells the framework that whichever entity commands it calls will execute code implemented by the Persona class. The command methods are not added to IPersona interface but to the Entity interfaces themselves. The ‘As’ statement can be used wherever in the specification to change persona to use.

[Test]
public void IWantToRegisterANewCustomer()
{
    SoThat(MyBusinessValues.WeIncreaseMarketablePotential)
       .As(new User())
}

public class User : IPersona
{
}

Now the behaviour statements are the only part of the syntax left, which are the given, when, and then. These just follow the ‘As’ in their usual fluent pattern with the ability to have ‘And’s used as well. ‘When’ can only follow a ‘Given’ and ‘Then’ can only follow a ‘When’ is the only real constraint. Each behaviour statement, whichever one it is, expects an entity method name. This is a delegate reference to the interface method that will be called against the Persona when executed. No parenthesis or parameters are needed. The test data is provided in the instantiation of the Entity instance specified in the test and statement. i.e. Given(customer.Register) where ‘customer’ is the entity instance and ‘Register’ is the method name as made available by the ICustomer interface.

[Test]
public void IWantToRegisterANewCustomer()
{
    ICustomer customer = new Customer("customer@email.com"); //Entity, your test data
    SoThat(MyBusinessValues.WeIncreaseMarketablePotential)
        .As(new User())    
        .Given(customer.Register);
}

Notice the User persona class below now implements the ICustomer interface. It also contains a public property required by the framework to inject the entity instance specified in the ‘Given’ behaviour statement above. The entity property can then be consumed by the implemented ICustomer interface method. This is in effect the test data. The methods can also alter the test Entity state as to represent what would be expected to happen by the production.

public class User : IPersona, ICustomer
{
    public Customer Customer { get; set; }

    public void Register()
    {
        //your test implementation code here. i.e. Webdriver code to automate a browser
        //Steps here may include
        //Check the page url is correct
        //enter user email address
        var emailToUse = Customer.Email;
        //click the Register button
    }
}

Here is the ICustomer interface definition. If you were implementing a CQRS pattern then this would be the Commands for a Customer aggregate/entity.

public interface ICustomer
{
    void Register();
}

Here is the Customer class that would be the intended one used in the product code. For instance, you would implement the necessary code to actually register a customer with the system.

public class Customer : ICustomer
{
    public string Email { get; set; }
    public void Register()
    {
        //this will eventually be the product code to register a Customer
    }
}

It would be recommended to separate the persona class up into partial classes to nicely separate the entity interfaces. If a persona implements more than one entity interface with the same method names then this partial separation may help or you could use the more implicit declaration. i.e. public void Register() would become void ICustomer.Register().

Finally we sometimes need to provide context for the entity. for example, rather than just ‘customer’ we may need to highlight in the behaviour a more descriptive and meaningful named state for the entity. i.e. ‘a new customer’, ‘newly registered customer’, ‘returning customer’. So I added a little helper extension that names an object instance.

[Test]
public void IWantToRegisterANewCustomer()
{
    var aUser = new User().Named("a user");
    ICustomer customer = new Customer("customer@email.com");

    SoThat(MyBusinessValues.WeIncreaseMarketablePotential)
        .As(aUser)
        .Given(customer.Named("new customer").Register)
        .When(customer.Named("awaiting customer").Confirm_Registration)
        .Then(customer.Named("the returning customer").Login);
}

That’s it really, no need to call a method to execute this, just add the semi-colon at the end. The output from this in the console would be.

I want to register a new customer
  so that we increase marketable potential
       as a user
    given register new customer
     when confirm awaiting customer registration
     then login the returning customer

Something Extra

Extra to this, I thought with this level of abstraction, how difficult would it be to target a platform implementation layer like a presentation layer, Api layer, or even direct access. All we need to do is take the Persona and call a specific implementation layer for it. For the test fixture I added a method which can be overridden that allows the test author to specify the target layer. All that is needed is to add another inner namespace for this layer where the framework expects the personas implementation to exist.

[TestFixture]
public class MyFeature : StoryFeatureBase
{
    public override string ApplicationLayer()
    {
        return "Presentation";
        //return "RestApi"; //alternative layer to test against
    }
    
    //... tests
}
public namespace Personas
{
    public class User : IPersona
    {
    }
    public namespace Presentation
    {
        public class User : ICustomer
        {
            public Customer Customer { get; set; }

            public void Register()
            {
               //i.e. Selenium Webdriver
            }
        }
    }
    public namespace RestApi
    {
        public class User : ICustomer
        {
            public Customer Customer { get; set; }

            public void Register()
            {
                //i.e. HttpClient web calls to access REST Api presented by product
            }
        }
    }
}

So to summarize, UBADDAS provides a concise means to combine the syntaxes of test methods, User Stories,  BDD, and even DDD (to a point). Reuse is much improved as it also provides a handy means to target an implementation layer without having to rewrite test behaviours at all. behaviour granularity and the level of specification now is implied via the use of Entities with single Commands.

You can find UBADDAS here on GitHub, or here at Nuget.

One thought on “Changing Behaviour, leaving out the detail

Leave a reply to kernowcode Cancel reply