Features are documentation, not tests

April 2013

Cucumber features are primarily documentation, and only incidentally tests to execute.

However, it’s easy to fall into the trap of writing features which don’t actually read like documentation at all, but a long, tortuous, and highly repetitive mess of tests.

The problem

Here’s an example of a feature that’s difficult to read.

    Feature: User adds a task to a project

    As a user I want to add a task to a project, so that I can track
    what's left to do on the project.

    Scenario: User adds tasks
      Given a user called "User"
      And a project called "Project"
      And the user "User" has permission "Task managment" for the project "Project"
      When I add a task "Task" to the tasks list of the project "Project"
      Then the task "Task" should be listed on the task list of the project "Project"

I’ve written many scenarios like this in the past: I’m sure many of us have. What’s wrong with it?

It doesn’t read like English

Scenarios like this are easy to code, because all the information you need is right there. However, they don’t read well: they are very repetitious in their use of language and are difficult to scan. They don’t read like English.

In my experience, those who are the best at coding are often the worst at writing features. They tend to think of steps like code and apply good coding principles to them. For example, they try to reuse steps as much as possible (the DRY principle), and give steps all the information they need to run (the Dependency Injection principle).

This sounds sensible, but it leads to poor scenarios and poor step code in practice. Steps are not method calls, and shouldn’t read like them.

The whole point of Cucumber is that we get the ability to use natural language to make our features easier to understand. Writing a feature in this way is akin to writing this blog post in the following style:

“If I kept mentioning the feature when discussing the feature with my audience, my audience will get bored with the fact that I kept mentioning the feature when discussing the feature with my audience, and my audience as a result of getting bored will lose interest in the feature that I keep mention when discussing the feature with my audience…”

Nobody speaks or writes like that! The key difference is that we use context when we communicate to people. We use shorthands such as ‘it’ to refer to someting that we’ve just discussed, and we constantly assume a huge amount of knowledge on the part of our listeners.

Using member variables in step definitions helps give context

When I’m tempted to be too repetitous and start writing my features like test code, I start using member variables to capture what the current state is my steps (I’ve written about the pros and cons of this before: see the article linked at the end of the post.) As long as you are judicious with your member variables and don’t let them get out of control, they can make your features much easier to read. Let’s restate the previous feature using context:

    Feature: User adds a task to a project

    As a user I want to add a task to a project, so that I can track
    what's left to do on the project.

    Scenario: User adds tasks
      Given a project called "Project"
      And I'm signed in as "User"
      And I have permission to add tasks to the project
      When I add a task to the project
      Then the task should be added to the project

Much easier to read. Here’s what the steps might look like:

    Given /^I'm signed in as "(.+?)"$/ do |name|
      @user = User.new(name)
    end

    Given /^a project called "(.+?)"$/ do |name|
      @project = Project.new(name)
    end

    Given /^I have permission to add tasks to the project$/ do
      @project.add_permission_to(@user, "Task management")
    end

Once you are freed from needing to refer to the domain objects you are using in every step, you are able to write your feature in pretty much the way you want to, and you can make them into true documentation, not matter what your audience.

Reading like a game manual

If we’ve now got the tools to make our features more flexible, then how about we try and make them true documentation that’s useful to our end users, not just our stakeholders?

For example: currently I’m in the process of working up a web prototype of some of the game mechanics on Sol Trader, a game I’ve been working on for the last year. The aim is to quickly iterate on the core simulation: it’s hard to do anything quickly in raw C++.

In order to document these game mechanics for my beta testers, I could have sat down and written a long markdown file, but instead I decided using the cucumber features themselves to describe the behaviour that I want to capture. I’ve used the preamble to write my “acceptance criteria” in prosaic format so that someone new to the game will be able to undrstand how to play. The scenarios are written as specific examples of game behaviour that still reads like a manual.

Here’s an example feature of a character joining a fleet:

    Feature: Joining other characters' fleets

    A character that is travelling to another location is vulnerable
    to attack from pirates. If you wish, you can choose to join a
    character's fleet as they travel to bolster their defences against
    any attack that they might come under. You will also participate
    in any attacks they might make against pirates.

    You choose to join travelling characters through meeting them in
    the Spaceport Bar, or by contacting them on the Comms Channel
    should you be in open space. Once you are in a character's fleet,
    you will stay with them until they leave the location, and you
    will travel with them to their destination. You can choose to stop
    being in the fleet at any time.

    ...

    Scenario: You can't join a fleet when someone is in the middle of a travel order
      Given you are playing Eddie
      And you are in Earth Orbit
      And Terry travelled to Mars Orbit a turn ago
      Then you won't be allowed to join Terry's fleet

I’ve chosen here to reverse the normal “Given I do this” and instead use “Given you do this” - it feels more like a game manual when I write the feature like this.

Hopefully it’s clear enough to understand, assuming you had some context about travel orders. Let me know what you think.

Stories that are specific but not soporific

Using a tool like Relish, which allows you to publish your features to a website, is a great way to ensure that the documentation that your project is producing is actually readable over time. When forced to read your features as documentation on a website, rather than as “code” in your text editor, you immediately start seeing ways that they can be improved.

The more interesting we make our features to read, the more likely people will read and update them.

A challenge for today: pick one scenario on your project and read it out loud to a non-technical co-worker. If they don’t get a sense of what the scenario is describe, try and rework it until they do.

Further reading

  • Should we store state in our steps: a blog article I wrote last year discussing the pros and cons of member variables in steps.

  • Sol Trader: the space trading and piracy game I’ve been writing for the last year or so. The online version will be available at http://online.soltrader.net once it’s ready.

  • Relish: a great tool for getting your features published in a non-technical format. Full disclosure: I train with the creator of Relish at BDD Kickstart, but although I don’t personally benefit from Relish. It’s a great tool though, you should use it.


Share


More articles

Extreme YAGNI: How BDD nails your prototyping stage

prototyping

Sometimes people don’t see the value in the BDD process. They contend that the BDD ceremonies are a waste of time, and get in the way of delivering real features to customers. Others cannot see how to apply BDD to their project, as no-one knowns exactly what the project will look like yet. As they’re only in the prototyping stage, by the time a feature file is written and made executable, it’s already out of date.

I don’t agree with this. If our process is set up right, we can prototype using just as effectively and retain the collaboration benefits that BDD gives us.

You Ain’t Gonna Need It

One of the biggest wins that Test-driven Development (TDD) gives us is the principle of YAGNI - “You Ain’t Gonna Need It”. It’s very tempting when writing code to go off on a tangent and produce a beautiful structured work of art that has zero practical use. TDD stops us doing this by forcing us only to write code that a test requires. Even some experts who don’t practice or encourage TDD often espouse the power of writing the calling code first in order to achieve much the same effect.

BDD gives us the same YAGNI win: but at a level higher than TDD. With the BDD cycle we’re adding thin slices of customer observable behaviour to our systems. If we can only write the code thats directly used by the business, then in theory we should be cutting down on wasteful development time.

However, there’s a snag here. if we’re prototyping, we don’t know whether this feature will make it into the final product. We still need to give feedback to our product team, so we need to build something. If the feature is complex, it might take a while to build it, and the feature might never get used. Why bother going through the process of specifying the feature using BDD and Cucumber features?

Happily, we can take YAGNI a level further to help us out.

Extreme YAGNI

Often in TDD, and especially when teaching it, I will encourage people to take shortcuts that might seem silly in their production code. For example, when writing a simple supermarket checkout class in Javascript, we might start with a test like this:

    var checkout = new Checkout();
    expect(checkout.total()).toEqual(0);

Our test defines our supermarket checkout to have a total of zero on creation. One simple way to make this work would be to define the following class:

    var Checkout = function() {
      this.total = function() {
        return 0;
      };
    }

You might think that’s cheating, and many people define a member variable for total, set it to 0 in the constructor, and miss this step out entirely. There is however an important principle at stake here. The code we have does exactly what the test requires it too. We may not need a local variable to store total at all.

Here’s the secret: we can practice this “extreme YAGNI” at the level of our features, too. If there’s a quick way to make our feature files work, then there’s nothing to stop us taking as many shortcuts as we can to get things working quickly.

For example, if we’re testing the user interface of our system via Cucumber features, one fast way to ensure things are working is to hard code the values in the user interface and not implement the back end behaviour too early. Why not make key pages static in your application, or hard code a few cases so your business gets the rough idea?

Again, you might think that’s cheating, but your features pass, so you’ve delivered what’s been asked for. You’ve spent the time thrashing out the story in a 3 amigos meeting, so you gain the benefits of deliberately discovering your software. You’re giving your colleagues real insight to guide the next set of stories, rather than vague guessing up front. Our UX and design colleagues now have important feedback through a working deployed system very quickly, and quick feedback through working software is a core component of the agile manifesto.

By putting off implementing the whole feature until later, we can use BDD to help us navigate the “chaotic” Cynefin space rather than just the “complicated” space. This in theory makes BDD twice as useful to our business.

Fast, fluid BDD

This all assumes that we have a fast, fluid BDD process, with close collaboration built in. If it takes a week to coordinate everyone for the next feature file, then the temptation is to have a long meeting and go through too many features, without a chance to pause, prototype, deliver and learn from working software. Maybe it’s time to re-organise those desks and sit all the members of your team together, or clean up your remote working practices, or block out time each day for 3 amigo sessions. You’ll be suprised how much small changes speed your team up.

Read more

Why you can scale agile with the right attitudes

The more I work with teams, the more I think agile at any scale lives or dies based on attitudes of people in the organisation, rather than the scale at which you are attempting to do it.

Software development is hard, and it’s harder the greater the scale as Rachel’s post eloquently argues. However, I don’t think the scale is always the limiting factor here.

I’ve seen small teams with such a lack of trust that they burden themselves with suffocating process, and larger organisations with so much trust that they’re able to operate at the speed of organisations a quarter of their size.

As organisations get bigger, the trust required gets greater. If the trust exists, teams are able to remain independent and don’t get burdened with process.

If you need to work at one thing to make your large organisation more agile, work on building trust between teams, and between teams and management. Try small wins, quick feedback, and constant celebration. Decentralise decisions, give people ownership in their areas, and act like they’re as good as you know they are.

If middle and upper management could be persuaded to give up control and provide leadership instead, your scale matters much less than you might think.

Read more

Why BDD works solo, and why that matters for everyone

“The monotony and solitude of a quiet life stimulates the creative mind.”

– Albert Einstein

It’s possible to play chess and many other games completely solo, without anyone else to play against.

solo chess

Incredibly, our minds are able to take on the role of both players in a competitive match. Our brains allow us to place ourselves in the opposing player’s shoes. Even more impressively, we are able to hide certain information from our own decision making process – such as what we’re planning to do next turn. We can analyse this incomplete set of information and play a move based solely on that, even if it’s sub-optimal.

BDD can be done solo

Using Behaviour-driven Development (BDD) on our own is much the same as playing a game against ourselves.

Instead of playing the opposing role, we take on the role of the stakeholder. We put ourselves in the shoes of the person paying for or commissioning the project. Because the BDD practice of writing gherkin-style features forces us to think in plain English, we jettison the role of programmer for a moment, and drift in the non-technical solution thought-space.

Why is this important? It’s a valuable exercise for a couple of reasons:

  • We gain a fresh perspective on what we are doing. By taking our heads out of the code and putting ourselves into the role of the stakeholder, we are able to see the wood for the trees and get perspective on the code we’re writing right now. It might be interesting code to write, but does it matter?

  • We gain a fresh perspective on the stresses of others. Every developer should take the opportunity to be a stakeholder once in a while. It’s very helpful to see what life it like on the other side of the estimation table. If all the members on a team are able empathise with the constraints and pressures our colleagues from different disciplines have, our team will run much more smoothly.

BDD solo is why BDD matters in the first place

Sometimes we confuse the concepts behind BDD with the tools used to practice BDD, like 3 amigo meetings, story breakdown and Cucumber.

BDD isn’t about the tools we use, it’s about the communication in the team. It’s about having the right conversations at the right times, hammering things out together, airing and refining thoughts and ideas, beating out ambiguity and forging common goals. This is why it matters.

If it was just about the tools, BDD would have been lost in the noise years ago, and BDD solo would be an expensive overhead. Why add another layer of tool complexity if it’s just us?

However, because BDD is about the concepts, not the tools, BDD works just fine solo: the process of deliberately stepping outside our developer role and thinking through our project from a product perspective gains us insight and understanding. This works no matter what tools, languages, frameworks or practices we may prefer.

When we’re solo, we dispense with a lot of the tools we don’t need. We don’t need to schedule a 3 amigo meeting with ourselves, for example. We’re left with a very pure form of BDD, set free from the needed constraints of multi-person teams.

Then we can begin to understand which of the common practices of BDD are only there to work around problems and can be discarded as needed, and which are truly foundational.

Try it out

Try using BDD on a personal project. Play the product owner, and write some Gherkin-style features. Pretend you can’t code. Really think about your problem without thinking about exactly how you’re going to solve it.

Whilst doing this, reflect on what practices you really need, and which you don’t. Which tools are less useful on your own? Which are more useful?

If you feel we’re just wasting time, I’d challenge us to think about what that’s telling us about our own ‘standard’ agile practice. How much of it is truly making us more agile, and how much of it is simply getting in the way?

Read more

How not to check in temporary code

facepalm

We’ve all done it.

We were skimming through a set of changes before checking in our code, trying to get the branch pushed up to the build server just before lunch. We’d forgotten that before we went home last night, we added a couple of lines of code to a method which sets up a debug state for testing. Easy to miss; and miss it we did. The change goes up, the build breaks, and we’re left feeling embarrassed.

How do we ensure we don’t check in temporary code? One way to do so is to utilise that old refactoring staple, Extract Method, along with the power of good naming.

Pull out temporary code into a method

Here’s some temporary code currently nestled inside the Card Pirates method for starting a new game. It fixes the position and the cards for the first two players, so I can easily jump to and test the combat user interface.

    def start_new_game
      @state = GameState.new_game(@queue)

      attacker = state.players.current_player
      defender = state.players.next_player

      attacker.x = 3
      attacker.y = 3
      attacker.hand = Card.hydrate ["10_of_england", "4_of_spain", "3_of_france"]

      defender.x = 4
      defender.y = 3
      defender.hand = Card.hydrate ["8_of_england", "3_of_spain"]
      state.map.square(4, 3).face_down_cards = []

      init_services(@state.current_player.name)

      @queue << Command.new(:start_game)
      @queue << Command.new(:start_new_player_turn, @state.current_player.name)
      show_board(@state.current_player.name)
    end

Can you spot where the temporary code stops, and the ‘real code’ starts? It’s difficult, isn’t it? I thought so too, so I extracted the temporary code into a method, and gave it a very obvious name:

    def test_the_combat_dont_check_in(state)
      attacker = state.players.current_player
      defender = state.players.next_player

      attacker.x = 3
      attacker.y = 3
      attacker.hand = Card.hydrate ["10_of_england", "4_of_spain", "3_of_france"]

      defender.x = 4
      defender.y = 3
      defender.hand = Card.hydrate ["8_of_england", "3_of_spain"]
      state.map.square(4, 3).face_down_cards = []
    end

    def start_new_game
      @state = GameState.new_game(@queue)

      test_the_combat_dont_check_in(@state)

      init_services(@state.current_player.name)

      @queue << Command.new(:start_game)
      @queue << Command.new(:start_new_player_turn, @state.current_player.name)
      show_board(@state.current_player.name)
    end

This has a number of advantages:

  • It’s quick to do. It only takes a minute to make a change like this, and saves you a lot of headaches later.
  • It’s easy to spot in a diff. It’s much harder to get out all the code unless it’s clearly encapsulated like this.
  • It’s easy to spot for someone else. If instead of going on a lunch break you were cycling home for the day, a co-worker would probably be able to spot and fix this before you got back online.
  • It might be further refactored later. These kind of ‘put the system in a certain state’ methods can actually be useful later - we might be able to use this for a tutorial mode, for example. Temporary code often ends up being much less temporary that we originally envisage.

I’ve found that small refactorings like this often save me lots of time later. How do you stop yourself checking in the wrong code?

Read more

How to decide whether a tool is right for you

tools

We are only at the beginning of our journey in building software. Our discipline is barely a few decades old. We have only a very little experience in how to correctly write code and a limited range of tools and skills to do it with. We should be actively looking for new tools, not wasting our time either promoting our toolset exclusively or disparaging the toolsets of others.

Tools are tools

Test-driven development has been one of those tools that has proved useful for many people over a number of years. Do I use it? Yes, much of the time.

Using a refactoring IDE has also proved useful to many people over a number of years, especially in certain languages. Do I use one? No. Does that mean it’s not a useful tool to others? No, of course not.

The ability to decouple code to promote changeability is also a great tool to have in your toolbox. Do I try and do this? Yes, wherever I can, and I’m always trying to get better at it.

There are many more. The insight as to when to refactor, not just how, is an incredibly valuable skill to have. The understanding that all code is built for someone and we should ensure we talk to them about what they want is powerful. Being able to check into source control without touching the network is a real speed boost, and gives me a detailed history of progress.

The recent TDD storm

So why do we get so hung up on one particular tool? When something works for us, we’re compelled to proclaim it’s the One True Way and that it’ll work for every problem and solve every headache. This is a grevious error, but in avoiding it, we can make the opposite one: when we find a tool’s limitations, we discard it completely and move on, proclaiming it useless for all.

DHH has a point. Don’t listen to the people who say there’s only one way to do a job, and that we should use any tool for everything.

Gary Bernhardt has a point. Test speeds do matter - the faster the better. Fast tests are a powerful tool.

Uncle Bob has a point. It’s not just about fast tests: separating concerns in order to promote changeability in code is a useful skill to learn.

Tom Stuart has a point. TDD is a useful tool because it gives you another client for your code, encouraging us to think harder about what it’s doing.

Seb Rose has a point. We need to learn how to use TDD (or indeed any tool) well before giving up on it.

Cory House has a point. People display their own biases in their opinions and we should learn from them all.

How to decide whether a software tool is right for you

It doesn’t matter whether it’s TDD, Vim, Git, Refactoring, OO, Functional programming, JavaScript, RubyMotion, etc. Follow the following advice repeatedly, substituting your own values:

If you haven’t tried tool X, give it a go. Many have found it helpful in areas Y and Z. Some have also found it applicable in areas A and B, but your mileage may vary. Some don’t get on with it, and a few hate it and say no-one should use it. Learn it properly before making any final decision about its usefulness to yourself and others. This will take <an amount of days/months/years> to do. Continue using it as long as it’s helpful to you.

There are as yet very few absolutes with software tools - we’re still way too primitive in our discipline for many of those. Let’s learn how to use as many tools and skills as possible, and use the right ones for the job. Let’s not decry the tools, skills and techniques of others if they are useful to them: let’s instead spend our energy actively seeking new skills and tools to further our discipline.

Read more