Tag Archives: bdd

Writing Cucumber JVM step definitions

When I started working with Cucumber JVM it took a while to get the knack of how to write nice and efficient step definitions. Being a regular expression ninja certainly helps but you can get by with a few examples which will enable you to write a wide range of Cucumber steps. Below are some I found the most useful.

Exact match

I know it’s a no-brainer but will get you started.

@Given("I have a cucumber step")
public void i_have_a_cucumber_step() throws Throwable {
    System.out.println("Step definition exact match");
}

Use anchors

Remember to use anchors to mark the beginning (^) and end of the expression ($). The step definition above will match both steps below:

Given I have a cucumber step
Given I have a cucumber step and a salad

What we really want is to match the first one and get Cucumber to give us a stub implementation of the second one. In order to avoid unexpected matches we need to add anchors:

@Given("^I have a cucumber step$")

Capture integers and strings

@Given("^I have (\\d+) (.*) in my basket$")
public void i_have_in_my_basket(int number, String veg) throws Throwable {
    System.out.println(format("I have {0} {1} in my basket", number, veg));
}

Let’s have a look at the step definition. By using round brackets we mark part of the expression as a capture group so that Cucumber can map it to a method parameter. In our case have the following patterns:

  • \d+ matching at least one digit, d represents a digit, + is a quantifier and means one or more times; the expression is escaped with a backslash, because it also is the escape character in Java we need to escape it with another backslash and we end up with \\d+
  • .+ matching at least one character, . (dot) represents any character

Use non-capturing groups

It may be useful to have a bit of flexibility and add words in the step which are not matched. This is what non-capturing groups can be used for. There is a ?: operator (question mark and colon) and if it is present at the beginning of the group it will not be mapped to method parameters.

@Given("^I have a (?:tasty|nasty|rusty) cucumber step$")
public void i_have_a_X_cucumber_step() throws Throwable {
    System.out.println("Step definition with a non-capturing group");
}

This step definition will match three different steps to one step definition. Note that I used logical operator described below.

Scenario: Non-capturing group        # cucumber/regex.feature:9
  Given I have a tasty cucumber step # CucumberSteps.i_have_a_X_cucumber_step()
  Given I have a nasty cucumber step # CucumberSteps.i_have_a_X_cucumber_step()
  Given I have a rusty cucumber step # CucumberSteps.i_have_a_X_cucumber_step()

Singular and plural

Use ? qualifier to match words in both singular and plural. ? at the end of a word makes the last letter optional. We can also use the logical alternative operator | (pipe) to support correct grammar as well as irregular plurals which will make sentence read better.

@Given("^There (?:is|are) (\\d+) (?:cats?|ox|oxen) fed by (\\d+) (?:persons?|people)$")
public void animals_fed_by_people(int animals, int persons) throws Throwable {
    System.out.println(
            format("{0} animal(s) fed by {1} person(s)", animals, persons));
}
Given There is 1 cat fed by 1 person
Given There are 2 cats fed by 1 person
Given There are 2 cats fed by 2 persons
Given There are 2 cats fed by 3 people
Given There is 1 ox fed by 4 persons
Given There are 3 oxen fed by 5 people

Use Data Tables

You can use DataTable to manage larger amount of data. They are quite powerful but not the most intuitive as you either need to deal with a list of maps or a map of lists.

@Given ("^I have the following order$")
public void i_have_the_following_order (DataTable table) throws Throwable {
    for (Map<String, String> map : table.asMaps(String.class, String.class)) {
        String vegetable = map.get("vegetable");
        String amount = map.get("amount");
        String cost = map.get("cost");
        System.out.println(
                format("Order of {0} {1}s at the cost of {2}", 
                amount, vegetable, cost));
    }
}
Scenario: Data tables
  Given I have the following order
    | vegetable | amount | cost |
    | cucumber  |   4    |  10  |
    | carrot    |   5    |   6  |
    | potato    |   6    |   4  |

Map data tables to domain objects

Luckily there are easier ways to access your data than DataTable. For instance you can create a domain object and have Cucumber map your data in a table to a list of these.

@Given("^I have another order$")
public void i_have_another_order(List<OrderItem> list) throws Throwable {
    for (OrderItem orderItem : list) {
        String vegetable = orderItem.getVegetable ();
        int amount = orderItem.getAmount();
        int cost = orderItem.getCost ();
        System.out.println(
                format("Order of {0} {1}s at the cost of {2}", 
                amount, vegetable, cost));
    }
}

Our domain object – OrderItem

package com.czeczotka.bdd.domain;

public class OrderItem {

    private String vegetable;
    private int amount;
    private int cost;

    public String getVegetable () {
        return vegetable;
    }

    public void setVegetable (String vegetable) {
        this.vegetable = vegetable;
    }

    public int getAmount () {
        return amount;
    }

    public void setAmount (int amount) {
        this.amount = amount;
    }

    public int getCost () {
        return cost;
    }

    public void setCost (int cost) {
        this.cost = cost;
    }
}
Scenario: List of domain objects
  Given I have another order
    | vegetable | amount | cost |
    | cucumber  |   4    |  10  |
    | carrot    |   5    |   6  |
    | potato    |   6    |   4  |

 More on Java and regular expressions

If you want a bit more detail on how regular expressions work in Java  probably the best  place to start is the Java Tutorial and the detailed javadoc for the Pattern class.

The code

The code of this post is on github so follow the link below to browse it.

http://github.com/czeczotka/cucumber-jvm-regex

You can easily clone and play with it locally by simply following these steps.

$ git clone https://github.com/czeczotka/cucumber-jvm-regex.git
$ cd cucumber-jvm-regex
$ mvn test

I believe with the tools described here you have enough options to get started and create powerful step definitions. Last thing to note is that when you run the code above the System.out.println will appear before Cucumber output and this is because Cucumber displays its output after scenario execution when system outs have already written to the output stream.

Cucumber JVM with Maven in minutes

So there you are, tempted by all the benefits of Behaviour Driven Development, curious of the adventure to come and excited about the new technology you want to try out. But where should you start? If you are in the Ruby world things are quite simple to setup but if Java and Maven are your weapons of choice then there are some tiny details to consider when departing on the BDD journey. In this blog post I will demonstrate how to setup a basic application with BDD tests using Java, Maven and Cucumber JVM which can be run command-line.

I want it now!

The code of this post is on github so if you are not interested in the setup details and just want to play with it then follow the link below to browse it.

http://github.com/czeczotka/cucumber-jvm-maven

You can easily clone and play with it locally by simply following these steps.

$ git clone https://github.com/czeczotka/cucumber-jvm-maven.git
$ cd cucumber-jvm-maven
$ mvn test

For all the exciting details read on.

Generate a Maven project

There is a good setup guide for Maven so you can have a look at that but basically we need to create a project skeleton. I used the command below but you may want to edit the groupId and artifactId parameters.

$ mvn archetype:generate 
        -DgroupId=com.czeczotka.bdd 
        -DartifactId=cucumber-jvm-maven 
        -DarchetypeArtifactId=maven-archetype-quickstart

You will be asked about version number and properties configuration but the defaults will do. The following structure with pom.xml, App.java and AppTest.java (which follows Maven’s Standard Directory Layout) should be created:

.
└── cucumber-jvm-maven
   ├── pom.xml
   └── src
       ├── main
       │   └── java
       │       └── com
       │           └── czeczotka
       │               └── bdd
       │                   └── App.java
       └── test
           └── java
               └── com
                   └── czeczotka
                       └── bdd
                           └── AppTest.java

Now that we have a bare-bones Maven project we can start doing Behaviour Driven Development by creating a failing acceptance test. Delete generated App.java and AppTest.java files and create the following cucumber feature for the calculator application (exciting, huh?)

Feature: Calculator
  As a user
  I want to use a calculator
  So that I don't need to calculate myself

  Scenario: Add two numbers
    Given I have a calculator
    When I add 2 and 3
    Then the result should be 5
  • line 1 – tell Cucumber this is a feature file,
  • lines 2-4 – user story, this is not executed but we add it for clarity,
  • line 6 – start a new scenario,
  • lines 7-9 – scenario steps which are executed.

In order to run the feature we create a runner class RunCalculatorTest which contains Cucumber execution parameters. Note that these can also be passed in command-line but we put them in the test for simplicity.

package com.czeczotka.bdd.runner;

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

@RunWith(Cucumber.class)
@CucumberOptions(
        format = { "pretty", "html:target/cucumber" },
        features = "classpath:cucumber/calculator.feature"
)
public class RunCalculatorTest {
}
  • line 7 – we want Cucumber to run this test,
  • lines 8-11 – pass on Cucumber options: output formatting and location of the feature file.

It’s probably a good moment to update pom.xml to add required dependencies and configure the compiler.


<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.czeczotka.bdd</groupId>
    <artifactId>cucumber-jvm-maven</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>cucumber-jvm-maven</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>1.1.8</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>1.1.8</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>
                    maven-compiler-plugin
                </artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
  • lines 12-31 – we use Cucumber and JUnit so Maven will download required jars,
  • lines 34-44 – java compiler configuration.

Now your project should look similar to this:

.
└── cucumber-jvm-maven
   ├── pom.xml
   └── src
       └── test
           ├── java
           │   └── com
           │       └── czeczotka
           │           └── bdd
           │               └── runner
           │                   └── RunCalculatorTest.java
           └── resources
               └── cucumber
                   └── calculator.feature

Run the tests and check the output

We’re good to go, let’s run the tests:

$ mvn clean test
[INFO] Scanning for projects...
[INFO]   
[INFO] -------------------------------------------------------
[INFO] Building cucumber-jvm-maven 1.0-SNAPSHOT
[INFO] -------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.3:clean (default-clean) @ cucumber-jvm-maven ---
[INFO]
[INFO] --- maven-resources-plugin:2.3:resources (default-resources) @ cucumber-jvm-maven ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /home/jakub/development/blog/cucumber-jvm-maven/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ cucumber-jvm-maven ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-resources-plugin:2.3:testResources (default-testResources) @ cucumber-jvm-maven ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ cucumber-jvm-maven ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/jakub/development/blog/cucumber-jvm-maven/target/test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.10:test (default-test) @ cucumber-jvm-maven ---
[INFO] Surefire report directory: /home/jakub/development/blog/cucumber-jvm-maven/target/surefire-reports

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running com.czeczotka.bdd.runner.RunCalculatorTest
Feature: Calculator
 As a user
 I want to use a calculator
 So that I don't need to calculate myself

 Scenario: Add two numbers     # cucumber/calculator.feature:6
   Given I have a calculator
   When I add 2 and 3
   Then the result should be 5

1 Scenarios (1 undefined)
3 Steps (3 undefined)
0m0.000s

You can implement missing steps with the snippets below:

@Given("^I have a calculator$")
public void i_have_a_calculator() throws Throwable {
   // Write code here that turns the phrase above into concrete actions
   throw new PendingException();
}

@When("^I add (\\d+) and (\\d+)$")
public void i_add_and(int arg1, int arg2) throws Throwable {
   // Write code here that turns the phrase above into concrete actions
   throw new PendingException();
}

@Then("^the result should be (\\d+)$")
public void the_result_should_be(int arg1) throws Throwable {
   // Write code here that turns the phrase above into concrete actions
   throw new PendingException();
}

Tests run: 5, Failures: 0, Errors: 0, Skipped: 4, Time elapsed: 3.367 sec

Results :

Tests run: 5, Failures: 0, Errors: 0, Skipped: 4

[INFO] -------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] -------------------------------------------------------
[INFO] Total time: 20.603s
[INFO] Finished at: Sun Jul 20 23:22:04 BST 2014
[INFO] Final Memory: 12M/211M
[INFO] -------------------------------------------------------

We are getting quite a lot of output but the most important thing is that Cucumber tells us about missing step definitions and provides skeletons for the implementation. That’s a very useful feature as it gives you information about the missing code and helps with writing regular expressions. So let’s put these steps in CalculatorSteps:

package com.czeczotka.bdd.steps;

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

public class CalculatorSteps {

    @Given("^I have a calculator$")
    public void i_have_a_calculator() throws Throwable {
        throw new PendingException();
    }

    @When("^I add (\\d+) and (\\d+)$")
    public void i_add_and(int arg1, int arg2) throws Throwable {
        throw new PendingException();
    }

    @Then("^the result should be (\\d+)$")
    public void the_result_should_be(int arg1) throws Throwable {
        throw new PendingException();
    }
}

When we run Maven again the output contains information about the step execution but because they throw cucumber.api.PendingException, the first one to do so is marked as pending rather than failed while the remaining steps are skipped:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.czeczotka.bdd.runner.RunCalculatorTest
Feature: Calculator
  As a user
  I want to use a calculator
  So that I don't need to calculate myself

  Scenario: Add two numbers     # cucumber/calculator.feature:6
    Given I have a calculator   # CalculatorSteps.i_have_a_calculator()
      cucumber.api.PendingException: TODO: implement me
        at com.czeczotka.bdd.steps.CalculatorSteps.i_have_a_calculator(CalculatorSteps.java:13)
        at ?.Given I have a calculator(cucumber/calculator.feature:7)

    When I add 2 and 3          # CalculatorSteps.i_add_and(int,int)
    Then the result should be 5 # CalculatorSteps.the_result_should_be(int)

1 Scenarios (1 pending)
3 Steps (2 skipped, 1 pending)
0m0.226s

cucumber.api.PendingException: TODO: implement me
        at com.czeczotka.bdd.steps.CalculatorSteps.i_have_a_calculator(CalculatorSteps.java:13)
        at ?.Given I have a calculator(cucumber/calculator.feature:7)

Tests run: 5, Failures: 0, Errors: 0, Skipped: 4, Time elapsed: 1.155 sec

Implement the steps

Ok, now it is the time to implement the steps

package com.czeczotka.bdd.steps;

import com.czeczotka.bdd.calculator.Calculator;
import cucumber.api.java.Before;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class CalculatorSteps {

    private Calculator calculator;

    @Before
    public void setUp() {
        calculator = new Calculator();
    }

    @Given("^I have a calculator$")
    public void i_have_a_calculator() throws Throwable {
        assertNotNull(calculator);
    }

    @When("^I add (\\d+) and (\\d+)$")
    public void i_add_and(int arg1, int arg2) throws Throwable {
        calculator.add(arg1, arg2);
    }

    @Then("^the result should be (\\d+)$")
    public void the_result_should_be(int result) throws Throwable {
        assertEquals(result, calculator.getResult());
    }
}

And the Calculator itself to satisfy the tests

package com.czeczotka.bdd.calculator;

public class Calculator {

    private int result;

    public void add(int arg1, int arg2) {
        result = arg1 + arg2;
    }

    public int getResult() {
        return result;
    }
}

Let’s run the tests hoping for a green bar

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.czeczotka.bdd.runner.RunCalculatorTest
Feature: Calculator
  As a user
  I want to use a calculator
  So that I don't need to calculate myself

  Scenario: Add two numbers     # cucumber/calculator.feature:6
    Given I have a calculator   # CalculatorSteps.i_have_a_calculator()
    When I add 2 and 3          # CalculatorSteps.i_add_and(int,int)
    Then the result should be 5 # CalculatorSteps.the_result_should_be(int)

1 Scenarios (1 passed)
3 Steps (3 passed)
0m0.218s

Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.164 sec

Results :

Tests run: 4, Failures: 0, Errors: 0, Skipped: 0

And it is green indeed!

Your project now has the following structure:

.
└── cucumber-jvm-maven
    ├── pom.xml
    └── src
        ├── main
        │   └── java
        │       └── com
        │           └── czeczotka
        │               └── bdd
        │                   └── calculator
        │                       └── Calculator.java
        └── test
            ├── java
            │   └── com
            │       └── czeczotka
            │           └── bdd
            │               ├── runner
            │               │   └── RunCalculatorTest.java
            │               └── steps
            │                   └── CalculatorSteps.java
            └── resources
                └── cucumber
                    └── calculator.feature

Console output problems

If your console is not configured to display coloured output from Cucumber you may see something that resembles a failed ascii-art like the one below.

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.czeczotka.bdd.runner.RunCalculatorTest
Feature: Calculator
  As a user
  I want to use a calculator
  So that I don't need to calculate myself

  Scenario: Add two numbers     ←[90m# cucumber/calculator.feature:6←[0m
    ←[32mGiven ←[0m←[32mI have a calculator←[0m   ←[90m# CalculatorSteps.i_have_a_calculator()←[0m
    ←[32mWhen ←[0m←[32mI add ←[0m←[32m←[1m2←[0m←[32m and ←[0m←[32m←[1m3←[0m          ←[90m# CalculatorSteps.i_add_and(int,int)←[0m
    ←[32mThen ←[0m←[32mthe result should be ←[0m←[32m←[1m5←[0m ←[90m# CalculatorSteps.the_result_should_be(int)←[0m

1 Scenarios (←[32m1 passed←[0m)
3 Steps (←[32m3 passed←[0m)
0m0.226s

Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.092 sec

You can fix it by telling the runner to give the monochrome output

@RunWith(Cucumber.class)
@CucumberOptions(
        monochrome = true,
        format = { "pretty", "html:target/cucumber" },
        glue = "com.czeczotka.bdd.steps",
        features = "classpath:cucumber/calculator.feature"
)
public class RunCalculatorTest {
}

What actually happens?

When you run maven’s test goal it tries to locate tests in the project and using the default naming convention it finds RunCalculatorTest. This test is configured to run with Cucumber which will pick up the configuration options including output (format and monochrome), glue code (step implementations) and location of the feature file. The feature file (written in a language called Gherkin) describes scenarios and their steps. Scenarios are executed by mapping steps to methods annotated with Given, When and Then annotations, instantiating their classes and executing these methods. Scenario and step execution can result in:

  • error – when there is an error or an exception during execution,
  • failure – when one of the assertions fails (AssertionError),
  • pending – when a step throws PendingException,
  • skip – when a step is not found or there was an exception in a previous step,
  • pass – when method execution is successful.

scenario output

I hope you find it useful.

Thanks