Monthly Archives: July 2014

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