Cucumber: building a better World (object)
Tháng Tư 15, 2009
How to write helper libraries for your Cucumber step definitions and how to upgrade your support libraries from Cucumber 0.2 to 0.3 (released today).
In cucumber, each scenario step in a .feature file matches to a
Then step definition. The step definitions are normal Ruby code. First class, bonnified, honky-tonk Ruby code. And what’s the one thing we love to do to Ruby code on a rainy Sunday afternoon? Refactor it. Turn messy code into readable “return in 50 years, on the time capsule, and get back to work quickly” code.
In Cucumber we use a special, until-now unknown, magic technique for refactoring step definitions. They are called “Ruby methods”. Oooh, feel the magic. You take some code in a step definition and you refactor it into a method. And you’re done. For example:
When /I fill in the Account form/ do fill_in("account_name", "Mocra") fill_in("account_abn", "12 345 678 901") click_button("Submit") end
Could be refactored into:
When /I fill in the Account form/ do fill_in_account_form end def fill_in_account_form fill_in("account_name", "Mocra") fill_in("account_abn", "12 345 678 901") click_button("Submit") end
Good work. Or is it? No, we’ve done something a little naughty. We’ve polluted the global object space with our method and turns out it just isn’t necessary. There’s a nicer way and a clean idiom for how/where to write helper methods.
Annoyingly, that idiom broke with the release of Cucumber 0.3. So I’ll introduce both so you can fix any bugs that you spot and know how to fix them.
The solution is to understand the existence of the World object and the clean technique for writing features/support/foobar_helpers.rb libraries of helper methods.
Introducing the World object
To ensure that each cucumber scenario starts with a clean slate, your scenarios are run upon a blank
Object.new object. Or in a Rails project its a new Rails test session
These are called
World objects (see cucumber wiki). You pass in a specific World object you’d like to use, else it defaults to
Object.new For a Rails project, you’re using
Cucumber::Rails::World.new for your world object each time, which is a subclass of
The benefit of a World object starting point for each scenario is that you can add methods to it, that won’t affect the rest of the Ruby world you live in: which will be the Cucumber runner. That is, you cannot accidently blow up Cucumber.
Extending the World object
Step 1, put methods in a module. Step 2, add the module to your World object.
So that we’re all extending Cucumber in the same way, there is a folder for where your helper methods should be stored, and a programming idiomatic way to do it. It has changed slight from Cucumber 0.2 to 0.3 so I’ll show both.
For our helper method
- Create features/support/form_submission_helpers.rb (its automatically loaded)
- Wrap the helper method in a module
module FormSubmissionHelpers ... end
- Tell Cucumber to include the module into each World object for each Scenario
In Cucumber 0.3+ your complete helper file would look like:
module FormSubmissionHelpers def fill_in_account_form fill_in("account_name", "Mocra") fill_in("account_abn", "12 345 678 901") click_button("Submit") end end World(FormSubmissionHelpers)
For Cucumber 0.2 your complete helper file might have looked like:
module FormSubmissionHelpers def fill_in_account_form fill_in("account_name", "Mocra") fill_in("account_abn", "12 345 678 901") click_button("Submit") end end World do |world| world.extend FormSubmissionHelpers end
Where the difference is the last part of the file. This mechanism is deprecated and results in the following error message after upgrading to Cucumber 0.3:
/Library/Ruby/Gems/1.8/gems/cucumber-0.3.0/bin/../lib/cucumber/step_mother.rb:189:in `World': You can only pass a proc to #World once, but it's happening (Cucumber::MultipleWorld) in 2 places: vendor/plugins/cucumber/lib/cucumber/rails/world.rb:72:in `World' vendor/plugins/email-spec/lib/email_spec/cucumber.rb:18:in `World'
Refactor step definitions. Put it in features/support/…_helpers.rb files, inside modules that are assigned to the World object.