Data Tables

Sometimes steps in a scenario need to describe data that doesn’t easily fit on a single line of Given, When, or Then. Gherkin allows us to place these details in a table right underneath a step. Data tables give you a way to extend a Gherkin step beyond a single line to include a larger piece of data.

For example, consider these steps:

 Given a User ​"Michael Jackson"​ born on August 29, 1958
 And a User ​"Elvis"​ born on January 8, 1935
 And a User ​"John Lennon"​ born on October 9, 1940
 ...

Boring! We wouldn’t tolerate this kind of repetitive stuff in a traditional specification document, and we don’t have to tolerate it in a Cucumber specification either. We can collect those steps together into a single step that uses a table to express the data:

 Given these Users​:
  | name | date of birth |
  | Michael Jackson | August 29, 1958 |
  | Elvis | January 8, 1935 |
  | John Lennon | October 9, 1940 |

That’s much clearer. The table starts on the line immediately following the step, and its cells are separated using the pipe character: |. You can line up the pipes using whitespace to make the table look tidy, although Cucumber doesn’t mind whether you do; it will strip out the values in each cell, ignoring the surrounding whitespace.

In the previous table, we’ve used a heading for each column in the table, but that’s only because it made sense for that step. You have the freedom to specify data in different ways, such as putting the headings down the side:

 Then I should see a vehicle that matches the following description​:
  | Wheels | 2 |
  | Max Speed | 60 mph |
  | Accessories | lights, shopping basket |

Or just to specify a list:

 Then my shopping list should contain​:
  | Onions |
  | Potatoes |
  | Sausages |
  | Apples |
  | Relish |

To explain how to work with these different shaped tables, we need to take a short dive down into the step definition layer. If you’re not interested in writing Ruby step definition code, feel free to skip this bit.

Working with Data Tables in Step Definitions

We’ll illustrate how to use a data table in your step definitions with a quick game of tic-tac-toe. Let’s imagine we’re building a tic-tac-toe game and we’ve started working on the basic feature for making moves on the board. We start with a scenario like this:

 Feature​:
 Scenario​:
 Given a board like this​:
  | | 1 | 2 | 3 |
  | 1 | | | |
  | 2 | | | |
  | 3 | | | |
  When player x plays in row 2, column 1
 Then the board should look like this​:
  | | 1 | 2 | 3 |
  | 1 | | | |
  | 2 | x | | |
  | 3 | | | |

We’ll show you how to grab the table from the first step, manipulate it in the second, and finally compare the expected board from the scenario with the actual one.

Run cucumber to generate the step definition snippets, and paste them into features/step_definitions/board_steps.rb:

 Given(​/^a board like this:$/​) ​do​ |table|
 # table is a Cucumber::Core::Ast::DataTable
  pending ​# Write code here that turns the phrase above into concrete actions
 end
 
 Given(​/^player x plays in row (d+), column (d+)$/​) ​do​ |arg1, arg2|
  pending ​# Write code here that turns the phrase above into concrete actions
 end
 
 Given(​/^the board should look like this:$/​) ​do​ |table|
 # table is a Cucumber::Core::Ast::DataTable
  pending ​# Write code here that turns the phrase above into concrete actions
 end

Notice that the snippets for the two step definitions where we’re going to receive a table are a little different. There’s a comment telling you that the argument you’re being passed is a Cucumber::Ast:Table object. That’s a really rich object with lots of methods for interacting with its data. We’ll show you some of the most useful ones now.

Let’s start fleshing out those step definitions.

Turning the Table into an Array

Under the hood, the table is just a two-dimensional array. Often we’ll want to work with it in that raw form, so we can call the raw method on it to do just that. Let’s get the raw data from the table and store it in an instance variable @board, which we can manipulate in our second step when we want to make a move.

As an experiment, add an implementation for the second step definition that just prints the raw board out so we can see what it looks like:

 Given(​/^a board like this:$/​) ​do​ |table|
  @board = table.raw
 end
 
 Given(​/^player x plays in row (d+), column (d+)$/​) ​do​ |arg1, arg2|
  puts @board
  pending ​# Write code here that turns the phrase above into concrete actions
 end
 
 Given(​/^the board should look like this:$/​) ​do​ |table|
 # table is a Cucumber::Core::Ast::DataTable
  pending ​# Write code here that turns the phrase above into concrete actions
 end

When you run cucumber, you should see the two-dimensional array printed out.

 $ ​​cucumber​​ ​​-f​​ ​​progress
 .
 [["", "1", "2", "3"], ["1", "", "", ""], ["2", "", "", ""], ["3", "", "", ""]]
 P-
 
 (::) pending steps (::)
 
 features/step_definitions/board_steps.rb:5:in ‘/^player x plays in row
 (d+), column (d+)$/’
 
 1 scenario (1 pending)
 3 steps (1 skipped, 1 pending, 1 passed)
 0m0.017s

Notice that the raw table includes the column and row headings.

Comparing Tables with Diff

So that we can start out with a failing test, we’ll skip doing any manipulation of the board in this step for now. So, remove the body of the second step definition and make the following implementation in the last step definition:

 Given(​/^a board like this:$/​) ​do​ |table|
  @board = table.raw
 end
 
 Given(​/^player x plays in row (d+), column (d+)$/​) ​do​ |arg1, arg2|
 end
 
 Given(​/^the board should look like this:$/​) ​do​ |expected_table|
  expected_table.diff!(@board)
 end

We’ve used the diff! method on the table that describes how things should look, passing it the actual board as we see it in our application. When you run cucumber again, you should see the step failed because the tables weren’t identical:

 $ ​​cucumber
 Feature:
 
  Scenario:
  Given a board like this:
  | | 1 | 2 | 3 |
  | 1 | | | |
  | 2 | | | |
  | 3 | | | |
  When player x plays in row 2, column 1
  Then the board should look like this:
  | | 1 | 2 | 3 |
  | 1 | | | |
  | 2 | x | | |
  | 3 | | | |
  Tables were not identical:
 
  | | 1 | 2 | 3 |
  | 1 | | | |
  | (-) 2 | (-) x | (-) | (-) |
  | (+) 2 | (+) | (+) | (+) |
  | 3 | | | |
  (Cucumber::MultilineArgument::DataTable::Different)
  ./features/step_definitions/board_steps.rb:9
  features/tic_tac_toe.feature:9
 
 Failing Scenarios:
 cucumber features/tic_tac_toe.feature:2
 
 1 scenario (1 failed)
 3 steps (1 failed, 2 passed)

If you have color in your console output, you’ll see the cells that were the same are green. Expected cells that weren’t found are yellow, and the actual cells are printed next to them in gray. It’s possible to tweak the comparison behavior by passing a hash of options to diff!, which you can read about in the documentation for Cucumber::Ast::Table.

Let’s fix the When step to make the scenario pass. Add this implementation to the second step definition:

 Given(​/^player x plays in row (d+), column (d+)$/​) ​do​ |row, col|
  row, col = row.to_i, col.to_i
  @board[row][col] = ​'x'
 end

Run the scenario, and you should see it pass.

This is just a taste of what you can do with data tables in Cucumber. We encourage you to read the documentation[14] for Cucumber::MultilineArgument::DataTable and play around with it yourself.

Data tables are a great feature of Gherkin. They are really versatile, and they help you express data concisely, as you would want to in a normal specification document. With backgrounds and data tables, you can do a lot to reduce the noise and clutter in your scenarios. Even when you use these tools, you will still sometimes see a pattern where one scenario looks a lot like the one that came before it and the one after it. This is where a scenario outline can help.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.189.186