Thursday, May 25, 2017

Excelling at CucumberJVM GLOBAL Step Definitions

Cucumber is going global baby!  They've a vision that any and all definitions are available at the beck and call of any feature file.  Other BDD implementations allow static linking of feature file to a specific set of code of definitions.  Let's give the idea of global definitions a try with CucumberJVM (Java) and see what we can learn, cover tools and tricks to work with them, and look at how to design the definition code to be maintainable.  The code is on git hub along with tag points for "before" and "after" refactoring.

Feature files, Steps, Definitions, and Test Runners, oh my!

Feature files are text files containing the BDD (well really Gherkin) Steps such as Give, When, Then.  Definitions are built in programming languages and define what those steps mean.  Test Runners (such as JUnit, or cucumber command line) launch a program that looks for feature files, parses the feature file, and execute each Step by executing a Definition that matches the Step.  BDD Test frameworks usually allow some configuration of how to match a Step with a Definition.  Cucumber's vision is that all Definitions should be global and that the feature file should contain enough context to do this correctly.

Feature file for planning

This is the feature file used in planning.  It reads pretty well and gives a team a starting point for conversations on a point of sales feature for a pet store.
Buying a dog at a pet store such as Petco give you deals such as this.
(click pic to enlarge)
After planning with a number of such documented features, Once the Sprint started, during development of test automation, I realized I needed more context.  If the feature file alone was going to "drive" definition discovery, having descriptive columns wasn't going to give me enough differentiation across all steps in a global context.  example:
When purchasing a "selected accessory"
would generate a match for all other definitions with the words "When purchasing a," totally missing the important piece "selected accessory."
The Natural editor reporting multiple Definitions matching this Step.

Adjusting the feature file by pulling the descriptive columns "out" will allow us to work with global definitions.

How to know a Step has enough "closure?"

First off, we'll never be perfect as reality always brings new adventure. But you'll get closer faster by: reading each step alone, ignoring the context of the scenario title, feature file name, and feature file location.  This is how I realized that "When purchasing a" had too little context as the nice context of the column name wasn't going to help me.

So global definitions will make your feature files a little more wordy.  And you will be forced to make decisions in the future when you discover collisions.  Feature file editors like Natural will complain to you when you add one that has ambiguous definitions.

Test Automation Design

First off, let's get a feedback loop working.  (Code is on GitHub.)  Put the feature file into source control, add a test runner (or if you got the cucumber plugin working in eclipse, that will work too), and execute your test.  Observe that the feature file is executed but the scenarios are skipped as there are no definitions.  Also the console will give you stub code for the methods.

Organize feature files in a sensible hierarchy

JUnit test runner

JUnit test case which hands off to Cucumber
Natural gives feedback that definitions are missing
(If you add definitions, sometimes the feature file needs to
be reopened to force Natural to repairs and check.)
Global definitions change how automation is designed and built.  When using BDD tools that allow the developer to control linking Steps to Definitions, you'd typically see a one to one mapping of feature file to the java file containing the class of definitions:
com/features/purchasing/BuyDog.feature
com/features/purchasing/BuyDogStepDef.java
com/feature/pageobjects/....   
With Cucumber, you're encouraged to build classes in this manner:
com/features/purchasting/BuyDog.feature
com/features/definitions/GivenBuysDog.java
com/features/definitions/WhenBoughtSelectedAccessory.java
com/features/definitions/ThenDiscount.java
com/feature/pageobjects/....  
Although this explosion of smaller objects isn't necessary a bad thing, it leaves us with a problem:  "When" at line 8 needs to communicate with the "Then" at line 9, so these smaller objects need a way to communicate with each other.  A Singleton pattern could do this but puts more burden on the programmer, as now lifecycle management needs to be done to maintain isolation between tests (so that running one scenario doesn't cause a side affect with another scenario due to mismanaged state in a Singleton).  A better alternative is to work with Cucumber's lifecycle for doing this via dependency injection.

Working inside a World

The World lifecycle pattern is simple: the state to be shared between Definitions is stored in the World, and the world is created at the start of executing a scenario and then destroyed upon completion of the scenario.  Upon execution of a new scenario, a new World is created again, and so on.  Although the World pattern is heavily emphasized in Cucumber.JS, it's not so explicit for CucumberJVM.  Cucumber manages the World lifecycle automatically if you use Dependency Injection.  PicoContainer (built by the authors of Cucumber) is a simple and lite weight framework that gets the job done via constructor injection.

Here's how

Add picocontainer to your build dependencies (Because I found PicoContainer.org hard to work with, I used Maven.org to search for the latest versions of "cucumber-picocontainer" and "picocontainer."):
Add two jars to activate World lifecycle and Dependency Injection
Take a look at your three definitions and create a new class for passing information.
Three Step Definitions
Since in this case it's about a shopping cart of items, lets go with that.
For now put the object in the same package as its steps in the top level package for definitions.  Later we'll reorganize but for now keep writing code because it will be easier to re-organize after more of the design has emerged.  Since PicoContainer uses constructor injection, add Constructors for the data injection.
Constructor Injection
(Click pic for larger resolution)
This is all the "structure" code needed for PicoContainer and Cucumber.  When Cucumber executes a feature file with these steps, and it matches to these definitions, it will use PicoContainer to find and inject the dependencies when it constructs these classes, and these dependencies will be inserted in the World during Scenario execution.

Here are the Given, When, Then definitions using the dependency:


 Execute the test runner and you'll see this is enough for the first scenario outline.


To illustrate the World is being destroyed, a short experiment such as injecting a counter to count how many times the Given is called will make this clear.

Counter is always one because it's replaced each time the Scenario is run
(Click to see full size pic.)
Although Counter is always incremented in the definition for Given, and checked in the definition for the Then, it is always set to 1 because each row of a scenario outline gets it's own World.

Ramifications of Global Definitions on Design

A good design does at least these two things well (in this order) that allow a program to respond to change:
1) communicates intent in an understandable way,  and
2) is maintainable.
Another 50 pages could be written about other important characteristics--the book Clean Code is a good reference--but let's keep it to the point: we don't program in binary because it's difficult to understand intent and if we wrote in binary anyhow, eventually you'll be hating life when you have to respond to new requirements.

Global definitions mean our feature files could have a relationship with any definition (Java class with a @Given, @When, @Then.  So organize the features files in a way that makes the feature files an index into your product's features.  Feature files will be the index into your definitions (Java code) as well.  To organize the java code so it communicates intent and is maintainable, use the principle of "keeping things that work together next to each other."  Said another way, keep definitions grouped with the things your injecting into them.  This is a big departure from BDD frameworks that don't do global definitions, where usually the feature files and definitions are grouped together in "src/java/com/feature/purchase/buydog."  With global definitions, doing so would actually misinform.  With global definitions, it'd be better to drop everything in one namespace.  But lets try something better than that.

For example, organize feature files thusly (there may not be an advantage to having feature files children of src/java directory, but I did this out of habit):
src/java/com/features/purchase/BuyDog.feature
src/java/com/features/purchase/BuyCat.feature
src/java/com/features/purchase/BuyFish.feature
src/java/com/features/returns/ReturnFishTank.feature
src/java/com/features/returns/ReturnDog.feature

For Java code, I looked at each set of definitions and their collaborators and tried to group them in a sensible way:
Then later, when I added automation for the few selected accessories a few are not


I had to decide if I wanted to collaborate between the When and Then with the shopping cart, I needed to drop them in the same "shopping" namespace as the previous scenario.  The fact that I'm using the same Given definition, then that reinforces that decision.  This all makes sense since they are all about the same thing.  But get used to the idea that just because steps are in the same feature file, their definitions could be anywhere.

Times goes on keep the stair rails polished

Since feature files are a reflection of a product's features, BDD test automation needs to respond to three kinds of changes:
  • new behaviors/features, 
  • adjusting existing behaviors/features, and 
  • adjust how existing behavior/feature operates.

New behaviors versus adjusting existing behaviors

Organize feature files in a sensible hierarchy with good feature file names so it's easily browsable and searchable in order to answer the question, "is this new idea the PO has a new behavior or a change in an existing behavior?"
src/java/com/features/purchase/BuyDog.feature
src/java/com/features/purchase/BuyCat.feature
src/java/com/features/purchase/BuyFish.feature
src/java/com/features/returns/ReturnFishTank.feature
src/java/com/features/returns/ReturnDog.feature 
...
(It'll be hard to organize features without knowing the business you're building behaviors for.  Go find someone to help/interview about that as this knowledge typically isn't in the IT part of the organization.)

The business want's to collect customer contact info so they can send them offers via physical mail or email.  To do that, they make this offer to the customer at time of checkout by offer VIP cards that give an additional 5% discount on purchases to collect your contact info and send you more offers. 
If it's known that there will be ten more VIP card behaviors, better to make a directory just for different VIP features.  But if all we know at the time is that there is just this one feature, then we can just add the behavior into an existing feature file as shown (we can always move the feature files around later).
Adding another scenario outline (highlighted) to BuyDog
The global definitions related to these steps need to be updated to pass information about the VIP card status (the definitions for the Given and Then).  Because the definitions are global, finding the impacted definitions should be driven from the feature file.  If you've a good feature file editor like Natural, you can open those steps so you can implement a good way to inject (via PicoContainer) an object to pass along VIP card status.  If you haven't a good editor, then use your IDE to search for the step, filtering by .java file.  If you use IntelliJ, it has built in support for Gherkin so that you can put the cursor on a feature files step, then with a CTRL/CMB-B, get to the definition defined by your Java code.  If you have nothing but vanilla Eclipse (no Natural plugin), here is how to work with search:




Answering the question, "what feature files use this definition?" is a bit harder in that you need to avoid the regex expressions.  I'm not aware of any tools that help.


Adjusting only implementation
In this case, the behavior has been implemented but, darn it, the implementation just seems lacking or is in need of an update.  Assumedly the PO knows it's an update of an existing implementation by browsing through the feature documentation (maybe it's been turned into a GitBook).  The team brings the story in with a reference to the existing feature file and a simple bullet points on how to change the implementation.  During the sprint, the developers start at the feature file and from there open the definitions, read the Java code, and then make changes to the definition to get the test failing because the definition has been updated to the new implementation.  With the automation complete, the developers build the functionality (assumedly using TDD so they have micro tests which keep test automation in a sleek and pointy pyramid shape.)

Closing Thoughts

With support from a feature file editor that will escort you to the definitions, World lifecycle management enabled via dependency injection, and the fact that Cucumber is maintained by developers with significant control of the direction and vision of the company (Aslak Helles√ły,  Joseph Wilk, Matt Wynne,Gregory Hnatiuk, and Mike Sassak) I'd say give global definitions a try.  It's easy to dismiss trying something like this out of hand.  In fact some have mentioned how they've tried global definitions and failed, (see References section, "Global scoping is Evil").  In that case they were doing BDD incorrectly (not doing *B* DD at all in fact) as shown in the below, complaining about "click the search button."
Implementation details about UI aren't behavioral
Building "un"behavioral tests is a common early adopter's mistake which can happen to even experienced people who haven't stepped out of the box.  Behavioral or Not? and Behavior Driven Development Elements of Style teach how to build feature files at the correct level.  Building tests in the way the author of the above wanted to wasn't maintainable so this effort was in bad shape with or without global definitions.  Global definitions forced them to fail faster which was a good thing as they would give up rather than build a bunch of automation that's expensive to maintain.  This is likely one of the reasons Cucumber removed the ability to not use global definitions.  If you're still unsatisfied, use some strategies to teach Cucumber about boundaries, use a different BDD framework such as JBehave, or change Cucumber (it's open source) yourself to meet your needs.



References

Lance's example code for this article on GitHub
picocontainer-for-singleton-di
how-to-pass-variable-values-between-steps-in-cucumber-java
Cucumber BDD environment installation
Global Definitions are EVIL

Troubleshooting

JUnit green bar stops rendering and tests not working

Click in the Test Selection window and look for a stack trace in the Failure Trace pane.  In cases like this, something has happened before JUnit execution has even started.  You'll need to correct the problem exposed in the Failure Trace.

Arity Problem

Failure Trace shows Arity problem
Fiddling with feature steps that already have definitions may result in the above when there is a mismatch between the number of parameters Cucumber is trying to pass from the step in the feature file into the definition.  Check the definition's parameter list and the feature file to see which needs to be straightened out.

Monday, May 1, 2017

Teaching Cucumber about Boundaries

Developers are trained to avoid using global anything in programs because you're making big commitments to the uncertain future.  Behavior Driven Development (BDD) demands teams and their source of requirements to decide what words mean, and then write test automation code that enforces the meaning.  In this way BDD encourages the definition of words to be used globally in a specific way in requirements.  The problem with Cumber (and all? implementations of Gherkin) is that the only information passed from the feature file to the automation framework is the the usual Gherkin keywords: Given, When, and Then, which isn't the whole picture.  Developers, since they can read the whole feature file, can teach the automation to do the right thing by linking to specific steps definitions designed with the whole situation in mind.  The BDD framework Cucumber, in particular, doesn't make it simple to control linking feature files to specific test automation (known as step definitions) because it's optimized for a flat global namespace between a feature file and the many step definitions, but there are some boundary controls.  Let's look at different levels of boundaries that can be created between your features files with Cucumber-JVM.

The Problem

Take a look at the below two feature files (click for high-res viewing):
Feature File
To implement test automation for these two feature files, a Java programmer (in the case of Cucumber-JVM) must write java code that drives their product to prove it works as the customer expects.  The problem comes up with "Then signoff" because a single Java method cannot figure out how to handle this case.  Although "signoff" is used in both features, it means two different things.  It's a pity that although the feature files have quite a lot of context (Feature title, user story, scenario title, directory location) which actually makes it clear what is meant by "signoff," none of that is available to communicate context when doing test automation in any of the Gherkin BDD frameworks.  The above two features are in two domains different domains: the domain of package delivery and the domain of multi-user systems.  One strategy is to recast the sentence "Then signoff" in one or both of those feature files so this overlap doesn't exist, and this is the first thing to try when faced with this.  In cases where such a collision is using words that are a "fixed part of the domain language" then we may want to *not* have a global namespaces and instead create a boundary (AKA a bounded context in Domain Driven Design terms).

Conceptually global step functions look like this (click pic for high resolution image):
Global Scope Step Definitions in a global 'dictionary'
Where you'll have a lot of Java steps definitions designed around each gherkin statement.  There isn't a direct relationship between a specific feature file and a specific steps definition.  Here the idea is to have many re-usable steps definitions (and an automation library that's used across all the steps definitions).


But perhaps, you want this for fine grain control and let the developer control all aspects of how each step in the feature file is automated (click pic for high resolution image):
Each feature file has it's own namespace for steps definitions
Here, each feature file gets one (or more) java classes to handle the scenarios within the feature file.  The relationship between the steps and the feature file are intimate.  The idea here is to have a very simple steps definition for a specific .feature file, and to push as much code as possible into the automation library so it can be re-used across all the steps definitions.

You can get to this extreme with some easy to do but additional steps.

Global Scope

This one is easiest to get started with as Cucumber is geared to make this easy.  Your test automation project should have package arranged like:

test.pageobjects
test.features
test.features.major_feature_one
test.features.major_feature_one.subfeature
test.features.major_feature_two
...

Create a JUnit execution class at the top of your test automation project like shown.
How everything works is that when launching the JUnit test case, it hands off to Cucumber via the @RunWith, which associates all the subpackage from that point as the namespace to use for matching step definitions.  So by putting the JUnit Test class at test.features makes all of our feature files from test.features and downward, match to any of the step definitions declared from this point on down.  (BTW, there are tools such as Natural that help in editing feature files and finding which step definition is active.)

Here's the big news: Nothing is stopping us from having multiple JUnit Test classes in different peer packages, effectively creating boundaries for how step functions are picked up by feature files.

Package Scope

By putting a Cucumberized JUnit Test case at the top of specific packages, we create separate bounded contexts, a term used by the practice Domain Driven Design, which basically sums up to: put the features and steps for the package delivery in one Java namespace and the features and steps for multiuser in another Java namespace, and then in each package, setup a Cucumberized JUnit Test case.  In this way, boundaries between these two different domains are made.

It looks like this:


Package organization
test.features.packagedeliver contains one Cucumberized JUnit Test case, many steps definitions, and many feature files.  test.features.processcontrol contains one Cucumberized JUnit Test case, many steps definitions, and many feature files.

Thusly its possible to have a "Then signoff" defined in a steps definition in the processcontrol context and a different definition for "Then signoff" by a steps definition in the packagedelivery context.  If another "Then signoff" steps definition is added to say, test.features.processcontrol.increase_system_load, Cucumber will put the brakes on and complain that "Then signoff" is ambiguous as there are multiple definitions in its bounded context.

But what if that isn't enough?  Can we make every feature file have its own context?  You bet.

Feature File Scope

If you need to do this, it's a bit of drudge work but it's not complex: for every feature file you'll need to add a Cucumberized JUnit test case, and due to a lovely quirk of Cucumber, drop that feature file's step definitions in their own package. #annoying

Conceptually that looks like this:
Literally, it looks like this (click image to see what's going on):

Two feature files in the test.features.processcontrol package want different step definitions for "Then signoff"

Now each Cucumberized JUnit test case defines the mapping from a specific feature file and a package to where to load its step definitions.  Because this strategy will end up producing a bunch of different JUnit test cases, you'll want to add a JUnit test suite so you can easily execute all your feature tests with one click.

Conclusions

You've got the tools to do whatever you want with Cucumber.  Treating every feature file as having its own scope is the ultimate of control but do you really need that?  The OCD control freak inside does want that and if so, better to find another framework to support that then Cucumber.  Defining boundaries using packages will keep the DDD people happy and would work in a pinch when you need it.  Cucumber is courageous in pushing people to build global dictionaries.  I've only heard of great drama coming out of such striving though people have successfully done it.  It seems unfair to having our language in feature files be global context (easy to do as we are humans) and then forcing the automation (a computer program) to discover the context when Gherkin doesn't pass along all the context from the feature files.

I'm happy to hear from you if you've a story to share: Twitter:@LancerKind, LancerKind@gmail.com.

References