Join our team!

We're looking for mid-level and senior software developers who love working throughout the stack, and have a track record for designing, building, shipping, and supporting web services and applications. Ideal candidates love Ruby, Rails and Ember, and using BDD and agile processes to ship working software quickly! Experience with Redis, Postgres, RabbitMQ, and ElasticSearch are a huge plus!

Learn more about our mission by visiting IoraHealth.com and by following @IoraHealth.

When you Really, Truly, Need to Parameterize your Cucumber

Before writing this post I did a quick search for “Cucumber worst practices parameterization,” and didn’t come up with much. (Apparently people don’t tag their terrible scenarios with @worst or the like.)

But parameterizing your Cucumber stories is surely one of the worst things you can do. It obviously creates dependencies on some external resource (the value of a variable), and it arguably makes your scenario non-deterministic. A better place for parameterization is probably in your RSpec.

But . . . Recently I wrote a small gem to provide for turning Pingdom alerts off and on before and after Capistrano deployment (pingdom-cap). And as I considered the way a potential user would evaluate the software, it struck me that I had better have an easily readable and exercisable integration test. It would look something like this:

Scenario: Pause check
  When I successfully run `pingdom-cap icu pause`
  Then the output should contain "Pausing Pingdom 'icu'"

This scenario is leveraging the Aruba gem to run commands and test the output. That parameter “icu” is the name of a Pingdom “check name.” When you type “pingdom-cap icu pause” you expect to be told that you’ve paused the check, so that the check won’t run and you won’t get those annoying emails and SMS’s during a deploy.

But I don’t think everyone wants to integrate against a check named “icu,” and it would be too hard and expensive to get everyone to change.

Were I evaluating this gem, the first thing I’d want to do is try it out on my own Pingdom configuration. And I really wouldn’t want to write any code. I’d like to run the scenario against my own parameters. I’d like to define environment variables and have them get picked up in the scenario. Something like this:

Scenario: Pause check
  When I successfully run `pingdom-cap <%= ENV['PINGDOM_CHECK_NAME'] %>  pause`
  Then the output should contain "Pausing Pingdom '<%= ENV['PINGDOM_CHECK_NAME'] %>'"

Nice idea. But Cucumber doesn’t run through ERB, and, as I say above, it’s probably a worst practice. But I want it. What to do? Really, what I want is to write something like:

When I (ahem, pass this dang thing through erb) and successfully run `pingdom-cap <%= ENV['PINGDOM_CHECK_NAME'] %>  pause`

More politely,

Scenario: Pause check
  When (erb) I successfully run `pingdom-cap <%= ENV['PINGDOM_CHECK_NAME'] %>  pause`
  Then (erb) the output should contain "Pausing Pingdom '<%= ENV['PINGDOM_CHECK_NAME'] %>'"

What this implies is selectively passing entire steps through ERB when the step is flagged at the start with “(erb)”. One might think that this should be a Cucumber-style tag, but I don’t think that’s the appropriate level for a transformation of this kind.

Here’s what I came up with:

require 'erb'

When /\A\(erb\) (.*)\z/ do |*matches|
  erbs = matches.map { |match| ERB.new(match).result(binding) }
  step *erbs
end

In other words: When the step begins with “(erb)” capture all of the matches, and run ERB over each match. Pass the splatted result to the step method. That is all.

Parting shots

Another way to do this would have been to constrain significantly the scope of what gets parsed into the scenario. In my code, any Ruby code gets evalulated in the ERB block. That’s just asking for trouble, and I apologize in advance for introducing this technique which will probably worsen Cucumber scenarios everywhere. Perhaps a better strategy would have been to introduce some different syntax that would only transform environment variable names. Something like this:

Scenario: Pause check
  When (env) I successfully run `pingdom-cap <<<PINGDOM_CHECK_NAME>>>  pause`
  Then (env) the output should contain "Pausing Pingdom '<<<PINGDOM_CHECK_NAME>>>'"

That might be a good gem. I’d use it.

Finally: As to whether this will work with substitution in scenario outlines . . . I’ll leave that as a problem for the reader.