Learning Cucumber - With Dynamic types must come documentation

May 21, 2009 📬 Get My Weekly Newsletter

Finally pulled the trigger on Cucumber, which allows one to write human-readable "features" that are essentially acceptance test cases. You can then execute them by adding some glue code in a mish-mash of Ruby DSLs and verify functionality.

It took me quite a while to decide to get going on this because the examples and documentation that are available are extremely hypothetical and very hand-wavy. A lot of the information glosses over the fact that you are still writing code and you still need to know what the keywords are and what data you will be given. Arcane non-human-readable symbols are almost preferable when getting started, because you don't get distracted by English. This is why Applescript drives me insane.

At any rate, I found this page, which was pretty helpful. It shows testing a rails app using, among other things, webrat (another tool severely lacking in documentation but that is, nonetheless, pretty awesome).

I'm writing a basic wiki (for science) and so I thought a good feature would be "home page shows all pages in sorted order", so I wrote it up:

Feature: pages are sorted
 As anyone
 The pages should be listed on the home page in sorted order

 Scenario: Visit Homepage
   As anyone
   When I visit the homepage
   Then The list of pages should be sorted
Now, the webrat/cucumber integration provided by rails means that the "plain English" has to actually conform to a subset of phrasing and terminology or you have to write the steps yourself (the features are everything under "Scenario"). It's not hard to do that, and it's not hard to modify the default webrat steps, but it was a distraction intially.

Next up, you implement the steps and here is where the crazy intersection of Ruby DSLs really made things difficult. The first two steps were pretty easy ("anyone" didn't require any setup, and webrat successfully handled "When I visit the homepage"):

Then /The list of pages should be sorted/ do
  response.should # I have no clue wtf to do here

end
A puts response.class and a puts response.methods gave me no useful information. I eventually deduced that since Cucumber is a successor/add-on/something to RSpec, perhaps should comes from RSpec. This takes a Matcher and webrat provides many. Specifically, have_selector is available that allows selecting HTML elements based on the DOM.
Then /The list of pages should be sorted/ do
  response.should have_selector("ul.pages")
end
Success! (sort of). My feature executing is all green, meaning the home page contains <ul class="pages">. have_selector also takes a block (totally undocumented as to what it is or does in the webrat documentation):
Then /The list of pages should be sorted/ do
  response.should have_selector("ul.pages") do |pages|
    # WTF is pages and what can I do with it?

  end
end
A puts pages.class later and I realize this is a Nokogiri NodeSet. Now, I'm in business, though it would've been nice to be told WTF some of this stuff was and what I can/should do with it. At this point it was pretty trivial to select the page names from my HTML and check if they are sorted:
response.should have_selector('ul.pages') do |pages|
   page_names = []
   pages.should have_selector('li') do |li|
     li.should have_selector('.pagename') do |name|
       name.each do |one_name|
         page_names &lt;&gt; one_name.content
       end
     end
   end
   assert_equal page_names,page_names.sort
   true
 end
(For some reason assert_equal doesn't evaluate to true when it succeeds and the block evaluates to false and then Cucumber/RSpec/Webrat/Ruby Gods claim my page is missing the ul tag). My initial implementation walked the DOM using Nokogiri's API directly, because I didn't realize that should had been mixed in (on?) to the objects I was being given. I'm still not sure if using that is the intention, but it seemed a bit cleaner to me.

So, this took me a couple of hours, mostly because of a combination of dynamic typing and lack of documentation. I'm all for dynamic typing, and I totally realize that these are free tools and all that. I think if the Ruby community (and the dynamic typing community in general) wants to succeed and make a case that dynamic typing, DSLs, meta-programming and all this (admittedly awesome and powerful) stuff enhance productivity, there has to be documentation as to the types of user-facing objects.

Now, given Github's general awesomeness, I'm totally willing to fork a repo, beef up the rdoc and request a pull, however I'm not even sure whose RDoc I could update to make this clear. Just figuring out that the have_selector in response.should have_selector is part of webrat was nontrivial (I had to just guess that should was part of RSpec and that the Webrat::Matchers module was mixed in). This is a problem and it's not clear to me how to solve it.

That being said, I was then able to create three more features using this system in about 10 minutes, so overall, I'm really happy with how things are working. Certainly if this were Java, I'd still be feeding XML to maven or futzing with missing semicolons. So, it's a net win for me.