Supercharging Your Step Definitions

Everybody’s got a different way to say the same thing. When we create BDD scenarios, those style prose choices show up in the way we write.

This is great for adding flavor to every day language, but not so great when it means we have to write possibly duplicated code, just to handle a stylistic change.

So if one person would write:

When I click the submit button

And another one would write:

When I have clicked on the submit element

Then this could result in writing another step. But why?? They’re doing the exact same equivalent thing in the same manner.

Why Not Do Both?

A trick is to pepper step definitions with enough synonyms to allow multiple steps to hit the step definition. We can use regular expressions to expand the usefulness of our step definitions, resulting in a lot less time writing new code, just for handling cosmetic changes.

Here’s an example step definition, based on the above:

When(~/^I have clicked on the submit element$/)

Now let’s break it down:

Who is doing the clicking?

  • I
  • a user
  • the user
  • you

We may not care who’s doing the action, so we can wrap it in a non-capturing group, like this:

When(~/^(?:I|a user|the user|you) have clicked on the submit element$/)

Note the “?:” token at the beginning of the group. This is how to specify that the group is the non-capture variety.

A non-capture group just means we want this part of the regular expression to help with matching, but we’re not going to use it for anything else after that. It’s just a filter. There’s an example of a capturing group later on.

Next question: What tense are we using? Should we match on present or past tense? Who cares! Put them all in:

  • have clicked
  • has clicked
  • is clicking
  • just clicked

We again don’t really need to track this for later, so we can use a non-capturing group. And we can be a little more slick this time so we have a shorter step definition:

When(~/^(?:I|a user|the user|you) (?:have|has|is|just) click(?:ed|ing) on the submit element$/)

This covers all possible permutations, even wonky ones like “have clicking”. It just means that we can has the flexibility, and have a somewhat shorter step definition.

Then we look at the prepositional part of the step. We’d either say we click the or on the target element:

When(~/^(?:I|a user|the user|you) (?:have|has|is|just) click(?:ed|ing) (?:on the|the) submit element$/)

Next, we can wrap a non-capture group around the element type itself. For brevity we’ll just do the ones in the examples further up: element or button:

When(~/^(?:I|a user|the user|you) (?:have|has|is|just) click(?:ed|ing) (?:on the|the) submit (?:element|button)$/)

Finally, the one place we might care about enough to capture is the name of the element we’re acting on–submit. All we do for that is leave off the non-capture token, like this:

When(~/^(?:I|a user|the user|you) (?:have|has|is|just) click(?:ed|ing) (?:on the|the) (.*) (?:element|button)$/)

Increasing Readability

This step definition is getting pretty long. There’s another thing we can do that can help with readability, and that is: put the regular expressions in variables, and then embed the variables in the step definition itself.

And, we can… probably… you know what, let’s clean this up a little bit too and make the step readable even with variables stuck in there.

How about we make variables for the following regexes:

def theUser = "(?:I|a user|the user|you)"
def clicks = "(?:have clicked|has clicked|is clicking|just clicked|clicked)"
def onThe = "(?:the|on the)"
def element = "(?:element|button)"

And then splice in these variables in the step definition like this:

When(~/^${theUser} ${clicks} ${onThe} (.*) ${element}$/)

Very niiiiiiiiiice. Not only does this shorten up the step definition, but it’s still clear what the step is doing.

Would this help clean your automation code up somewhat? Decrease repetitive code, shorten maintenance and increase readability? Probably so.

Got any similar tips? (?:Leave|Drop) them (?:in|down in) the comments below!

Readability is one of the three pillars of good test automation–the other two being maintainability and extendability. If you haven’t mastered these pillars, don’t worry, because I know a consultancy that can help. I’ve known the president of the company for… wow, my whole life! He’s good people. 


4 thoughts on “Supercharging Your Step Definitions

  1. Thanks for sharing, very cool tips.

    PS: typo in your (?:last|final) step?
    It currently says “${element)$/”. I am expecting the closing curly bracket, not the round bracket. Please confirm.
    Like so: “${element}$/”


  2. Why not do both?

    The reason not to get used to doing both is that it weakens the structure of the grammar you are building for your product and engineering teams. When you conflate nouns like “element” and “button” you lose the ability to have them mean different and specific things. In HTML all buttons are elements but not all elements are buttons; and moreso, “element” to a non-developer is ambiguous, so the “submit element” could mean, to some, the box on the page where the submit button is located.

    When dealing with tense, allowing any and all tenses similarly weakens the ability to remember how to structure your steps. If you allow all tenses, why not allow passive voice? What happens when “the button was clicked” rather than “the user clicked the button”? How many regexs are going to be used to support these types of possibilities?

    Maintaining a core grammar with consistent rules is extremely valuable in defining the language of the domain that you’re testing, and thus making it as precise as possible. When you start being too flexible with what kind of words are allowed within the context, it weakens everybody’s ability to conform to something that actually means what you want it to mean, and you get something like AppleScript.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s