BDD Steps with Parameters

Motivation

In Behavior Driven Development, it is possible that individual steps will "reappear" throughout different scenarios or features. Those steps might not be fully identical, but differ only slightly in details, such as a value to fill into a form field. Maintaining two or even more independent step implementations with a lot of duplicate code causes more work in the long run. Over time, changes would have to be done consistently to all copies. Or even worse, some copies are forgotten.

Passing parameters from steps to script functions makes it easy to do basic functional decomposition and refactoring on your tests, eliminating duplicated code.

Let's take the following two scenarios as an example:
[code language="gherkin" highlight="4,10"]Scenario: Error message when using non-existing credentials
Given an opened web browser
When the Download Area login page is loaded
And I login as 'max' with password 'secret'
Then an error message should be shown

Scenario: Squish Coco account lists Squish Coco downloads
Given an opened web browser
When the Download Area login page is loaded
And I login as 'coco1' with password 'coconut'
Then I should see the most recent Squish Coco packages[/code]
The corresponding step implementations could be
[code language="python"]@When("I login as 'max' with password 'secret'")
def step(context):
type(waitForObject(":DLArea_Login_Username"), 'max')
type(waitForObject(":DLArea_Login_Password"), 'secret')
clickButton(waitForObject(":DLArea_Login_Submit"))

@When("I login as 'coco1' with password 'coconut'")
def step(context):
type(waitForObject(":DLArea_Login_Username"), 'coco1')
type(waitForObject(":DLArea_Login_Password"), 'coconut')
clickButton(waitForObject(":DLArea_Login_Submit"))[/code]

Solution

The implementation for the third step of both scenarios ("And I login as...") would benefit from a parameterization. Instead of hard-coding a different user/password combination for each step implementation, we can define a single step implementation that accepts the username and password as function parameters, and thus can be used in both scenarios.
[code language="python"]@When("I login as '|word|' with password '|word|'")
def step(context, username, password):
type(waitForObject(":DLArea_Login_Username"), username)
type(waitForObject(":DLArea_Login_Password"), password)
clickButton(waitForObject(":DLArea_Login_Submit"))[/code]
In the @Step decorator, for the name, we have added a |word| placeholder, which matches the corresponding value in the step from the scenario. The value is provided as a parameter in the step function. Function parameters are filled in using the same order they appear in the step.

In addition to |word|, which matches an alphanumeric string, one can also match a number using |integer| or an arbitrary sequence of characters with |any|.

Read more about the placeholders here: Using Step Patterns with Placeholders

For a more advanced control of matching step declarations, you can also make use of regular expressions instead of the three convenient placeholder types.

Read more about using regular expressions in step declarations here: Using Step Patterns with Regular Expressions

Comments

    The Qt Company acquired froglogic GmbH in order to bring the functionality of their market-leading automated testing suite of tools to our comprehensive quality assurance offering.