A modest proposal towards reusable step definitions for Cucumber
gga
#2011-10-05
Typically, cucumber step definitions look like this:
When /^I select the recipient named "([^"]*)"$/ do |recipientname|
within_jqm_page do
page.find('a', :text => recipientname).click
end
end
Step definitions implemented like this end up becoming a pain to re-use. Essentially you are calling a function, except to do so you have to construct a string, and then execute that. We’ve all seen how that ends.
Given /^all the entered recipient details are valid$/ do
When %{I fill in "name" with "Valid Name"}
And %{I fill in "phone" with "4152234567"}
And %{I fill in "email1" with "valid.email@example.com"}
end
The direct outcome is very hard to maintain step definitions — like the above — and thus very poor quality cucumber tests. Patterns like page objects and personas take work to extract when following this approach. This approach that is analogous to writing VB6 applications in the early 2000s where all logic was just a double-click on a form button away. And as with VB6, that cucumber directly encourages this approach is part of the problem.
I hear a lot of complaints about cucumber these days. These complaints tend to target the English language test scripts: while business readable acceptance tests may be nice, these are useless if the business is never actually reading the tests.
The unspoken heart of this complaint seems to be the English language tests are responsible for the poor maintainability of the average cucumber test suite. I agree. But that shouldn’t be the case.
After trying to build well-factored cucumber suites on a few occasions, I pretty much hate the pattern of just attaching anonymous blocks to regular expressions. Instead, I want step definitions that directly invoke methods. Something like:
Given /^I am on the (\w+) page$/, :go_to_known_page,
:on => lambda { current_user }
class User
def self.current_user
...
end
def go_to_known_page(page_name)
visit Page.page_named(page_name)
end
end
World(User)
The significant part is in the first two lines. Instead of passing a
block to the Given
, a symbol for a method to invoke would be
passed. This method would be invoked with the arguments matched by the
regular expression, after any Transform
s have been applied. The
:on
is optional. This is a block evaluated against the World
. If
:on
is provided, the method would be sent to the result of this
block instead.
Yes, this would force you to define classes and attach methods to those. Yes, this would require you to think about the interrelationships of your objects. Yes, this is quite a lot more limited than an arbitrary block of code. Yes, that is exactly the point.
After a quick glance through the cucumber source this does not look difficult to add. Should I go to the effort of adding it?
Update 2012-08-22: While implementing this proposal I found that my originally suggested syntax didn’t work. I’ve amended this post to reflect what I actually did implement.