Reputation: 11197
I'm new to BDD and trying to use it to flesh out Functional Specs on a new project.
Most examples you see online seem very simple and light in regards to detail. I'm struggling to know what is important to include in a scenario and what should not be included.
For example, given a title like: A User Signs In
Scenario 1: A user signs in with valid credentials
Given the Sign In page is displayed
When the user enters a username and password
And the user submits the request
Then the user is directed to the Home page
What I don't understand is, what if the user has been disabled by the administrator or locked out due to too many failed password attempts, etc. Are those separate scenarios? If so, does the Scenario 1 need to indicate that this user is not disabled and is not locked out?
What about things that happen on the back end -- i.e. the client wants the system to log every user log in -- is that included as part of the scenario? Most things I've read make it sound as though BDD is supposed to stay focused on the user interaction with the system.
If you do include things like logging in the back end -- what about things like incrementing a failed login counter on a failed login attempt? This seems like more of a technical detail -- so where does it get documented if a lockout feature is required?
As you can see, I'm having a hard time deciding where to draw the line in regards to the scope of the BDD scenario.
Thanks for help in getting a better understanding!
Upvotes: 3
Views: 353
Reputation: 17637
What contexts should we include in a scenario?
I find it helps to think of scenarios not as tests, but as living documentation that helps to illustrate how the system behaves and why that's valuable.
If you can tell that the user isn't locked out and isn't disabled in that first scenario, then you don't need to include those steps.
For something like logging in with an active, enabled account this is obvious, but it might not be for something like a trader staying within their trading limits, or a fetal heartbeat running at a healthy rate, or a comment that hasn't yet reached the reporting threshold. You can decide whether to include those kind of contexts pragmatically.
I prefer though to move documentation that doesn't change or doesn't change very often, like core domain concepts, outside of the scenarios. Perhaps it can be at the blurb at the top of the scenario file, or on a team wiki somewhere that new joiners get to read before they code. It doesn't have to be in the scenario.
(Obviously you would need to include the contexts which cause the failure in any failure scenario though!)
What about side-effects and other outcomes?
All outcomes that matter do so because at some point, they become visible or have effects beyond the system whose behaviour is covered in the scenario.
For instance, when I get money from a cash machine, my account is also debited. I might not see that as part of the behaviour, but it does have to happen. That's because there's another stakeholder involved in the scenario - the bank.
Or perhaps, when Fred gets a refund on his microwave, the microwave is put back into stock and the stock count is incremented. Fred, the till operator and the stock controller are all stakeholders here.
In the case where we're adding something to the logs, there's a stakeholder who's going to use those logs for something else. Having an awareness of that value can help us work out why we're logging, and describe that outcome from the POV of the stakeholder who finds it valuable.
If the outcomes can be shipped independently and still be valuable, they can appear in separate scenarios. I'm pretty pragmatic about this, so I might put them in the same scenario to start with, then refactor them out later once the number of "stock control" scenarios means that it should probably have its own feature files.
If the outcomes can't be shipped independently, which is usually the case with anything transactional, I like to see those outcomes appear together in at least one scenario, to make it obvious that they're related. They don't have to appear in every scenario. For instance, once we've written the scenario where the user doesn't have funds so their account doesn't get debited, we probably don't have to mention the account again in any other cash withdrawal failure scenarios.
This perspective of multiple stakeholders is also related to the BDD concept of "Outside-In", where all the behaviour we add to the system is done to provide a value to some stakeholder through some interface.
What about interactions with other users or with time?
Sometimes - very occasionally - we need more than one when to describe what's happening, and the example of a login counter being incremented is a great one to use.
The value of the login counter is only that it disables the account after 3 attempts (for instance). It has no value in and of itself. It's an implementation detail. If we made this into an outcome in a scenario, we'd be coupling ourselves to that implementation, which wouldn't be great. So having two scenarios - one to describe how the login counter works and one to show why it's valuable - doesn't feel right since one of them isn't describing valuable behaviour:
Given Clarence logged in successfully last time
When Clarence enters his password incorrectly
Then the login counter should be incremented.
Given Clarence's login counter was incremented twice
When Clarence enters his password incorrectly
Then his account should be disabled.
Yuk. Let's not do that.
The only value in that counter is from the interaction of Clarence's behaviour with Clarence's future (or past) behaviour. So we could have something like:
Given Clarence logged in successfully last time
When he enters his password incorrectly
And enters his password incorrectly
And enters his password incorrectly
Then his account should be disabled.
Of course, that's a bit of a mouthful, so we'd probably just say:
Given Clarence logged in successfully last time
When he enters his password incorrectly 3 times
Then his account should be disabled.
We can do that because it's the same thing happening. Some interactions though involve different things happening ("time passing" is another one I frequently encounter).
Given Clare is editing trade 12345
When Stephen edits and saves that trade
And Clare tries to save that trade
Then she should be told that Stephen has already edited it.
In this case of course Clare's interaction is different to Stephen's, since her attempt to save fails.
Note the use of tries here to indicate failure; another example of how I use assumptions. If it doesn't say tries we can assume that the event was successful. The alternative would be:
When Clare saves the trade successfully
Unless a successful outcome is somehow surprising and not the norm, this would get a bit repetitive, so I prefer to use nothing for the default and tries for failure. Having a difference between them is important from an automation perspective since it lets us do things like move the automated workflow to an error page instead of the confirmation page.
It also reads quite nicely. Which is pretty much why we're trying to use these English language tools anyway.
Upvotes: 5