Moving From Cucumber to Turnip

I was about to refactor one of my old ruby apps when I realized that I didn’t want to write more cucumber features and scenarios. I do bdd whenever I can (I even wrote a detailed post about it), but in ruby I don’t want to have test cases both under ./features for cucumber and ./spec for rspec any more. I know that it is possible to run both of them at once, but that is not simple: it makes automatic test case execution, getting test coverage, and maintenance complicated. Moreover, it is easy to forget to run one of them before commit, and that usually leads to a failed build. So, I decided that I would ditch cucumber and use only rspec and use it "" do ... end methods as scenarios and write steps such as when_i_enter_something, but then I found turnip, and I like it very much.

Moving from cucumber to turnip was pretty easy. It uses the same feature syntax as cucumber, which means that I don’t have to do anything with the feature files besides moving them under the ./spec folder. I’ve created a acceptance folder under ./spec just to keep the acceptance test cases separated from the unit tests.

~/Code/ruby/dina-coding-dojo/numerals % ls -1 features spec/acceptance
features:
numeral_conversion.feature
user_interface.feature
...

spec/acceptance:
numeral_conversion.feature
user_interface.feature
...

And one of the feature files - spec/acceptance/user_interface.feature:

Feature: Provide a simple form for the user so that she can use the converter

  Background:
    Given I am on the main page

  Scenario: Display a number as a word
    Given I am on the main page
     When I enter 10 into my field
      And I press submit
     Then I see it as a name in the result
      And I see the original number as well

  Scenario: Don't display anything when the user pressed submit without giving a number
    Given I am on the main page
     When I press submit
     Then I do not see any errors just the content of the main page
  # ... (more scenarios)

Running the test cases:

~/Code/ruby/dina-coding-dojo/numerals % bundle exec rspec spec
..........

Finished in 3.74 seconds
10 examples, 0 failures
~/Code/ruby/dina-coding-dojo/numerals %

It didn’t work because turnip needs to “know” that it has to run the features, too. It can be easily achieved by adding -r turnip/rspec to ./.rspec:

~/Code/ruby/dina-coding-dojo/numerals % cat .rspec
--colour -r turnip/rspec
~/Code/ruby/dina-coding-dojo/numerals %

Now, running the test cases again:

~/Code/ruby/dina-coding-dojo/numerals % bundle exec rspec spec
...
  Provide a simple form for the user so that she can use the converter
  Don't display anything when the user gave something else than a number
  I am on the main page -> I enter something else than a number into my field
  -> I press submit -> I do not see any errors just the content of the main page
    # No such step: 'I am on the main page'
    # ./spec/acceptance/user_interface.feature:68

Finished in 4.18 seconds
50 examples, 0 failures, 40 pending
~/Code/ruby/dina-coding-dojo/numerals %

Nice, that seems to work, and it is time to move the steps, too. They will go under the spec/acceptance/steps with the name spec/acceptance/steps/user_interface_steps.rb folder, but will need little change:

# cucumber step:
 #...
Given /^I am on the main page$/ do
  visit "/"
end

When /^I enter (\d+) into my field$/ do |number|
  @number = number
  fill_in("number", :with => number)
end
 #...
 # ---------------------------------------------
 # turnip step:
 #...
require "spec_helper"

module UserInterfaceSteps
  step "I am on the main page" do
    visit "/"
  end

  step "I enter :number into my field" do |number|
    @number = number
    fill_in("number", :with => number)
  end
end
 # ...

The body of the method is the same, but the syntax is a bit different. Turnip doesn’t do regexp argumentation (I never understood why that beneficial anyway), it uses symbols as placeholders. As you can see, it is not necessary to put the placeholder between "" because turnip will find the placeholder without them. Moreover, the steps aren’t global anymore. If you need global ones, just define them in the Turnip::Steps module.

There are three things that I still have to do: tell turnip where it can load the steps from, make them available for scenarios, and make the capybara methods - such as visit - work. Turnip has its own configuration file (./spec/turnip_helper.rb) that it loads automatically. Add the following line to spec/turnip_helper.rb, so that the steps are loaded:

Dir.glob("spec/acceptance/steps/**/*_steps.rb") { |f| load f, true }

This will load the steps, and you can use the following line when you want to use them:

# default
RSpec.configure { |c| c.include UserInterfaceSteps }

In this example I’m loading them right after definition:

module UserInterfaceSteps
  step "I am on the main page" do
    visit "/"
  end

  # ... (more steps)
end

RSpec.configure { |c| c.include UserInterfaceSteps }

Three more lines in the spec_helper.rb so that the capybara methods - such as visit - can be used in rpsec:

# ...
require 'rspec/rails'
require 'rspec/autorun'

require 'capybara/rspec'
require 'capybara/rails'
require 'turnip/capybara' # for scopes
 # ...

And technically we are done: both the acceptance and unit test cases are executed with one command.

~/Code/ruby/dina-coding-dojo/numerals % bundle exec rspec spec
..................................................

Finished in 5.56 seconds
50 examples, 0 failures
~/Code/ruby/dina-coding-dojo/numerals %

Turnip supports scopes, too (they start with @ like @selenium or @database). For example, there are test cases I’d like to run with selenium:

@selenium
  Scenario: Display a number as a word
    Given I am on the main page
     When I enter 10 into my field
      And I press submit
     Then I see it as a name in the result
      And I see the original number as well

This will open my Firefox and run the test case in it, but that will ruin the whole “running everything with one command fast and simple” experience. Fortunately, turnip handles scopes as tags, so it is pretty easy not to run the scoped test cases:

~/Code/ruby/dina-coding-dojo/numerals % bundle exec rspec spec --tag "~selenium"
Run filtered excluding {:selenium=>true}
.................................................

Finished in 4.83 seconds
49 examples, 0 failures
~/Code/ruby/dina-coding-dojo/numerals %

The ~ tells rspec not to run the tests with the specified scope or tag, and the "" tells my zsh not to interpret the ~ symbol.

Turnip knows much more then I mentioned in this post (the code samples are here), such as calling steps from other steps, custom steps, placeholders, and different scope options etc., but I won’t write about them now because its README is very detailed. Turnip is a great tool that makes my development work easier and I hope you’ll give it a chance, too.


comments powered by Disqus