There were several talks touching on some of the best practices in writing Cucumber features and scenarios in the CukeUp 2013 conference. The message that was repeated in several of the talks was 'push how down'.

I want to first show two different styles of writing scenarios and then continue with explaining what push how down means.

Imperative vs. Declarative

Another discussion point is whether to have imperative or declarative scenarios. Imperative scenarios tend to have more steps and more details in them and this question is very closely related to the task of removing details from tests steps. Adding the details to a lower layer of implementation makes it is easier to write declarative scenarios, i.e.

Given the user navigates to the home page
And enters "username" in the "username" field
And enters "password" in the "password" field
When the user clicks on the login button
Then the user is logged in

could be written as

Given the user is logged in
And is on the home page

Using declarative style helps both the person writing and maintaining the tests in the long run as well as the product owner to see the forest from the trees, i.e. the acceptance criteria of the feature instead of a huge number of specific actions the user needs to take to accomplish the task.

What does pushing how down mean?

The details in the first example above have not been added to the steps just for fun but are actually necessary for running the tests and they need to be defined somewhere. This is where pushing the details down in the stack comes in.

A typical structure for the code is the following:

Scenario -> Step definitions -> Page model

Pushing how down is about removing test details from the test scenarios in feature files and moving them to a lower test implementation level. The idea is that having too many details in test scenarios makes tests harder to read and will lead to the business requirement being lost in all the details.

Instead of having a lot of steps and details in the scenario in a feature file they can be moved to the step definitions file and even further down to the page model. If we take the example from above

Given the user is logged in
And is on the home page

and consider where did the details go, the following piece of code could be the implementation of the test step in the step definitions file. The page model implementation is made up for the purposes of this example:

# step_definitions.rb
Given(/^the user is logged in$)/ do
    page.visit
    page.username.set "username"
    page.password.set "password"
    page.login_button.click
end

The details can be further pushed down to the page model by doing the following:

# page_model.rb
class WebPage
  def login
    visit
    username.set "username"
    password.set "password"
    login_button.click
  end
end

# step_definitions.rb
Given(/^the user is logged in$)/ do
    page.login
end

Why move code to the page model?

The problem with Cucumber step definition files is that they are very flat in structure and there is not much that can be done in terms of reusing code except for reusing exactly the same steps. Steps can be called from other steps but that is usually not encouraged and is more cumbersome than doing the code reuse in a lower level. It is also fairly difficult to find steps that can easily be reused between pages but it always depends on the site.

When moving code to the page model, one can find patterns and remove duplicated code much more easily.

Depending on how the page model is implemented, there is also one further possibility of removing code duplication, which is using inheritance between the page models. One could have a generic page for the site implementing elements and methods that are used all over the site and then have specific pages inherit the functionality from that page.

Other points to consider

  1. Testing through the browser is slow. Don't repeat tests for functionality that has already been tested.
  2. Don't try to test everything through browser based automated tests. Unit tests are required to test the lower level components.
  3. Test one thing at a time. Having one When, i.e. action, helps to isolate the problem and makes it easy for the reader to understand what is being tested.
  4. Use a Background to remove duplicated steps
  5. Givens are steps to get the system into the desired state before taking the action to be tested.
  6. Givens can be adjusted according to the situation. Combining single steps into a suitable setup step for a scenario can improve the readability dramatically. If the user needs to login, navigate to a page and fill in a form, instead of
    Given the user logs in 
    And the user navigates to the page
    And the user fills in the form
    When the user subscribes to the newsletter

you can write

    Given a logged in user fills in a form on the page
    When the user subscribes to the newsletter

This way the test gets to the point quickly and it is clear what is actually being tested in this scenario.

Summary

Using more declarative style of writing test scenarios is in general the preferred way. The flip side of this is that when testing a feature like login, the scenarios will have to be detailed if Cucumber is chosen as the test tool. If the functionality under test is actually logging in to the site, the individual actions to login should be part of the test scenario. This is quite similar to test cases for specific functionality vs. an end-to-end test plan in manual testing.

Also, once a piece of a functionality is tested, the same detailed steps don't need to be repeated but replaced with a more declarative approach.

When doing BDD, the tests concentrate in testing the acceptance criteria due to that being the actual target of the work and also from a practical reason of wanting to be able to do test runs quickly to get the feedback an agile team requires. This usually requires some separation between tests specifically related to the acceptance criteria and other tests. On the other hand, after having implemented the page models and other supporting test code, there is clearly an advantage in using the same code base for all tests.

If that is the case, some of the tests will most likely be of the imperative form. If that is considered to be problematic from the business requirement and readability point of view, the tests can be tagged and separated into more business and QA oriented categories.

The following questions might help to clarify the situation:
1. Are you doing only BDD with Cucumber or also running other automated tests?
2. If only BDD, what tool is used for other automated tests and why are you using a different tool?
3. Is there a separate team doing automated QA from the BDD team?

Hopefully this will be useful when considering how to write the tests for your team.