BDD on Rails
By Michał Krężelewski, 14 Sep 2015
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.
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 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.
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
features/ directory, which will contain
.feature files, as well as step definitions and supporting files. To run your features, use a new rake command:
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:
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.
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:
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.
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
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
1 2 3
When "I go to the main page" do visit root_path end
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: