Programming

10 Useful Cucumber Tips

May 17, 2012
--
User avatar
Adrian Perez
@blackxored

Cucumber is a very vibrant eco-system on it's own. It became the defacto-standard tool for high-level testing and a great addition to the Behavior Driven Development set of tools. I'm sharing with you a list of tips related to it.

The Tips

1. Write Declarative Step Definitions

I thank this has become so common-practice, and I totally advocate the decision of removing web_steps.rb. As you go to the roots of the process itself, it's all meant to be described in a language that's both familiar to your end users and yourself, but also uses dead simple domain terms, as in a token for a conversation.

A good written feature might be written like this.

Scenario: User subscribes to category
Given a User exists
And is logged in
When he subscribes to the "Programming" category
Then he should see the posts listed on that category

Perhaps it suffers from a little global abuse but it seems to me that the readability gains outperform that. In order to get the most of this fairly simple feature, and to get the overall picture, you need to understand that:

  1. We don't care about which User exists, we just want to imply it exists. I don't care about any specific user information in this moment, so don't use it. A generic user factory it's just enough.
  2. We don't care about the how's in anywhere, we use code for that, the features should have the what's and not the how's.
  3. Which user is logged in should be implicit, as is here. Why? Because there's no interaction with other user's here, we just don't need it.
  4. He should see the posts in that category, we've just created it right? We don't care about other categories right now.

2. Use unique actions across you steps codebase

Your actions should be unique. They should be easily distinguishable from any other actions, and they shouldn't be ambiguous. Don't do this, although it might seem pretty trivial.

When /I (order|buy) (.+)/ do |item|
  # ...
end

Although the flexibility might temp you, it'll hurt you later. You're to be expected to have clarity and unambiguity in your feature files.

3. Use and compare only what you need in Tables

Table diffs are a great feature. Just get in mind don't to over-use them. When you're comparing it seems that you can add to readability by cutting the clutter and writing the right step definition.

Background:
  Given the following Products:
    | name  | price  | description |
    | Socks | 5.00   | ...         |
    | Cap   | 3.00   | ...         |

  # ...

Scenario: Buying Products
  # ...
  When I buy a "Cap"
  And I buy a pair of "Socks"
  Then my shopping list should contain:
  | Socks |
  | Cap   |

Adjust your table diffs to use and compare only relevant information. We don't care about the price and description in the shopping list, we only want to verify the products are there.

As an aside, it would be really nice if we can avoid the need to prevent repeating the action and do some chain step defining, but it would be strange or abused, I mean I guess there is a good reason this hasn't been implemented. I'm talking about something like this:

# ... Same as before
When I buy a "Cap"
And a pair of "Socks"
# ...

Chain the "buy a..." step definition. What do you think?

4. DRY with Transforms

This is one of the nicest features added to Cucumber. It allows you to avoid the need of repeating the same data transformation from scenario to step definition and instead defining those in a single place. Of course, it could be abused as everything in life, but it's still a life-saver on it's own right. This simple example it's perhaps the most common example found in my code:

Transform /^\d+/ do |number|
  number.to_i
end

Then, on the step definition (Look mom, no to_i!)

Then /^I should have (\d+) pets/ do |count|
  Pets.count.should == count
end

5. Use Tagged Hooks

Tagged hooks are a great feature that allows you to run custom code before and after a tagged scenario. It builds upon the normal Before and After hooks, but now you can use tags with it. This helps avoiding global abuse and code duplication, along with the added readability.

Suppose you need to perform certain actions if the scenario is for an administrator. You don't want to repeat those actions, and you don't want to call that helper method on every feature/scenario, right?

In the scenario, just a tag away:

@admin
Scenario: Whatever you want

And you define the tagged hook:

Before('@admin') do
  user = Factory.create(:admin)
  login(user)
end

Clean and concise.

6. Use Feature Tags and Subfolders

Cucumber tags are so great that I couldn't help by mentioning them. You should try to use that as much as it makes sense. Subfolders too. Don't try to put everything in a single place when you're only a require features away.

Get comfortable with the @wip tag.

Integrate your team's workflow with tags as well.

7. Nest Steps with Care

Nest steps carefully, in preference don't nest them at all. Nesting comes with coupling and there are good chances that the benefits are not that huge.

8. Name Feature Branches

Choosing branch names and feature file names is entirely up to you. I use, as many, prefixed feature branches. Setting up your branches name to be as closely as possible to the feature you're working on, and the filename when the feature is described it's a very helpful initiative.

Examples:

Branch: feature/commenting, Feature: features/commenting.feature

Branch: feature/profile-notifications, Feature: features/profile/notifications.feature

9. Use guard-cucumber

Use guard-cucumber to run your features for you. Features are slow, I know, but you should do this anyways. It's pretty annoying having your features running that many times, so what about using the Gem and pausing the file modification from time to time? I work this way and I run my entire suite on every "Pomodoro" break.

10. Take screenshots

No, I'm not telling you how to use PrintScreen or Shutter ;)

Besides the known HTML reports, it came to me somewhere that you can actually take screenshot of your scenarios involving javascript. This is a really neat feature for reports (people love visuals), but specially for debugging.

After('@javascript') do |scenario|
  if scenario.failed?
    page.driver.browser.save_screenshot("html/#{scenario.__id__}.png")
    embed("#{scenario.__id__}.png", "image/png", "SCREENSHOT")
  end
end

Conclusion

That's it. Just a quick list of 10 tips you might heard (or not) about.

Did you think about other interesting tips while you were reading?

Cool, add a comment on them.

~ EOF ~

Craftmanship Journey
λ
Software Engineering Blog

Stay in Touch


© 2020 Adrian Perez