Thomas Sundberg

October 31, 2011

Why separate test definitions from a driving JUnit class?

Why should the step definitions be separated from the driving JUnit class when using Cucumber for Java?

An example of a setup using Cucumber for Java could look like this:

package se.sigma.cucumber;

import cucumber.annotation.en.Given;
import cucumber.annotation.en.Then;
import cucumber.annotation.en.When;
import cucumber.junit.Cucumber;
import cucumber.junit.Feature;
import org.junit.runner.RunWith;

@RunWith(Cucumber.class)
public class DataRoamingStepAndTest {
    @Given("^I am on a ship to Finland$")
    public void I_am_on_a_ship_to_Finland() {
        // Express the Regexp above with the code you wish you had
    }

    @Then("^I verify that I have data roaming off on my telephone$")
    public void I_verify_that_I_have_data_roaming_off_on_my_telephone() {
        // Express the Regexp above with the code you wish you had
    }

    @When("^it departs from the harbor$")
    public void it_departs_from_the_harbor() {
        // Express the Regexp above with the code you wish you had
    }
}

This class mixes JUnit and the step definitions into one class. This may seem like a good idea. We need to drive the feature using some mechanism and it is nice to use JUnit because it is supported in many environments.

There are, however, a few problems when mixing steps and the driving class.

  • Coupling step definitions to a particular feature
  • Runner agnostic
  • Single Responsible Principle – SRP

Coupling step definitions to a particular feature

A feature describes a behaviour. If you use the same description to describe two different behaviours of a system, then you have an ambiguous behaviour. This is therefore a smell that indicates that the system isn’t clearly defined.

Steps in Cucumber are defined global. One particular step definition can be used in more then one feature, but it may not be executed in more then one place. The rational behind this global definition is to make sure that the system isn’t using the same description to describe different parts.

Runner agnostic

Cucumber is not tied to JUnit. The steps can be executed using any runner. Not allowing to tie the step definitions to a JUnit class with specific runner is a consequence of this desired behaviour.

Single Responsibility Principle – SRP

The single responsibility principle says that everything should have one and only one responsibility. The class above has two responsibilities. It connects the feature to the step definitions, which is an undesired behaviour, and implements the steps. This responsibility should be split to two or more classes.

The correct way

A another approach is to separate the step definitions and the JUnit class that can drive them into two separate classes. The result looks like this:

File: src/test/java/se/sigma/cucumber/DataRoamingTest.java
package se.sigma.cucumber;

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

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

An empty JUnit class that runs the steps defined in the feature.

File: src/test/java/se/sigma/cucumber/DataRoamingSteps.java
package se.sigma.cucumber;

import cucumber.annotation.en.Given;
import cucumber.annotation.en.Then;
import cucumber.annotation.en.When;

public class DataRoamingSteps {
    @Given("^I am on a ship to Finland$")
    public void I_am_on_a_ship_to_Finland() {
        // Express the Regexp above with the code you wish you had
    }

    @Then("^I verify that I have data roaming off on my telephone$")
    public void I_verify_that_I_have_data_roaming_off_on_my_telephone() {
        // Express the Regexp above with the code you wish you had
    }

    @When("^it departs from the harbor$")
    public void it_departs_from_the_harbor() {
        // Express the Regexp above with the code you wish you had
    }
}

The steps are implemented in a separate class that may be used by any feature.

This is the preferred way and it is actually being forced since late October 2010. A CucumberException will be thrown if the test class contains any methods.

The Feature

The feature used in this example is not very interesting. But it look like this:

File: src/test/resources/se/sigma/cucumber/DataRoaming.feature
Feature: Avoid high data roaming charges

    As an owner of a mobile telephone
    I want to avoid high roaming charges
    So I can use my money to buy beer instead

    Scenario: Travelling to Finland
        Given I am on a ship to Finland
        When it departs from the harbor
        Then I verify that I have data roaming off on my telephone

Maven

The Maven pom used is defined as:

File: pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>se.sigma.cucumber</groupId>
    <artifactId>separate-steps-and-junit</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>1.0.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>1.0.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Acknowledgements

This post has been reviewed by some people who I wish to thank for their help

  • Malin Ekholm
  • Aslak Hellesøy

Thank you very much for your feedback!

Resources

About these ads

1 Comment »

  1. Thanks for the writeup Thomas. Some things are a little unclear:

    * “One particular step can be used in more than one feature” – you mean step definition I suppose (steps are in gherkin)
    * The recent change not allowing methods in JUnit classes doesn’t have anything to do with performance.
    * You say “methods are instantiated twice”. Did you mean “classes are instantiated twice”?
    * Not sure what you mean by “JUnit runner creates all its methods”? You mean JUnit instantiates classes?

    The main reason for this change is to prevent step definitions to be coupled to a particular feature (so they can be reused in other features).

    Keep up the good work!

    Comment by Aslak Hellesøy — November 1, 2011 @ 01:26


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Theme: Customized Silver is the New Black. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 39 other followers

%d bloggers like this: