Sie sind auf Seite 1von 5

Chapter 1.

Cucumber Techniques

Recipe 1

Compare and Transform Tables of Data


Problem
Your tests are in English, but your data is in HTML. What you and your stakeholders call a last name, your app calls customer_name_last. What you call February
24, your app calls 2012-02-24T10:24:57-08:00. You need to translate between the two.

Ingredients
Ast::Table,1 Cucumbers table-crunching workhorse
Rubys built-in BigDecimal for representing currencies2

Solution
In this recipe, well assume were getting data from our app using a GUI
automation library or web scraping framework. The data will be in whatever
format the behind-the-scenes API provides. This format may be grisly, so we
dont want it in our human-readable Cucumber tests.
How do we address this mismatch between our top-level tests and the
underlying API? Well use Cucumber to transform the table in our .feature file
to whatever the API needs. We can change columns, convert data inside cells,
or perform tricky custom transformations.
This recipe comes in several flavors so that you can practice applying all these
techniques.

Renaming Headers
Imagine you have the following test steps:
tables/tables.feature
Scenario: Renaming headers
Given I am logged in as a buyer
When I search for available cars
Then I should see the following cars:
| color | model
|
| rust | Camaro |
| blue | Gremlin |

1.
2.

http://rdoc.info/github/cucumber/cucumber/Cucumber/Ast/Table
http://www.ruby-doc.org/stdlib-1.9.3/libdoc/bigdecimal/rdoc/index.html

www.it-ebooks.info

report erratum discuss

Compare and Transform Tables of Data

Your team has standardized on the U.S. spelling of color, but the API youre
calling to scrape the data from your app happens to use the U.K. spelling.
tables/step_definitions/table_steps.rb
When /^I search for available cars$/ do
@cars = [{'colour' => 'rust', 'model' => 'Camaro'},
{'colour' => 'blue', 'model' => 'Gremlin'}]
end

If you compare these tables directly in Cucumber, youll get a test failure,
because the color column name in your examples doesnt match the colour key
returned by the API.
Cucumbers map_headers!() method lets you transform the table in your examples
into the format expected by your underlying API.
tables/step_definitions/table_steps.rb
Then /^I should see the following cars:$/ do |table|
table.map_headers! 'color' => 'colour'
table.diff! @cars
end

If your team members have written several scenarios and have been alternating
between spellingswell, you really should pick one and standardize. But in
the meantime, you can pass a regular expression or a block to map_headers!()
for more control over the column renaming.
table.map_headers! /colou?r/ => 'colour'
table.map_headers! { |name| name.sub('color', 'colour') }

What if you need to change the values inside the table, not just the headers?

Converting Data Inside Cells


Ast::Table can do more than just rename columns. It can manipulate the data

inside cells too. Imagine you have the following scenario:


tables/tables.feature
Scenario: Converting cells
Given I am logged in as a buyer
When I view warranty options
Then I should see the following options:
| name
| price |
| Platinum | $1000 |
| Gold
| $500 |
| Silver
| $200 |

Cucumber reads every table cell as a string. So, it will see the price of the
platinum plan, for instance, as the string '$1000'.

www.it-ebooks.info

report erratum discuss

Chapter 1. Cucumber Techniques

One of our older projects used the RSpec Story Runner, Cucumbers predecessor. At
the time, the Story Runner didnt support tables or tags. For one particularly repetitive
test, we implemented our own ad hoc version.
# Modes: Regular, Analysis, Time
Scenario: Rounding
When I enter 1.000001
Then the value should be 1

We would preprocess the scenario in Ruby and generate three scenarios that would
put the hardware into Regular, Analysis, or Time mode before running the test.
Thank goodness Cucumber came along!

But this hypothetical used-car API returns the prices as BigDecimal values like
1000.0. It also furnishes some extra information youre not using for this test:
an administrative code for each plan.
tables/step_definitions/table_steps.rb
require 'bigdecimal'
When /^I view warranty options$/ do
_1000 = BigDecimal.new '1000'
_500 = BigDecimal.new '500'
_200 = BigDecimal.new '200'
@warranties = [{'name' => 'Platinum', 'price' => _1000, 'code' => 'P'},
{'name' => 'Gold',
'price' => _500, 'code' => 'G'},
{'name' => 'Silver',
'price' => _200, 'code' => 'S'}]
end

You need to convert the strings from your scenario into numbers to compare
against your API. You can do this with Cucumbers map_column!() method. It
takes a column name and a Ruby block to run on every cell in that column.
tables/step_definitions/table_steps.rb
Then /^I should see the following options:$/ do |table|
table.map_column!(:price) { |cell| BigDecimal.new(cell.sub('$', '')) }
table.diff! @warranties
end

Notice that Cucumber didnt complain that the API had an extra code column
thats not used in the scenario. In the next section, well talk about these
kinds of table structure differences.

www.it-ebooks.info

report erratum discuss

Compare and Transform Tables of Data

Comparing Tables Flexibly


By default, Cucumber ignores surplus columns, that is, columns that are
present in your internal data but not in your scenario. Any other difference
in table structuremissing columns, surplus rows, or missing rowswill
show up as a test failure.
You can change this default by passing an options hash to diff!() containing
3
:missing_col or :surplus_col keys with true or false. (true means be strict.) For
instance, if you want Cucumber to report the extra code column as a failure,
you could use the following call:
table.diff! @warranties, :surplus_col => true

The three table operations youve seen so farrenaming headers, converting


cells, and comparing structurewill get you through most of the situations
where you need to map your Cucumber table to your underlying data. For
those last few edge cases, you have one more trick up your sleeve.

Passing Cucumber Tables into Your Code


If your needs are really complex, you can always extract the data from where
its bottled up in the Ast::Table object and do whatever crunching you need on
plain Ruby objects.
There are several ways to get the raw data out of a table. You can call rows()
or hashes() to get the cells (minus the headers) as an array of arrays or an array
of hashes. Heres what the output looks like with the table from the car scenario from the beginning of this recipe:
basic.rb(main):001:0> table.rows
=> [["rust", "Camaro"], ["blue", "Gremlin"]]
basic.rb(main):002:0> table.hashes
=> [{"color"=>"rust", "model"=>"Camaro"}, {"color"=>"blue", "model"=>"Gremlin"}]
basic.rb(main):003:0>

If you need the header row as well, you can call raw().
raw.rb(main):001:0> table.raw
=> [["color", "model"], ["rust", "Camaro"], ["blue", "Gremlin"]]
raw.rb(main):002:0>

If your headers are in the first column (rather than the first row), you can
transpose() the table or call rows_hash().

3.

Cucumber also allows you to ignore surplus or missing rows, but that use is rarer.

www.it-ebooks.info

report erratum discuss

Chapter 1. Cucumber Techniques

transpose.rb(main):001:0> table.transpose
=>
|
color |
rust
|
blue
|
|
model |
Camaro |
Gremlin |

transpose.rb(main):002:0> table.rows_hash
=> {"color"=>"model", "rust"=>"Camaro", "blue"=>"Gremlin"}
transpose.rb(main):003:0>

Using the techniques in this recipe, you can keep your Cucumber features
in the language of the problem domain. The mundane details of data formats
and APIs will be confined to your Ruby step definitions, where they belong.

www.it-ebooks.info

report erratum discuss

Das könnte Ihnen auch gefallen