Natural-language BDD can be hard to maintain. Liz Keogh demonstrates a simple code-based alternative.
One of my clients recently asked me how often I use Cucumber or JBehave in my own projects. Hardly ever, is the answer, so I want to show you what I do instead.
The English-language Gherkin syntax is hard to refactor. The tools form another layer of abstraction and maintenance on top of your usual code. There’s a learning curve that comes with them that can be a bit tricky. The only reason to use the tools is because you want to collaborate with non-technical stakeholders. If nobody outside your team is reading your scenarios after automation, then you don’t need them.
There may still be other reasons you want the tools. They’ll be more readable than the code I’m about to show you. Dynamic languages are harder to refactor anyway; I work primarily with static typing. Maybe you want to take advantage of hooks for your build pipeline. Maybe you already know and feel comfortable with the tools. Maybe you just really want to learn the technique. That’s OK. But you don’t need them.
So here’s a simple alternative.
Have some conversations, and write down the examples
I like it when the developers do this, and get feedback on their understanding. Writing it in semi-formal Gherkin syntax is pretty useful for helping spot missing contexts and outcomes. All the usual goodness of Three Amigos conversations still applies.
Find a capability, and the thing that implements it
Your application or system probably has a number of things that it enables people or other systems to do. We’re going to be using a noun that matches those things as a way of starting our DSL. Here are some examples:
- Buying things → the basket
- Making trades → a trade
- Commenting on an article → a comment / the comments
- Doing banking → the account
You may find the language is a bit stilted here (I did say the English was clearer!) but that’s a trade-off for the ease of getting started with this. You might find other things which make more sense to you; it’s sometimes possible to use verbs for instance.
- Searching for a car → I search
You’ll get the idea in a moment. Each of these is going to be the stem of a bit of code.
Start with comments in the code
Sometimes I like to just start with my scenario written in comments in the code. For each step, think about whether the step has already happened, is the thing that triggers some interesting behaviour, or is the outcome of that behaviour. Add Given, When or Then as appropriate:
// Given an article on Climate Change // When I post a comment "This is a really // conservative forecast." // Then it should appear beneath the article.
Add the Given, When or Then to your stem, and…
- Given the basket…
- When the trade…
- When a comment…
- When a search…
- Then the account…
…construct your steps!
Now we’re in code.
GivenAnArticle().on("Climate Change") GivenTheBasket().contains("Pack of Grey Towels") WhenTheTrade().isCreated() .withCounterparty("Evil Corp") .forPrice(150.35, "USD") ..... .andSubmitted() WhenISearch().For("Blue Ford Fiesta") ThenTheAccount().shouldHaveBalance(15.00, "GBP")
You can see that trading one is using a builder pattern; each step returns the trade being constructed for further changes, until it’s submitted. I sometimes like to use boring, valid defaults in my builder so that these steps only call out the really interesting bits.
I normally suggest that a ‘When’ should be in active voice; that is, it should show who did it. If that’s important, add the actor.
WhenTheTrade().isCreated() ..... .andSubmittedBy("Andy Admin")
or
WhenTheTrade().isCreated() .by("Andy Admin") ..... .andSubmitted()
Active voice would normally look more like:
When("Andy Admin").createsATrade() .... .andSubmitsIt()
But now our ‘When’ is ambiguous; we can’t tell which kind of capability we’re about to use, so it makes it really, really hard to maintain. It’s OK to use passive voice for DSLs.
As I construct these, I delete the comments.
Sometimes I like to just put all the detailed automation in which makes the steps run, then remove the duplication by refactoring into these steps. (Sometimes it’s enough just to just leave it with detailed automation, too, but at least leave the comments in!)
Pass the steps through to Page Objects; use the World for state
You’ll probably find you need to share state between the different steps. I normally create a ‘World’ object, accessible from the whole scenario.
Each of the stems you created will correspond to one or more page objects. I like to keep those separate, so my steps in the DSL don’t do anything more than just call through to that object and return it.
Listing 1 is an example of my scenario object for a Sudoku solver.
public class Scenario { private readonly SudoqueSteps _sudoqueSteps; private readonly CellSteps _cellSteps; private readonly HelpSteps _helpSteps; private World _world; protected Scenario() { _world = new World(); _sudoqueSteps = new SudoqueSteps(_world); _cellSteps = new CellSteps(_world); _helpSteps = new HelpSteps(_world); } protected CellSteps WhenISelectACell{ get { return _cellSteps; }} protected CellSteps ThenTheCell{ get { return _cellSteps; }} protected SudoqueSteps GivenSudoque{ get { return _sudoqueSteps; }} //... protected HelpSteps WhenIAskForHelp { get { return _helpSteps; } } protected HelpSteps ThenTheHintText { get { return _helpSteps; } } } |
Listing 1 |
It does get quite long, but it’s pretty easy to maintain because it doesn’t do anything else; all the complexity is in those underlying steps.
And Listing 2 shows how I use it.
[TestFixture] public class PlayerCanSetUpAPuzzle : Scenario { [Test] public void APlayerCanSetUpAPuzzle() { GivenSudoque.IsRunning(); WhenISelectACell.At(3, 4).AndToggle(1); ThenSudoque.ShouldLookLike( "... ... ..." + NL + "... ... ..." + NL + "... ... ..." + NL + " " + NL + "... ... ..." + NL + ".1. ... ..." + NL + "... ... ..." + NL + " " + NL + "... ... ..." + NL + "... ... ..." + NL + "... ... ..." + NL); } } |
Listing 2 |
Full scenarios are available at https://github.com/lunivore/sudoque/tree/master/Sudoque.Scenarios .
This one was written in plain old NUnit with C#. I’ve done this with JUnit and Java, and with JUnit and Kotlin. The examples here are only from toy projects, but I’ve used this technique on several real ones.
There are lots of tools out there which help you to construct these kind of DSLs; but I’ve found they also come with their own learning curve, constraints, maintainability issues etc.. This is a pretty easy thing to do; I don’t think it needs anything more complicated than I’ve put here.
It’s also very easy to refactor overly-detailed, imperative scenarios, of the kind created by lots of teams who didn’t know about the conversations, into this form.
It’s easy to move to the BDD tools if you need them
With your Page Objects already in place, it’s pretty quick to get something like Cucumber up and running and make the step definitions call through to the page objects exactly as you were before, with just a little bit of refactoring of method names.
It’s a lot harder to move from Cucumber and regex to a DSL.
Chris Matts once had some great wisdom. “If you don’t know which technology to choose, pick the one that’s easy to change. If it’s wrong, you can change it.”
This is the one that’s easy to change, so I tend to start with this. And sometimes it doesn’t need to change.
This article was first published on Liz Keogh’s blog: https://lizkeogh.com/2019/08/27/scenarios-using-custom-dsls/ .
is a Lean and Agile consultant based in London. She is a well-known blogger and international speaker, a core member of the BDD community and a passionate advocate of the Cynefin framework and its ability to change mindsets. She has a strong technical background with 20 years’ experience in delivering value and coaching others to deliver, from small start-ups to global enterprises. Most of her work now focuses on Lean, Agile and organizational transformations, and the use of transparency, positive language, well-formed outcomes and safe-to-fail experiments in making change innovative, easy and fun.