Transforming My Cukes

This past week on the cucumber list Aslak asked if people knew about and where using Transforms. Based on the response I would have to say that not many know about it. I have to put myself in this category. I decided to dig in and here is what I’ve learned.

A transform is a filter that can be applied to step definition arguments to refactor out common code. The simplest example is one in which you want to convert all number arguments to numbers instead of strings. Actually this can be used for far more advanced transformations than just numeric transformations. Let’s look at a few examples and rewrite some step definitions from previous postings at the same time.

Looking back

In a previous article we wrote cukes to verify the contents of a shopping cart. We had Scenarios that had steps like this:

And I should see "Pragmatic Project Automation" in the description for line "1"
And I should see "29.95" in the each for line "1"
And I should see "29.95" in the total for line "1"

and the corresponding step definitions:

Then /^I should see "([^\"]*)" in the description for line "([^\"]*)"$/ do |desc, line|
  @shopping_cart.description_for_line(line.to_i).should == desc
end

Then /^I should see "([^\"]*)" in the each for line "([^\"]*)"$/ do |each, line|
  @shopping_cart.each_for_line(line.to_i).should == "$#{each}"
end

Then /^I should see "([^\"]*)" in the total for line "([^\"]*)"$/ do |total, line|
  @shopping_cart.total_for_line(line.to_i).should == "$#{total}"
end

The first thing I would like to point out is that I am calling to_i on each line value passed into the step. The second thing to notice is that I am transforming each book price to a dollar amount like "$#{total}". These are both candidates for using Transforms.

Two transforms to go

Let’s start by eliminating the need to convert the string line number to a number. The common pattern here is that we are stating line "1" in each step. Let’s create a Transform for this.

Transform /^line (\d+)$/ do |line_string|
  line_string.to_i
end

This transformation works by looking through the Scenarios and finding portions of steps that match "line N" where N is a number. When it finds a match it calls our Transform and places the number string value into the line_string parameter. The code in the step simply calls to_i on the string to convert it to a number. Notice that we did not include the double quotes in the pattern to match. With this simple Transformation we can eliminate all of the duplicate to_i calls in our steps. We can also eliminate the need for the double quotes around the number.

Then /^I should see "([^\"]*)" in the description for (line \d+)$/ do |desc, line|
  @shopping_cart.description_for_line(line).should == desc
end

Notice how our pattern in the step definition is (line \d+). We are creating the pattern that will match our Transform. We also need to modify the Scenario to remove the double quotes.

And I should see "Pragmatic Project Automation" in the description for line 1

Very good!

Now let’s move on to the dollar transformation. We need to create a transformation for any number of digits, a period, and exactly two digits. Here is what that looks like.

Transform /^(\d+\.\d{2})$/ do |dollar_amount|
  "$#{dollar_amount}"
end

And with this transformation we can now remove all of the string formatting in our step definitions.

Then /^I should see "([^\"]*)" in the each for line "([^\"]*)"$/ do |each, line|
  @shopping_cart.each_for_line(line.to_i).should == each
end

Then /^I should see "([^\"]*)" in the total for line "([^\"]*)"$/ do |total, line|
  @shopping_cart.total_for_line(line.to_i).should == total
end

That’s all there is to it. By creating these simple transformations I was able to eliminate duplication I had in several step definitions.

One thought on “Transforming My Cukes

  1. Pingback: Transforming My Cukes

Leave a Reply

Your email address will not be published. Required fields are marked *