Make no bones about it, BDD ain’t easy! It’s actually darn-right difficult and takes time to master and do it right. Luckily, the benefits far outweigh the initial costs. If you’ve decided to practice BDD then you’ve already made a conscious decision to break the ‘burn-and-scrape’ cycle, a wise choice indeed!
Still, good intentions alone are not always enough. Many developers become disillusioned and frustrated with a process that doesn’t seem to work for them. In this article, I’d like to explore some of the most common obstacles and pitfalls that fresh BDD adopters encounter and offer some tips to help overcome them.
I’ll assume that, like most Ruby BDD developers, you’re using Cucumber for your implementation. If you happen to use a different tool, or even another language, don’t panic: the principles described here remain the same, regardless of your choice of tools.
At the Beginning Was…System Definition!
“Where do I begin? How do I define behavior?”
This most commonly-asked question is also the cause of most stumbling blocks in the BDD life-cycle. Fortunately, it’s also the easiest to answer:
When starting a new project always -and I mean always – define what’s inside your system and what’s outside of it.
The circle in the center is our system. It’s what we’re modelling, designing and going to develop. The little figurines connected to our system are Actors. If we want to get off to a good start we need to start thinking like them.
What’s an ‘Actor’?
Well, certainly not Adam Sandler, LOL! (note to editor: are we going to get sued for this??). Traditionally, going back to where the term originated, an Actor is an entity external to our system that interacts with -or is acted upon by- our system. So, our end-users, system admins, and 3rd-party APIs are all Actors.
Now, I’m going to be so bold as to expand this definition, specifically for BDD:
An Actor is an external entity that has a vested interest or stake in the system and the power to safe-guard this interest by dictating system behavior.
Note that I’m not mentioning interaction here at all. This is because system behavior can be dictated by entities that have never laid fingers on your system, never interacted with it directly. Your company’s Owner or Directors are all Actors on your system, as they have a vested interest in it. As does your Sales Manager and anyone or anything else that is entitled to expect a certain behavior from your system.
Actually, many of your non-functional features (security, performance, et al) will come from these ‘passive’ Actors, so know their importance and don’t underestimate them.
Tip: Generalize Your Actors
Some Actors tend to take on different roles, depending on how they interact with our system. For instance, in a messaging or chat system, the end-user will be either a publisher or a subscriber, often both. Using the generic Actor’s name (e.g. user) simplifies diagrams and brief descriptions. Using the concrete Actor’s name (e.g. publisher or subscriber) helps flesh out specific behavior that’s not always obvious when looking at it from a generic point of view.
Tip: ‘External’ Means Just That
Let’s say we have a database that our system needs to read/write to. If this database is within our control ( i.e. we created it, maintain it. etc.) then it’s a part of our system and internal to it. If we have no control over it, other than just reading or writing to it, then the database is external to our system and should be treated as an Actor.
Let’s see what our diagram should look like now, taking into account generalizations and ‘passive’ Actors.
Once we have a clear idea of who our Actors are, we can start writing our Features. This is accomplished by going through our Actors and identifying behaviors, or sets of behaviors, that the Actor expects from our system or our system expects from the Actor. The key to successfully defining behavior and writing features is that we have to think like the participating Actor.
Here’s how we’re going to do it.
Every Feature Must Tell a Story.
Not just any ol’ story. A story in a specific format:
As an [Actor] I want [some System Behavior] So that I can [have a tangible benefit]
If the Actor in the first statement is not present in your system diagram, ask yourself if they ought to be. If the answer is “yes”, add them to your diagram. There could be other features that this Actor drives. If you don’t know the Actor’s there in the first place, you’ll never find out.
The ‘So that..’ statement must point to a tangible benefit for the Actor who drives the feature. If it doesn’t, re-word it or reject it.
Feature: User Presence As the Web-User, I want to see if other users are logged in, so that I can know if my friends are present.
Well, do you now Mr Web-User? What tangible benefit does it have to you other than satisfying your curiosity about your friends’ online habits? As it stands, this Feature’s story doesn’t justify it’s validity. This one, however, does:
Feature: User Presence As the Web-User, I want to see if other users are logged in, so that I can contact them.
See the difference? The first story results in a whim, the second one in a solid, actionable benefit.
Why Should I Care?
For starters, the second story leads to the discovery of an extra behavior/feature. The first one doesn’t. Can you guess what the extra feature is?
Even more importantly, having a valid User Story for our Feature helps us understand and define what a Feature is. Consider this:
Feature: Account Profile As a New User, I want to create an account profile, so that I can register with the system.
This Feature is somewhat flawed. The anticipated benefit (register) isn’t really a benefit but rather a means to an end. The New User doesn’t really care about registering , she just wants to create an account so she can go and use the system, that’s her ultimate goal, that’s what adds value to her interaction. So, let’s rewrite the Feature:
Feature: Account Profile As a New User, I want to create an account profile, so that I can be allowed to use the system.
OK, now we have a tangible benefit for our Actor. Still, something doesn’t seem right. In order for the user to be allowed to use the system, she’ll need to do a lot more than just create an account profile. She’ll need to create credentials, confirm her email address, and so on. In other words, she’ll have to go through a set of system behaviors in order to achieve her goal. This set of behaviors is the Feature that our system needs. So the Feature description should read like this:
Feature: Account Creation As a New User, I want to create an account, so that I can be allowed to use the system.
The individual behaviors (credentials, account profile, email confirmation, etc) will be captured in our Feature’s scenarios. They don’t warrant a separate Feature for each, as they individually fail to realize the Actor’s goal. A good Feature Story helps us see the forest for the trees.
Speak the Actor’s Language
Language is a funny thing. The same word can have different meaning depending on when, where, or by whom it’s been uttered. I’ve heard the word ‘session’ referring to three different things by three different people in the same conversation (a Product Owner, a Developer, and a Network Engineer). When writing Features, we always adopt the Actor’s language. If our Actor thinks a ‘session’ is a quick get-together with his friends over coffee, then that’s what we treat it as in our Feature. Writing a glossary of ambiguous terms that anyone can look up can be very helpful.
Actors Care About the ‘What’ Not the ‘How’
The thing is, Actors only care about results. Your end-user wants to see their bank balance on the screen. Your sysadmin wants to see some system logs. Your third-party API wants to see some data payload in a specific format. They don’t care how the system produces the bank balance, the log file or the payload as long as it does produce it correctly.
Question: What’s wrong with the following scenario?
Feature: Logging in (..story omitted for brevity) Scenario: Valid login Given I am on the "login" screen When I enter "email@example.com" in "email" field And I enter "password1" in "password" field And I click the "login" button Then the message "you are logged in" appears
Answer: It’s been written by a Developer, not an Actor. It’s all about about the ‘How’ rather than the ‘What’. If we take off our Developer hat, put on our Actor hat and re-write the feature, we should get something like this:
Feature: Logging in (..story omitted for brevity) Scenario: Valid login Given I have an authorized account Then I can login
Yes, I can hear what some of you are thinking:
“So what? Isn’t it ok to use standard web-based UI methods when we’re specifying a web-based system?! “
Well no, it kind of isn’t ok, not when we’re writing our Features. First off, our rewritten scenario is much more readable, it doesn’t get bogged down in incidental details. Secondly, it’s very plausible that, in the near future, the login method will change and will require an iris scan or some other new and wonderful technique. We don’t want to be changing our Feature Scenarios each and every time their implementation changes. This is what our Step Definitions are for. The ‘How’ goes in in our Steps, the ‘What’ stays in our Features.
Another way of looking at it is from an imperative vs declarative perspective. As Developers we love imperative style: If A then B, followed by C, etc. Actors, on the other hand, think declaratively: B(A):- C(A). And we now know how to think like Actors, right?
BDD offers huge rewards to those who can master it. By following certain principles and adhering to a specific mental model we can write stable, valuable, and readable Features. At that point, we’ve won half the battle before it’s already begun. The other half is thinking about our Step Definitions. But that’s another topic for another time :)