Introduction

This article aims to introduce Behaviour Driven Development (BDD), using JBehave 2, throughout a development episode; starting with a user story, describing a desired feature, through to the completion of working software.

Readers will learn how to use JBehave 2 to create executable scenarios that ensure desired application behaviour and will be introduced to the "outside-in" design philosophy at the heart of BDD.

JBehave 2 can be downloaded here

Where do we Start?

BDD starts with a conversation. A conversation involving the stakeholders of the software being developed and those involved in its development. Typically this will involve: business sponsors, users, business analysts, developers, QA and project managers. The goal is to develop a shared understanding of the features to be implemented, the business value those features provide along with a set of acceptance criteria for each feature so that we know when we are "done". Introducing Behaviour Driven Development, by Dan North, provides an insightful account of the evolution of BDD for those interested in the origins of the approach.

User Stories

Features identified are represented as User Stories[1]. Each user story provides a description of a feature that provides value to a user. Although User Stories do not define a strict format, many teams have found the following format useful:

As a [Role]
I want [Feature]
So that I receive [Value]

Once a story has been identified we then focus on the scenarios that describe how the user expects the system to behave using a sequence of steps in the following form:

Given [Context]
When [Event Occurs]
Then  [Outcome]

The scenarios defined for a given story provide the acceptance criteria which will be used to determine when the feature is complete.

The Tab Player

The example presented here is based on the Tab Player problem posted to the excellent Ruby Quiz.

Tablature, more commonly known as tab, is a notation used to describe where players should place their fingers on a string instrument such as the guitar. This example will follow the development of a feature to "play" guitar tabs.

    Story: Play Tabs

    As a music fan
    I would like to convert guitar tabs to music
    So that I can hear what they sound like
    Scenario: My My Hey Hey
    Given tab
    e|---------------------------------
    B|---------------------------------
    G|---------------------------------
    D|----------0--0-------------------
    A|-0--2--3----------2--0-----0--0--
    E|------------------------3--------
    When the guitar plays
    Then the following notes will be played
    A0 A2 A3 D0 D0 A2 A0 E3 A0 A0

The "My My Hey Hey" scenario provides the acceptance criteria by which we will use to determine when the story is complete.

Notice that both the story and the associated scenario are written in terms of the problem domain in plain english that the user can understand. The motivations of the user are clearly stated. In this example, the user wants to hear the introduction to the classic Neil Young song, Hey Hey My My.

Creating the Scenario

BDD advocates taking an "outside in" approach to development where we write an executable scenario that ensures that the application is behaving correctly with respect to a given scenario, which describes an interaction with the system that provides value to the user.

A executable scenario is created by extending org.jbehave.scenario.Scenario.

package com.tabplayer;

import org.jbehave.scenario.Scenario;

public class HeyHeyMyMy extends Scenario {

        @SuppressWarnings("unchecked")
	public HeyHeyMyMy() {
            super();
	}
}

Executing the Scenario

JBehave plays very nicely with JUnit4 and scenarios are executed just like a standard JUnit4 test. Just select run test from your favorite IDE.

By default JBehave will attempt to load a scenario file named "hey_hey_my_my" from the Class Path. Running the HeyHeyMyMy scenario will produce the following output on the console:

    Given tab
    e|--------------------------------------
    B|--------------------------------------
    G|--------------------------------------
    D|----------0--0------------------------
    A|-0--2--3----------2--0-----0--0-------
    E|------------------------3------------- (PENDING)
    When the guitar plays (PENDING)
    Then the following notes will be played
    A0 A2 A3 D0 D0 A2 A0 E3 A0 A0 (PENDING)

Each of the steps in the scenario are in a state of pending, meaning that JBehave has not yet been configured to map each step to Java code. The next section will describe how to define steps for your scenarios.

Defining Scenarios Steps

So far the scenario is not particularly useful as is does not result in any application code being executed. Textual scenarios are mapped to code by extending org.jbehave.scenario.steps.Steps and providing methods for each step in the scenario.

For example:


package com.tabplayer.steps;

import org.jbehave.scenario.annotations.Given;
import org.jbehave.scenario.annotations.Then;
import org.jbehave.scenario.annotations.When;
import org.jbehave.scenario.steps.Steps;

public class PlayTabSteps extends Steps {

    @Given("tab $asciiTab")
    public void tab(String asciiTab) {
    }

    @When("the guitar plays")
    public void guitarPlays() {
    }

    @Then("the following notes will be played $notes")
    public void theFollowingNotesWillBePlayed(String notes) {
    }
}

Annotations are used to map: Given, When and Then steps in the scenario to methods that will be called when the scenario is executed. Step parameters are represented using the following syntax: $parameter.

Currently step methods can define parameters of the following types: double, int, long, float, String, Double, Integer, Long, Float

The PlayTabSteps can be registered with the HeyHeyMyMy scenario by passing an instance of PlayTabSteps to the constructor of org.jbehave.scenario.Scenario.

package com.tabplayer;

import org.jbehave.scenario.Scenario;

public class HeyHeyMyMy extends Scenario {

        @SuppressWarnings("unchecked")
	public HeyHeyMyMy() {
            super(new PlayTabSteps());
	}
}

Executing the HeyHeyMyMy scenario will result in a "passing" scenario.

    Given tab
    e|--------------------------------------
    B|--------------------------------------
    G|--------------------------------------
    D|----------0--0------------------------
    A|-0--2--3----------2--0-----0--0-------
    E|------------------------3-------------
    When the guitar plays
    Then the following notes will be played
    A0 A2 A3 D0 D0 A2 A0 E3 A0 A0
    

Writing a Failing Scenario

We have seen that scenarios that do nothing will be reported as successful. The next stage in the process is to write a scenario ensuring that the expected notes are played by the Guitar. As this functionality does not exist yet, we expect the scenario to fail.

When implementing methods that map to steps in a scenario, we are forced to think about the interfaces of the classes in the domain from a top-down perspective. As the scenarios themselves are written in terms of the problem domain they are excellent sources of inspiration for class names in the domain model and allow the emergence of a ubiquitous language for describing the application as promoted by Domain Driven Design [2].

A Tab class will be used to represent tablature that has been parsed from an ascii representation. The given tab step implementation will simply create a new instance of the Tab class passing the ascii tab representation in the constructor.


public class PlayTabSteps extends Steps {

    private Tab tab;

    @Given("tab $asciiTab")
    public void tab(String asciiTab) {
        tab = new Tab(asciiTab);
    }
}

A Guitar class will be responsible for playing a piece of Tab. It therefore seems sensible for the guitar class to have a play method that accepts the tab to be played as a parameter.


public class PlayTabSteps extends Steps {

    private Tab tab;
    private Guitar guitar;

    @When("the guitar plays")
    public void guitarPlays() {
        guitar = new Guitar();
        guitar.play(tab);
    }
}

So far the Hey Hey My My scenario has helped to drive out classes in the domain model, namely Guitar and Tab, and the interaction between these classes. Did I mention that BDD involved design?

To achieve a failing scenario we now need to ensure that the expected notes have been played in the specified order. Given that this is a toy example, the guitar will maintain a list of notes played (in order) so that we can compare them with the actual notes expected.

The example scenario communicates that in the world of the axe wielding Rock God, a note has two components, a guitar string and fret position. These each sound like candidate domain classes to me.

Converting Step Parameters to Domain classes

Ensuring behaviour often involves a converting a string representation of an expected outcome to domain objects. In this example we need to convert a list of expected notes to be played represented as strings, such as "A1", into a collection of Note instances.

I like to place the responsibility for converting string representations of domain classes into small converter classes. Typically each converter will be responsible for generating instances of one type of class from a string representation. A PlayedNotesConverter will therefore be created to convert a list of notes representated as strings, e.g.: "A1", "G11", "B7", into a collection of Note instances.

Here is the interface:

public class PlayedNotesConverter {

    public List<Note> convert(String notes) {
        ...
    }
}

Exploring the Domain

We know from the example, that a note consists of a guitar string and a fret position. These properties will now be added to the Note class.

public class Note {

    private GuitarString string;
    private int fret;

    public Note(GuitarString string, int fret) {
        this.string = string;
        this.fret = fret;
    }
}

Guitar strings are implemented using an enumeration, as follows:

package com.tabplayer.domain;

public enum GuitarString {
    e,B,G,D,A,E
}

With the supporting string conversion code in place we are now able to focus on the implementation of the step method, theFollowingNotesWillBePlayed, that will ensure that the expected notes were played by the guitar.

...
public class PlayTabSteps extends Steps {
    ...

    @Then("the following notes will be played $notes")
    public void theFollowingNotesWillBePlayed(String notes) {
        List<Note> expectedNotes =
            new PlayedNotesConverter().convert(notes);

        // Ensure that the expected notes are played by guitar
    }
}

The comment above clearly describes the expected behaviour of the application. Wouldn't it be great if we could write actual code that communicates just as clearly? Thanks to the wonderful Hamcrest we can do just that.

...
public class PlayTabSteps extends Steps {

    private Tab tab;
    private Guitar guitar;

    @Given("tab $asciiTab")
    public void tab(String asciiTab) {
        tab = new Tab(asciiTab, new TabParser());
    }

    @When("the guitar plays")
    public void guitarPlays() {
        guitar = new Guitar();
        guitar.play(tab);
    }

    @Then("the following notes will be played $notes")
    public void theFollowingNotesWillBePlayed(String notes) {
        ensureThat(expectedNotes(notes), arePlayedBy(guitar));
    }

    private List<Note>expectedNotes(String notes) {
        return new PlayedNotesConverter().convert(notes);
    }

    private Matcher arePlayedBy(Guitar guitar) {
        return new NotesPlayedMatcher(guitar);
    }

    private class NotesPlayedMatcher extends BaseMatcher {

        private Guitar guitar;

        public GuitarHasPlayedNotes(Guitar guitar) {
            this.guitar = guitar;
        }

        public boolean matches(Object expectedNotes) {
            return guitar.notesPlayed().equals(expectedNotes);
        }

        public void describeTo(Description description) {
        }
    }
}

The scenario is now complete and will provide the following output when executed.

    Given tab
    e|--------------------------------------
    B|--------------------------------------
    G|--------------------------------------
    D|----------0--0------------------------
    A|-0--2--3----------2--0-----0--0-------
    E|------------------------3-------------
    When the guitar plays
    Then the following notes will be played
    A0 A2 A3 D0 D0 A2 A0 E3 A0 A0 (FAILED)

    java.lang.AssertionError:
    Expected: <[A0, A2, A3, D0, D0, A2, A0, E3, A0, A0]>
         got:
    

The failing scenario is currently telling us that the guitar does not currently play the expected notes. Lets fix the Guitar class so that it does.

BDD at the Class Level

At the class level, BDD recommends the same techniques used by traditional Test Driven Development(TDD). However, the vocabulary used to describe developement shifts the focus from defining tests to defining behavior. Rather than creating a GuitarTest class, BDD favours the creation of a GuitarBehaviour class. A behaviour class will contain example methods, starting with "should", describing the behaviour behaviour expected of the class being driven. As with TDD, production code not be written until we have a failing example describing the behaviour to be implemented.

So why the departure from such well known concepts such a test? Most experience TDD practitioners will agree that following TDD has had a profound effect on the way they design and implement applications. The process of writing tests firsts encourages systems that are composed of lots of small collaborating classes, often with a single responsibility. When specifying tests first, developers are forced to answer questions such as: what behaviour is required from this class and what classes will it need to collaborate with to achieve its goal? In order to keep tests small and focused the number of collaborators are kept to a minimum. TDD is therefore a design tool that results in a suite of regression tests that describe how classes behave including their interactions will other classes. The focus should therefore be on the specification of behaviour, rather than testing, and hence BDD.

Playing Guitar

The scenario has already identified that the Guitar class needs to implement a notesPlayed method. We will now write a GuitarBehavior class that will describe how the Guitar class behaves, which in turn will drive out the interactions between the Guitar and Tab classes.

    package com.tabplayer.domain;

    import static org.hamcrest.core.IsEqual.equalTo;
    import static org.jbehave.Ensure.ensureThat;
    import org.junit.Test;
    import static org.mockito.Mockito.mock;
    import static org.mockito.Mockito.stub;

    import static java.util.Arrays.asList;

    public class GuitarBehaviour {

        @Test
        public void shouldPlayTheNotesTranscribedInTheGivenTab() {

            // Given
            Guitar axe = new Guitar();
            Tab tab = mock(Tab.class);
            stub(tab.notesToBePlayed()).toReturn(
                asList(Note.A(1), Note.B(2)));

            // When
            axe.play(tab);

            // Then
            ensureThat(axe.notesPlayed(),
                       equalTo(asList(Note.A(1), Note.B(2))));
        }
    }

Here the Tab class is being mocked as it does not exist yet. The goal of this example is to ensure that the guitar plays all of the notes that are represented in the given tab class. This leads us to thinking about what methods the guitar needs on the tab class to achieve it's goal of playing the notes transcribed in the tab. It seems sensible to have the tab provide a list of Note instances to be played. We therefore add this method to the Tab class and stub a collection of notes to be returned. We then write an assertion to ensure that guitar plays the notes that the Tab tells the guitar to play through the notesToBePlayed method.


    java.lang.AssertionError:
    Expected: <[A1, B2]>
         got: <[]>

With a failing example to drive development we can now make the Guitar class play the notes represented in the given tab. The following code will result in a green bar.

    package com.tabplayer.domain;

    import java.util.ArrayList;
    import java.util.List;

    public class Guitar {

        public List<Note> notesPlayed = new ArrayList<Note>();

        public void play(Tab tab) {
            notesPlayed = tab.notesToBePlayed();
        }

        public List<Note> notesPlayed() {
            return notesPlayed;
        }
    }


Now that we have a fully functional guitar that will play each note in a given piece of tab, lets see what happens when we execute the HeyHeyMyMy scenario.

Given tab
e|--------------------------------------
B|--------------------------------------
G|--------------------------------------
D|----------0--0------------------------
A|-0--2--3----------2--0-----0--0-------
E|------------------------3-------------
When the guitar plays (FAILED)
Then the following notes will be played
A0 A2 A3 D0 D0 A2 A0 E3 A0 A0 (NOT PERFORMED)

java.lang.UnsupportedOperationException
    at com.tabplayer.domain.Tab.notesToBePlayed(Tab.java:15)

Still not done! The scenario output is telling us that the candidate step, the guitar plays, failed due to Tab#notesToBePlayed() throwing an UnsupportedOperationException. Let's fix it. Notice that JBehave stops executing the scenario as soon as a failure is detected, making it easy to see where the problem lies.

Reading Tab

The Tab class is required to convert ascii tab into a List of Note instances. This sounds like a job for a TabParser that the Tab class will delegate to. Lets write an example to see how this interaction might work.

package com.tabplayer.domain;

import static com.tabplayer.domain.Note.A;
import static com.tabplayer.domain.Note.B;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.jbehave.Ensure.ensureThat;
import org.junit.Test;
import static org.mockito.Mockito.*;

import static java.util.Arrays.asList;
import java.util.List;

public class TabBehaviour {

    @Test
    public void shouldParseTabUponConstruction() {

        TabParser parser = mock(TabParser.class);
        String asciiTab = "e|--------A3-B6-";
        new Tab(asciiTab, parser);

        verify(parser).parse(asciiTab);
    }

This example describes that we expect the Tab class to parse the given ascii tab upon constrution. Once again we see that the examples are driving out the interfaces of collaborators, developing the interface required by the client. Running the example provides the following result.

org.mockito.exceptions.verification.WantedButNotInvoked:
Wanted but not invoked:
TabParser.parse("e|--------A3-B6-")
	at com.tabplayer.domain.TabBehaviour.
        shouldParseTabUponConstruction(TabBehaviour.java:22)

We now need to ensure that Tab parses the given ascii tab upon construction. The following code will give us a green bar:

package com.tabplayer.domain;

import java.util.List;

public class Tab {

    private final List<Note> notes;

    public Tab(String asciiTab,
               TabParser parser) {
        notes = parser.parse(asciiTab);
    }

    public List<Note> notesToBePlayed() {
        throw new UnsupportedOperationException();
    }
}

The remaining functionality required by the Tab class is to return of list of notes to be played from a given piece of tab. We will therefore write an example to ensure that the Tab returns the same list of notes that are read from the given ascii tab using the TabParser.

package com.tabplayer.domain;

import static com.tabplayer.domain.Note.A;
import static com.tabplayer.domain.Note.B;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.jbehave.Ensure.ensureThat;
import org.junit.Test;
import static org.mockito.Mockito.*;

import static java.util.Arrays.asList;
import java.util.List;

public class TabBehaviour {

    @Test
    public void shouldProvideNotesToBePlayedInOrder() {
        TabParser parser = mock(TabParser.class);
        String asciiTab = "e|--------A3-B6-";

        List<Note> notes = asList(A(3), B(6));
        stub(parser.parse(asciiTab)).toReturn(notes);

        Tab tab = new Tab(asciiTab, parser);

        List<Note>; actualNotes = tab.notesToBePlayed();

        ensureThat(notes, equalTo(actualNotes));
    }
}

We run the example and get an expected failure.

    
        java.lang.UnsupportedOperationException
	at com.tabplayer.domain.Tab.notesToBePlayed(Tab.java:16)
    

The implementation of Tab#notesToBePlayed() is trivial as we need only return the collection of Note instances provided by the TabParser. Lets fix the Tab class.

package com.tabplayer.domain;

import java.util.List;

public class Tab {

    private final List<Note> notes;

    public Tab(String asciiTab,
               TabParser parser) {
        notes = parser.parse(asciiTab);
    }

    public List<Note> notesToBePlayed() {
        return notes;
    }
}

The code above will fix the previously failing example.

We now have working Tab and Guitar classes. Lets run the scenario to see if the story is complete.

Given tab
e|--------------------------------------
B|--------------------------------------
G|--------------------------------------
D|----------0--0------------------------
A|-0--2--3----------2--0-----0--0-------
E|------------------------3------------- (FAILED)
When the guitar plays (NOT PERFORMED)
Then the following notes will be played
A0 A2 A3 D0 D0 A2 A0 E3 A0 A0 (NOT PERFORMED)

java.lang.UnsupportedOperationException
	at com.tabplayer.domain.TabParser.parse(TabParser.java:15)

We still have a little more work to do. The step, given tab, is failing because the TabParser has not been implemented yet. As before we will write an example describing how we expect the TabParser to behave.

    package com.tabplayer.domain;

    import static com.tabplayer.domain.Note.*;
    import static org.hamcrest.core.IsEqual.equalTo;
    import static org.jbehave.Ensure.ensureThat;
    import org.junit.Test;

    import java.util.Arrays;
    import java.util.List;

    public class TabParserBehaviour {

        @Test
        public void shouldConvertTabIntoNotesToBePlayed() {

            String asciiTab =
                "e|--------------7--5------------\n" +
                "B|--------4-4-4-----------------\n" +
                "G|-------3----------------------\n" +
                "D|-----2------------------------\n" +
                "A|---1--------------------------\n" +
                "E|-1----------------------------\n";


            TabParser parser = new TabParser();

            List<Note> notes = parser.parse(asciiTab);

            List<Note> expectedNotes =
                Arrays.asList(E(1), A(1), D(2), G(3),
                              B(4), B(4), B(4), e(7),
                              e(5));
            ensureThat(expectedNotes, equalTo(notes));
        }
    }

The shouldConvertTabIntoNotesToBePlayed example will fail with the following message:

    
    java.lang.UnsupportedOperationException
    at com.tabplayer.domain.TabParser.parse(TabParser.java:15)
    at com.tabplayer.domain.TabParserBehaviour.
    shouldConvertTabIntoNotesToBePlayed(TabParserBehaviour.java:28)
    

Our next task is to implement the parse method on the TabParser. For brevity the implementation of the TabParser will be omited. Rest assured that the TabParser provides a green bar!

We now have complete implementations of the following classes: Tab, TabParser and Guitar. We will now execute the scenario to determine if we have completed the story, fulfilling the acceptance criteria.

Executing the scenario provides the following successful output, indicating that the story is complete with respect to acceptance criteria associated with the Play Tabs story.


Given tab
e|--------------------------------------
B|--------------------------------------
G|--------------------------------------
D|----------0--0------------------------
A|-0--2--3----------2--0-----0--0-------
E|------------------------3-------------
When the guitar plays
Then the following notes will be played
A0 A2 A3 D0 D0 A2 A0 E3 A0 A0

Summary

The preceding examples have demonstrated a BDD approach to developing software that meets the real needs of users. Throughout the process, not a line a code has been written that does not either provide business value or ensures that value has been realised through the creation of working software.

We have seen how BDD forces developers to think about the application being development from the outside-in, which in turn drives out the interfaces between collabarating objects that make up the application. The examples demonstrate that the acceptance scenarios are not enough to ensure high quality software. Developers must ensure that they resist the urge to omit writing examples of how classes behave at the unit level, which in turn will drive out interactions with other classes. Failure to do so will result in software that has high external quality, meeting the needs of it's users, but a lower internal quality increasing the cost of future development.

During the development of a user story the focus switches often between the behaviour of the application as a whole and the behavior and interactions of individual units of code.

To close, the most important concept to take away from this article is the fact that BDD is a collaborative design process involving rich communication between the stakeholders of the software and those responsible for its development. This ensures that the software being developed is of high value, meeting the needs of real users. If those conversations do not happen, then no tool, JBehave or otherwise will be able to save a project that is developing unwanted or low priority features.

Acknowledgements

Thanks to Liz Keogh for taking time to review the article and providing encouraging feedback.

References

  • [1] User Stories Applied, Mike Cohn.
  • [2] Domain Driven Design, Eric Evans

Valid XHTML 1.0 Transitional