BDD on Rails

By Michał Krężelewski, 14 Sep 2015

Today’s programmers use more and more agile practices in their everyday work. Even projects following standard software development life cycle can benefit from adapting them. Automatic testing and TDD brought more confidence to our work, facilitated implementing modifications to existing features and often lead us to better code design. But now it’s not enough. We have to push the benefits from tests to the limits and BDD allows that.

BDD builds on top of TDD and adds to its practices a lot of value. It brings ubiquitous language to the project, allows for better communication between client and developers. It offers a lot for project managers and leaders, but also makes life of a developer a lot easier. Following BDD principles gives us clear requirements, tests are easier to understand and they can serve as documentation. BDD shifts focus of testing subjects and gives us confidence that we test what we should be testing – behaviour.

Cucumber

If you use TDD, starting with BDD will be easy – it’s basically a set of best practices for it. BDD has a set of simple rules, which tell how to write specs and what to test. Specifications are divided into three parts: Given (setting up test conditions), When (invoking action on subject) and Then (assertions). Tests should have descriptive names and existing test frameworks allow for using methods and assertions that are similar to natural language – all of which combined gives us tests that are readable by both technical and non-technical users. Good naming conventions prove useful during regression tests.

BDD comes also with set of guidelines for test subjects. In contrast to TDD it shifts focus from testing implementation to testing behaviour – and using that leads to better design and gives more flexibility when change has to be introduced. Specs should be like a written and executable client requirements – the high level ones should act as acceptance tests. The goal is to write tests in a way that needs changing them only when requirements change. Using BDD gives us confidence that we are testing what really needs to be covered and it is a more pragmatic approach than TDD.

If you want to see BDD in action, we recommend Ruby. It’s a powerful and fun to use language and it has an excellent BDD toolset.

Cucumber

Cucumber is the most popular framework for BDD in Ruby. It introduces special language, called Gherkin, in which you will write your tests. In contrast to RSpec, features described in Gherkin are plain text, not code, and as such can - and should - be understood by anyone, most notably the client.

Cucumber feature looks like this (example taken from Cucumber wiki):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  Feature: Some terse yet descriptive text of what is desired
    Textual description of the business value of this feature
    Business rules that govern the scope of the feature
    Any additional information that will make the feature easier to understand

  Scenario: Some determinable business situation
    Given some precondition
    And some other precondition
    When some action by the actor
    And some other action
    And yet another action
    Then some testable outcome is achieved
    And something else we can check happens too

  Scenario: A different situation
    ...

Features will be described later in detail.

Using Cucumber in Ruby on Rails

Installation

First step to use Cucumber in your project is installing it. Just add these two gems to your Gemfile:

1
2
3
4
group :test do
  gem 'cucumber-rails', :require => false
  gem 'database_cleaner'
end

Bundle them by running bundle install, and generate Cucumber scripts and directories with the following command:

rails generate cucumber:install

This will create config/cucumber.yml, script/cucumber and features/ directory, which will contain .feature files, as well as step definitions and supporting files. To run your features, use a new rake command:

rake cucumber

Features

Let’s take closer look at features. A feature is something your application has or does - for example periodically sends newsletter or allows user to share their photos publicly. One feature consists of multiple scenarios, which describes how this feature works in different contexts.

To demonstrate basic usage of Cucumber in Rails, we’ll write a feature from scratch. This example feature will be the first one we’ll write in the application, which will be shown in the incoming second part of this article. This application will allow User to create Items and Shops (Shops sell Items) and then create Shopping Lists.

In the simplest version, after compiling Shopping List, the application will show in which Shops the Items that the User wants are the cheapest.

First, create a new file named create_item.feature in the features/ directory. At the top of a .feature file there is a Feature keyword, followed by a short description of it. For example:

1
Feature: Creating Items

In the following lines you can write a business goal which will implement this feature. Since Cucumber ignores text written before the first Scenario, it’s not necessary to write anything, but it’s definitely a good idea.

1
2
3
   In order to easily create Shopping Lists with Items sold in nearby Shops
   As a User
   I want to add Items to the system

In the snippet, you can see a quite popular pattern above with which you can describe business goals. It consists of three parts: why is the feature necessary, who wants it, and what is it. Of course, you don’t have to follow this format, but be sure to include answers to these three questions in your description.

Scenarios

Next, we write some scenarios. Each scenario begins with the keyword “Scenario” and contains multiple steps.

1
2
3
4
5
    Scenario: creating unique item
      Given there is a "Milk" Item
      When I go to the main page
      And I create "Bread" Item
      Then I see "Bread" on the Item list

So, if somebody created Item named Milk, then creating Bread is possible and results in the appearance of Bread on the Item list. We shouldn’t test for creating record in database, since this fact has no real value for the customer.

Scenario’s steps use three main keywords:

  • Given, for supplying context to the scenario
  • When, for describing actions
  • Then, for describing results

It’s important to note that Cucumber ignores the keyword step it starts with and matches only the later part. Thus, you can start your steps with “And” wherever it feels natural. The only rule for choosing the right keyword is that the Scenario must be easily understood.

Step definitions

Running Cucumber now produces this output:

1
2
3
4
5
6
7
8
9
10
[...]
Feature: Creating Items
  In order to easily create Shopping Lists with Items sold in nearby Shops
  As a User
  I want to add Items to the system

  Scenario: creating unique item   # features/create_item.feature:6
    Given there is a "Milk" Item   # features/create_item.feature:7
      Undefined step: "there is a "Milk" Item" (Cucumber::Undefined)
[...]

This is because Cucumber doesn’t know what we mean when we say “there is a Milk Item”. To add some meaning to your steps you need to define them in step_definitions directory.

A recommended way to organize your step definitions is to divide them by the domain. For example, most of the steps we’ve created, belong to Item domain. Thus, we should create a file named step_definitions/item_steps.rb and place the following code there:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Given "there is a \"$name\" Item" do |name|
  Fabricate :item, name: name
end

When "I create \"$name\" Item" do |name|
  within "#new_item" do
    fill_in "Name", with: name
    click_on "Create"
  end
end

Then "I see \"$name\" on the Item list" do |name|
  within ".items" do
    expect(page).to have_content name
  end
end

The steps definitions will usually be based on Capybara. If you’re not familiar with Capybara, be sure to check out links at the end of this article.

There is one more step that needs definition and it doesn’t really fit within Item steps. You could put it in main_page_steps.rb:

1
2
3
When "I go to the main page" do
  visit root_path
end

Conclusion

This is a very simple story for a very simple feature. Its purpose was to demonstrate core concepts of BDD as they are used in Cucumber. Writing good stories requires a bit more though. As a developer, you will need to change your mindset completely - forget about implementation and focus on business goals instead. Cucumber makes this transition easier with its clever use of plain text stories.

Sources, inspirations and interesting reads:

About the author

Michał Krężelewski

comments powered by Disqus