Cucumber rounded the 1,000,000 download mark a couple of days ago, and is clearly a very popular tool. It owes a lot of its popularity to Cucumber-Rails - a Ruby gem that sets up Cucumber in a Rails project. One of the reasons Cucumber-Rails has become popular is that it is relatively easy to get started with.
Most people who learned to ride a bike as a child had training wheels. When a child learns to ride on his or her own, the training wheels are removed.
Until last week, Cucumber-Rails also came with pre-mounted training wheels, in the form of a generated
web_steps.rb file with 30 or so reusable step definitions.
This gave a lot of people a flying start with Cucumber and Rails. Many people never removed the training wheels. Instead they soldered them to the frame by piling up with long, verbose and brittle scenarios that depend on them.
Half a year ago we added a warning to the generated
web_steps.rb, because relying on them has so many negative effects.
Some people followed this advice, but there are still a lot of people who fall into the trap. In Cucumber-Rails 1.1.0 they were removed for good,
and in 1.1.1 we also removed its tricycle cousin - the
cucumber:feature generator. The training wheels came off.
The reactions were exactly as I had expected. -A combination of applause and gnashing of teeth. Most of the people who disagree say something along the lines of:
This is going to make it harder for people to get started with Cucumber.
I say: this is going to make it harder for people to use Cucumber badly. Now they have to:
- Learn the Capybara API instead of the
- Think harder about what goes into a scenario.
If this hurts the adoption of Cucumber-Rails I’m perfectly fine with it. You already have plenty of rope to hang yourself with, and I’m not going to tie the noose for you.
Let me explain why
web_steps.rb is a terrible, terrible idea.
Cucumber was designed around the core principles of BDD, and one of these principles is to improve stakeholder collaboration through a ubiquitous language. This essentially means that both code and executable specifications (Cucumber scenarios) should be written in the language of the domain. The “domain” is defined by the value the stakeholders and users hope to achieve with the software. This can be booking a ticket or sharing pictures with friends or an infinite number of activities. Clicking links and buttons or filling in text fields has nothing to do with the domain.
Cucumber scenarios that consist of 10 or so steps that click links, fill in fields, push buttons and look for text are going to bore your stakeholders to death.
Their eyes will glaze over, and they won’t even spot mistakes. Even programmers will have a hard time spotting conceptual errors at this abstraction level. Using Cucumber exclusively to emulate a keyboard and a mouse is possible, but it’s not what it was designed for.
If all you need is a testing tool for driving a mouse and a keyboard, don’t use Cucumber. There are other tools that are designed to do this with far less abstraction and typing overhead than Cucumber. Capybara DSL and Steak are much better if you want a programmer/tester-only tool.
Allowing technical details to bleed into scenarios does more harm than losing stakeholder involvement through boredom. It makes everything harder to change as well. Consider this scenario for logging in:
Scenario: Successful login Given a user "Aslak" with password "xyz" And I am on the login page And I fill in "User name" with "Aslak" And I fill in "Password" with "xyz" When I press "Log in" Then I should see "Welcome, Aslak"
If users have to log in there is usually a lot of functionality that is only accessible to users who have logged in. This means logging in has to happen before most scenarios:
Background: A logged in user Given a user "Aslak" with password "xyz" And I am on the login page And I fill in "User name" with "Aslak" And I fill in "Password" with "xyz" When I press "Log in" Then I should see "Welcome, Aslak"
You’ll have to repeat this in all of your scenarios that describe functionality for logged in users. When someone decides to allow users to log in with an email address instead of a user name we have to go over all of our scenarios and change them.
It’s not a question of if the user interface has to change, but when. Usually a UI change has far wider consequences than this example.
Where is my workflow?
Cucumber was designed to help developers with TDD at a higher level. A common challenge with conventional TDD is that developers don’t know where to start. It’s hard to figure out what tests to write.
The idea with Cucumber (and BDD in general) is that stakeholders assist in writing scenarios - or executable specifications. This solves the where do we start problem with TDD. The scenarios express what a user should be able to do, and not how. When a scenario is defined, programmers implement the required functionality.
This kind of workflow is much harder to follow when scenarios are written in a low-level, imperative style. Very few stakeholders or business analysts are going to agree to defining functionality in terms of mouse clicks and key presses. They think and talk at a higher abstraction level, and scenarios should capture that.
I don’t care about this tree hugging stuff, give me my web_steps.rb
When I started the Cucumber project in 2008 I had three major goals:
- Help stakeholders express what they want via executable specifications
- Help programmers write better software by making TDD/BDD easy
- Reduce misinterpretations through a ubiquitous language
Scenarios based on
web_steps.rb undermine those goals, and that is why I decided to remove them.
Maybe it’s a pride thing. -Like the shopkeeper who refuses to sell merchandise he believes is of bad quality, even if doing so would mean more customers.
Unfortunately, there are books, screencasts and tutorials out there that teach you Cucumber using
web_steps.rb disappeared for good this would harm the people
who are selling this training material. People who have bought it won’t get what they paid for. So I present to you: cucumber-rails-training-wheels.
This is a stillborn project. I will not accept bug reports or pull requests. The only reason it exists is to make old tutorials and books work.
Fine. I still think it’s easier to write scenarios based on web_steps.rb
Which one of these lines are easier to write?
When I press "Log in"
I don’t think it’s harder to read the Capybara API than the regular expressions in
web_steps.rb. If it is, someone needs to improve the Capybara API docs.
Can you give me an example of the new way?
Let’s take the login scenario above and make it a little more readable:
Scenario: User is greeted upon login Given the user "Aslak" has an account When he logs in Then he should see "Welcome, Aslak"
The step definition for the first step would create a new
User record and store a reference in a
@user variable. This would use ActiveRecord directly. The interesting part is
logging in. Cucumber prints out snippets of code for undefined steps:
When /^he logs in$/ do # express the regexp above with the code you wish you had end
Assuming we know what the log in screen is supposed to look like, we can implement this step definition:
When /^he logs in$/ do visit('/login') fill_in('User name', :with => @user.name) fill_in('Password', :with => @user.password) click_button('Log in') end
That wasn’t so hard. Now let’s improve on that
Background we had earlier so we can log in before other scenarios:
Background: The user is logged in Given a logged in user Scenario: Upload a picture # Some steps here
When we’re describing other parts of the system that require login, the login details like user name, password and how to log in are only distracting. That is why we make it a one-liner. Let’s look at how we would define that:
Given /^a logged in user$/ do @user = User.create!(:name => 'Aslak', :password => 'xyz') visit('/login') fill_in('User name', :with => @user.name) fill_in('Password', :with => @user.password) click_button('Log in') end
Can you spot the duplication with the
When /^he logs in$/ step definition? Let’s improve this:
module LoginSteps def login(name, password) visit('/login') fill_in('User name', :with => name) fill_in('Password', :with => password) click_button('Log in') end end World(LoginSteps)
Now your two step definitions can be simplified:
When /^he logs in$/ do login(@user.name, @user.password) end Given /^a logged in user$/ do @user = User.create!(:name => 'Aslak', :password => 'xyz') login(@user.name, @user.password) end
Not only have you made your Scenario and Background easier to read, you also isolated the login details in one single place. Changing the login process to use an email instead of a name will be easy. No scenarios have to change, and you only have to change a couple of places in your step definitions.
web_steps.rb training wheels makes Cucumber harder to use badly. You have to learn the Capybara API instead of regular expressions, and you will take more advantage of the snippets Cucumber prints for undefined steps. This also means you have to think in terms of the domain and not the user interface when you write scenarios. This makes them a lot easier to maintain. Short, declarative scenarios serve as easily readable documentation, and non-technical stakeholders and business analysts are more likely to get intimate with them.
There are also a couple of things you no longer have to do:
- Wade through
web_steps.rband try to make your steps fit their mold. (You’re in charge and can say what you want)
- Update a cryptic
- Be afraid to change generated code you don’t really understand
Here are some great resources for more information:
- Refuctoring your Cukes by Matt Wynne
- Imperative vs Declarative Scenarios in User Stories by Ben Mabey
- Whose domain is it anyway? by Dan North
- You’re Cuking it Wrong by Jonas Nicklas
- Why Bother With Cucumber Testing? by Jack Kinsella
- 171gifer likes this
- tyushokigyotushin likes this
- e-assistant likes this
- nychkablog likes this
- benmorganio likes this
- sbarrax likes this
- fireiceriver reblogged this from aslakhellesoy
- camere-de-supraveghere likes this
- weight-loss-meals-delivered likes this
- nostriluu likes this
- cougars--paris likes this
- turkcell--fatura-ode likes this
- gaming--computer likes this
- vodafone--fatura-odeme likes this
- avea--fatura-ode likes this
- aslakhellesoy posted this