Specification By Example… By Example

One of the classic causes of problems in software engineering is a disconnect between specification and implementation – often in the form of ambiguity, incompleteness, or contradictions. Defects can be introduced at through the way requirements are described, that could be caught on paper for very low cost but go unchecked and cost far more to fix after development time has been invested. Gojiko Adzic’s book Specification By Example proposes Behaviour Driven Development, with requirements expressed in a format that is both readable and executable, written in close collaboration between specialists in the business and delivery, as an antidote – and this can be very effective.

At the heart of Behaviour Driven Development is the practice of expressing the requirements for a feature as a set of scenarios, written in a way that can be shared between readers from both technical and business backgrounds. These scenarios are then used to implement tests against the system, so that the behavior of the system is measured against the original scenarios with as little translation or interpretation as possible. This often takes the form of scenarios written by composing steps, so that new scenarios can be formed by re-use of existing steps in a different permutation. These scenarios should be completely agnostic of the implementation, and it is possible to use the same scenarios with steps implemented differently to perform different types of test against the system. For example, the steps could be implemented with simple method calls against stubbed infrastructure to give a fast-running set of component tests, or with automation bindings against a user interface to give slower end-to-end coverage of a deployed instance – with no modification to the actual scenario, which is simply applied to a different set of steps.

A popular format for writing BDD scenarios is the Gherkin language, also commonly known as Cucumber (the Ruby implementation) and used in SpecFlow (a .Net implementation). Gherkin is based around a syntax of Given… When… Then to provide a semi-formal structure to the scenarios. The steps are then bound to method calls by the specific BDD tool (SpecFlow, Cucumber etc), with parameters extracted from the scenario text.

  • Features are used as a container to group related Scenarios
  • Scenarios give an example of system behavior
  • Scenarios are built up from Given, When and Then steps
  • If there are multiple steps of the same type, And can be used in place of Given, When or Then
  • A Given step describes a precondition, something that has already happened that contributes to a known state before the action we are interested in; there can be many Givens, although for readability it is worth trying to condense these. this can be helped by use of Background, which describes a set of Givens that apply to all scenarios in a feature
  • The When step describes the action that the scenario is all about; there should be a single When step in a scenario
  • The outcomes of this action are described in Then steps

A simple example

Think back to primary school maths…

Given an apple costs £0.40
And a banana costs £0.25
And I have £2.00
When I buy
    | Item        | Quantity    |
    | Apple        | 3        |
    | Banana    | 3        |
Then the total cost is £1.95
And my change is £0.05

Notice that we have multiple Given steps, building up the information needed to fully describe the scenario. There is a single When step, describing an action that causes a change in the system we are interested in. In this case I’ve thrown in a table step – which is a way to express a more complex fact about the system in a more readable way. Finally, there are some Then steps, describing the outcomes we are interested in.

Building it up

When I’m creating sets of these scenarios, I normally start with a skeleton of just the scenario headings. From these, I pad out the simpler scenarios with steps first. As I get to more complex scenarios, I might go back and tweak the previous ones to accommodate the later ones. For example, to describe the game of Hangman I might start with headings:

Scenario: New game

Scenario: Guessing a letter that appears once

Scenario: Guessing a letter that appears multiple times

Scenario: Completing the word

Scenario: Guessing incorrectly

Scenario: Repeating an incorrect guess

Scenario: Guessing incorrectly on last life

From this point, I’d pad out the first scenario:

Scenario: New game
When I start a new game with the word “hangman”
Then the mask shown is “_______”
And the player has 5 lives left

Notice that this time there was no need for any setup, and the scenario simply starts with a When step. We can at this stage implement steps for this test, and the production code to make it pass, or we can keep going. I usually try to make the most of time with a Product Owner, and press ahead with scenarios that I can implement later. Let’s pad out some more scenarios:

Scenario: Guessing a letter that appears once
Given the word “hangman”
And the mask “_______”
When guessing the letter “h”
Then the mask shown is “h______”

Scenario: Guessing a letter that appears multiple times
Given the word “hangman”
And the mask “_______”
When guessing the letter “a”
Then the mask shown is “_a___a_”

Notice here that we are re-using steps – there are parameters in the steps that change, but the step itself is the same. This is normally implemented in bindings with things like regular expressions. These scenarios are perfectly complete, so let’s add some more:

Scenario: Completing the word
Given the word “hangman”
And the mask “hangma_”
When guessing the letter “n”
Then the mask shown is “hangman”
And the game is won

Here we’ve added an extra fact about the game in the form of another Then step. We didn’t need it at first, but we’d may as well add this step to the previous tests as a further piece of validation:

Scenario: Guessing a letter that appears once
Given the word “hangman”
And the mask “_______”
When guessing the letter “h”
Then the mask shown is “h______”
And the game is ongoing

What’s interesting here is that by thinking of more scenarios, our depth of thought about the simple scenarios grows. We can take things about the domain for granted, but adding scenarios encourages us to be more explicit about these concepts. Let’s add another:

Scenario: Guessing incorrectly
Given the word “hangman”
And the mask “_______”
And I have 5 lives left
When guessing the letter “z”
Then the mask shown is “_______”
And there are 4 lives left
And the game is ongoing

Scenario: Guessing incorrectly on last life
Given the word “hangman”
And the mask “_______”
And I have 1 lives left
When guessing the letter “z”
Then the mask shown is “_______”
And there are 0 lives left
And the game is lost

Again here, we have added a new piece of information to our model of the game, because the new scenario required it. We can back-fill previous scenarios, to make it explicit that a player does not lose a life when they guess correctly. Let’s add the last scenario:

Scenario: Repeating an incorrect guess
Given the word “hangman”
And the mask “_______”
And I have previously guessed
    | Guess        |
    | z        |

And I have 5 lives left
When guessing the letter “z”
Then the mask shown is “_______”
And there are 5 lives left
And the game is ongoing

Once again, we added depth to our model, but only because a scenario needed it. When this happens, we have a choice – we can back-fill previous scenarios to make them more explicit, or we can choose not to. In this particular case, I would probably choose not to back-fill. By not back-filling, it can make it clear to a reader of the scenarios exactly what data is needed for each scenario. This example is simple, but a more complex system might have many more variables. If we were to explicitly declare the full state of all of these variables, the scenario would lose clarity, as it’s not obvious which variables are pertinent to this particular scenario. A common mistake, especially if scenarios are being used to drive automation, is to make the scenario overly verbose, with a long set of steps to manipulate the full application state. A better approach here for readability and maintainability is to provide the most terse set of steps needed for the feature being described, and leave anything else out – these things can be coordinated from the steps directly. Writing scenarios that are implementation-agnostic, and can be run against a function directly or against an automation framework, gives more value from the exercise, and also normally results in better quality of scenarios.

Summing up

By defining the behavior of a system in an implementation-agnostic format, that can be shared and worked on collaboratively with non-technical members of the team, we open up some real benefits. It’s relatively easy to get going with this, and really not much extra work – if we’re going to build something we have to define its behavior somehow, so we’d may as well do it in a way that everyone can work with. It is often much quicker to work this way, as the requirements go through fewer translations. To get started, why not try writing some scenarios to describe a familiar problem space – I like to use games when teaching this as a workshop, because they have well-known and tightly defined rules which lend themselves to this format. Once the pattern is familiar from trying simpler examples, it’ll be easier to apply the same technique to the task at hand. Try and get your product owner or business experts involved with this – it can be a lot of fun, and you’ll probably all benefit from the exercise of adding structure to the description of the system’s behavior.