I’ve been doing a bunch of BDD acceptance tests and automation lately, having some wins and losses and learning new stuff as I go. Whilst enjoying working with Cucumber, I had a bit of challenge with sharing and issolation.
I’m not sure I like the solution I arrived at, I need to use it in anger more to see that for myself. It’s a bit complex and I still don’t like the global-ness of the world stuff, could be evolved somewhat. I hope it at least gives some ideas to others or helps someone come up with something more elegant!
The problem …
I was writing a bunch of scenarios for Features and wanted to re-use Steps and related code across various Features.
I was hoping to make it quicker to compose new Scenarios, quicker to modify and maintain by drying up my code a bit. Cucumber and Groovy can be a bit challenging, the way step files are compiled and the scope of variables between them seemed to cause me the challenges.
I also desired to encapsulate some state and operations so they are not shared, to avoid wierd global state side-effects impacting Scenarios.
My general desire was:
- A JUnit runnable which ran my Features based on a Annotation (e.g. @AcceptanceTest).
- Many features, multiple Feature files (one per Feature), multiple Scenarios within the Feature.
- A dedicated Step file for a Feature, which also contains state for the Features Scenarios.
- A shared Step file and class for the bits I wanted to re-use across Features.
- Independence of tests, state not carried over between scenarios.
- Not too complex/abstract to understand (not sure I got there). Clear hierarchy, not spaghetti.
Some of the pains I seemed to encounter:
- You can't see variables defined in one step file in another step file
- You can't see variables in global scope (or world scope) within any classes within step files
- Can't have steps with the same "phrase" in multiple feature files, they clash at runtime
- Even using glue to try and separate steps with separate JUnit runnable tests, it seems Cucumber will still by convention find your other steps and they clash
- Mix-ins seem useful, but seem to get themselves in a pickle when running in cucumber, things getting defined in scope you wouldn't expect and lazy re-initialising when you don't expect
- World can only be defined once, you can't redefine it or add to it easily
- I even got in a weird state where the one Step file was being compiled (I think) twice, saying Steps were duplicates of themselves... that was fun.
Where I’m at so far …
I used a combination of World() and Feature specific “State” classes in Step files.
I defined the World once, but I use @Before hooks to refresh the World properties in place.
Note, I also pass the world reference in (delegate) when I create the step state, so I can use properties from the world internally or delegate to it. This was kind of the discovery for me that led to the pattern I have now for better or worse.
Here’s an example shared steps file:
And here is an example feature steps:
And a feature:
Someone also mentioned to me the use of the Groovy @Field annotation on your global fields, so you could see them in other step files.
Probably something to use at your discretion w.r.t global state.
I need to play with it some more, could be a much simpler solution than what I have above for sharing!
In shared script:
@Field SomeObject myObject
In other Groovy scripts: