Cucumber data tables

Filed under: Cucumber, Programming, — Tags: BDD, Behaviour Driven Development - BDD, Cucumber, Cucumber DataTable, Cucumber-jvm, DataTable, JUnit, Java, Maven, Test automation — Thomas Sundberg — 2014-06-30

Cucumber has a nice feature that will help you to use tables in your scenarios. The table can easily be converted to a list or a map that you can use in your step. I will show you a few examples that may help you get started.

Convert a one column table to a List

A first example of a scenario with a table could be this:

  Scenario: The sum of a list of numbers should be calculated
    Given a list of numbers
      | 17   |
      | 42   |
      | 4711 |
    When I summarize them
    Then should I get 4770

This table can easily be converted to a List<Integer> that you can use in your step.

When you execute your feature without the step defined, you will get this snippet to help you:

You can implement missing steps with the snippets below:

@Given("^a list of numbers$")
public void a_list_of_numbers(DataTable arg1) throws Throwable {
    // Write code here that turns the phrase above into concrete actions
    // For automatic transformation, change DataTable to one of
    // List<YourType>, List<List<E>>, List<Map<K,V>> or Map<K,V>.
    // E,K,V must be a scalar (String, Integer, Date, enum etc)
    throw new PendingException();
}

The DataTable arg1 can be replaced by a List<Integer> numbers that can be used in the step.

A complete example may look like:

src/test/resources/se/thinkcode/Sum.feature

Feature: Cucumber can convert Gherkin data tables to a list of a type you specify.

  Scenario: The sum of a list of numbers should be calculated
    Given a list of numbers
      | 17   |
      | 42   |
      | 4711 |
    When I summarize them
    Then should I get 4770

The corresponding implementation could look like this:

src/test/java/se/thinkcode/steps/ArithmeticSteps.java

package se.thinkcode.steps;

import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;

import java.util.List;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

public class ArithmeticSteps {
    private List<Integer> numbers;
    private int sum;

    @Given("^a list of numbers$")
    public void a_list_of_numbers(List<Integer> numbers) throws Throwable {
        this.numbers = numbers;
    }

    @When("^I summarize them$")
    public void i_summarize_them() throws Throwable {
        for (Integer number : numbers) {
            sum += number;
        }
    }

    @Then("^should I get (\\d+)$")
    public void should_I_get(int expectedSum) throws Throwable {
        assertThat(sum, is(expectedSum));
    }
}

This example converted a table with scalars to a list of Integers. Let me show an example where I convert a table to a Map.

Convert a two column table to a Map

We need to specify a Map<K, V>. The types must match the columns in the table. It could look like this:

src/test/resources/se/thinkcode/SimplePriceList.feature

Feature: Cucumber can convert a Gherkin table to to a map.
  This an example of a simple price list.

  Scenario: A price list can be represented as price per item
    Given the price list for a coffee shop
      | coffee | 1 |
      | donut  | 2 |
    When I order 1 coffee and 1 donut
    Then should I pay 3

The corresponding implementation could look like this:

src/test/java/se/thinkcode/steps/SimplePriceList.java

package se.thinkcode.steps;

import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;

import java.util.Map;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

public class SimplePriceList {
    private Map<String, Integer> priceList;
    private int totalSum;

    @Given("^the price list for a coffee shop$")
    public void the_price_list_for_a_coffee_shop(Map<String, Integer> priceList) throws Throwable {
        this.priceList = priceList;
    }

    @When("^I order (\\d+) (.*) and (\\d+) (.*)$")
    public void i_order_coffee_and_donut(int numberOfFirstItems, String firstItem,
                                         int numberOfSecondItems, String secondItem) throws Throwable {
        int firstPrice = priceList.get(firstItem);
        int secondPrice = priceList.get(secondItem);

        totalSum += firstPrice * numberOfFirstItems;
        totalSum += secondPrice * numberOfSecondItems;
    }

    @Then("^should I pay (\\d+)$")
    public void should_I_pay(int expectedCost) throws Throwable {
        assertThat(totalSum, is(expectedCost));
    }

}

The interesting part is the implementation of the_price_list_for_a_coffee_shop(Map<String, Integer> priceList). The table will be converted to a map with a String as key and Integer as the value. I use this map as a dictionary when I calculate the total sum to be paid for a coffee and a donut.

The example above show how a relatively simple table can be converted. If it would be a real price list, I would like to see the currency used. Let me extend the example to include a currency. This will allow me to introduce a custom type that the DataTable converter will be able to use.

Convert a three column table to a Map

We are unfortunately not able to convert a custom type to a Map. But we can solve this by using a List<YourType> and then convert this to a map that is easier to use as a dictionary. But let me start with the feature before we look into the step implementation.

src/test/resources/se/thinkcode/InternationalPriceList.feature

Feature: Cucumber can convert a Gherkin table to to a map.
  This an example of a more complicated price list.

  Scenario: An international coffee shop must handle currencies
    Given the price list for an international coffee shop
      | product | currency | price |
      | coffee  | EUR      | 1     |
      | donut   | SEK      | 18    |
    When I buy 1 coffee and 1 donut
    Then should I pay 1 EUR and 18 SEK

The price list now handles currencies. It is a very strange coffee shop that uses different currencies for different products. But it allows me to introduce a custom type that can be automatically converted to a List that can be converted to a Map. I want it as a map so I can use it as a dictionary when I use it.

My custom type is implemented as

src/test/java/se/thinkcode/steps/Price.java

package se.thinkcode.steps;

public class Price {
    private String product;
    private Integer price;
    private String currency;

    public Price(String product, Integer price, String currency) {
        this.product = product;
        this.price = price;
        this.currency = currency;
    }

    public String getProduct() {
        return product;
    }

    public Integer getPrice() {
        return price;
    }

    public String getCurrency() {
        return currency;
    }
}

The steps are finally implemented as:

src/test/java/se/thinkcode/steps/InternationalPriceList.java

package se.thinkcode.steps;

import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

public class InternationalPriceList {
    private Map<String, Price> priceList;
    private int sekSum;
    private int euroSum;

    @Given("^the price list for an international coffee shop$")
    public void the_price_list_for_an_international_coffee_shop(List<Price> prices) throws Throwable {
        priceList = new HashMap<String, Price>();

        for (Price price : prices) {
            String key = price.getProduct();
            priceList.put(key, price);
        }
    }

    @When("^I buy (\\d+) (.*) and (\\d+) (.*)$")
    public void i_order_coffee_and_donut(int numberOfFirstItems, String firstItem,
                                         int numberOfSecondItems, String secondItem) throws Throwable {
        Price firstPrice = priceList.get(firstItem);
        calculate(numberOfFirstItems, firstPrice);
        Price secondPrice = priceList.get(secondItem);
        calculate(numberOfSecondItems, secondPrice);
    }

    private void calculate(int numberOfItems, Price price) {
        if (price.getCurrency().equals("SEK")) {
            sekSum += numberOfItems * price.getPrice();
            return;
        }
        if (price.getCurrency().equals("EUR")) {
            euroSum += numberOfItems * price.getPrice();
            return;
        }
        throw new IllegalArgumentException("The currency is unknown");
    }

    @Then("^should I pay (\\d+) EUR and (\\d+) SEK$")
    public void should_I_pay_EUR_and_SEK(int expectedEuroSum, int expectedSekSum) throws Throwable {
        assertThat(euroSum, is(expectedEuroSum));
        assertThat(sekSum, is(expectedSekSum));
    }
}

I have to convert the list with my custom type to a map manually. Converting the list to a map will simplify how I can use it later.

The table contains a headline with product, currency and price. The order is different compared to the order of the fields in my Price class. This difference is handled by Cucumber when converting the table.

Wrapping up

The file structure used to implement this example is

example
|-- pom.xml
`-- src
    |-- main
    |   `-- java
    `-- test
        |-- java
        |   `-- se
        |       `-- thinkcode
        |           |-- RunCukesTest.java
        |           `-- steps
        |               |-- ArithmeticSteps.java
        |               |-- InternationalPriceList.java
        |               |-- Price.java
        |               `-- SimplePriceList.java
        `-- resources
            `-- se
                `-- thinkcode
                    |-- InternationalPriceList.feature
                    |-- SimplePriceList.feature
                    `-- Sum.feature

There are two files left to show, the Maven pom and the runner I use to run the example. The Maven pom looks like this:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>se.thinkcode.blog</groupId>
    <artifactId>example</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>1.1.7</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>1.1.7</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

It is small and only contains the dependencies needed for Cucumber and JUnit.

To run the example, I need something that will run Cucumber. I use the JUnit runner. The implementation looks like this:

src/test/java/se/thinkcode/RunCukesTest.java

package se.thinkcode;

import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;

@RunWith(Cucumber.class)
public class RunCukesTest {
}

Conclusion

Using tables may increase the expressiveness when writing a scenario. Use them whenever you need. An alternative to use a table may sometimes be to create a scenario outline.

Credits

I wish to thank Johan Helmfrid and Malin Ekholm for the feedback.

Resources



(less...)

Pages

About
Events
Why

Categories

Agile
Automation
BDD
Clean code
Continuous delivery
Continuous deployment
Continuous integration
Cucumber
Culture
Design
DevOps
Executable specification
Git
Gradle
Guice
J2EE
JUnit
Java
Javascript
Kubernetes
Linux
Load testing
Maven
Mockito
New developers
Pair programming
PicoContainer
Presentation
Programming
Public speaking
Quality
React
Recruiting
Requirements
Scala
Selenium
Software craftsmanship
Software development
Spring
TDD
Teaching
Technical debt
Test automation
Tools
Web
Windows
eXtreme Programming

Authors

Thomas Sundberg
Adrian Bolboaca

Archives

Meta

rss RSS