Tag Archives: Scenarios

Loading custom-named BDD scenario files for JBehave

Now that I’m using JBehave in a commercial project, I’ve rewritten the loading of the scenario files in such a way that I can call my tests something like com.example.login.InvalidLoginScenario and have the corresponding scenario file under {project}/src/test/resources/invalid_login.scenario.

Previously…

The standard JBehave scenario file is loaded with UnderscoredCamelCaseResolver, which converts the classname from camel-case to underscore-seperated classname. A resource path is constructed from the package plus the underscored filename to locate the file – e.g. {src.test}/com/example/login/invalid_login_scenario.

Previously (with inspiration), I modified the testcase to override the default Configuration object, which allowed the loading of the scenario file with an extension – so it would now look for {src.test}/com/example/login/invalid_login_scenario.scenario.

Goal

To make the creation and maintenance of the JBehave scenarios and testcases easier, I decided on some standards:

  • JBehave testcase classes should be suffixed with ..Scenario, to clearly communicate their purpose.
  • Scenario filenames should map to their corresponding test classes.
  • Scenario files should reside in the same location under resources/.
  • Scenario files should have the extension .scenario, to improve readability.

But instead of having a file named invalid_login_scenario.scenario, I want the test class InvalidLoginScenario to map to the file invalid_login.scenario. All this was basically possible with existing JBehave classes, when configured the correct way (and certain functions overridden).

The Source

import org.jbehave.scenario.PropertyBasedConfiguration;
import org.jbehave.scenario.RunnableScenario;
import org.jbehave.scenario.errors.PendingErrorStrategy;
import org.jbehave.scenario.parser.ClasspathScenarioDefiner;
import org.jbehave.scenario.parser.PatternScenarioParser;
import org.jbehave.scenario.parser.ScenarioDefiner;
import org.jbehave.scenario.parser.UnderscoredCamelCaseResolver;

/**
 * Customisation of standard JBehave {@link PropertyBasedConfiguration} to allow clearer naming of scenario files:
 * <ul>
 * <li> Usage of *.scenario file extension</li>
 * <li> Strip 'Scenario' off test class names</li>
 * <li> Load scenario files from classpath root</li>
 * </ul>
 * So a test class named <code>InvalidUsernameScenario</code> would be attempting to resolve the resource path <code>/invalid_username.scenario</code>.
 *
 * This configuration also fails on 'pending' (unimplemented) steps.
 */
public final class ScenarioConfiguration extends PropertyBasedConfiguration {
    private final ClassLoader _classLoader;

    public ScenarioConfiguration(final ClassLoader classLoader) {
        _classLoader = classLoader;
    }

    @Override
    public ScenarioDefiner forDefiningScenarios() {
        final ResourceNameResolver filenameResolver = new ResourceNameResolver(".scenario");
        filenameResolver.removeFromClassname("Scenario");
        return new ClasspathScenarioDefiner(filenameResolver, new PatternScenarioParser(this.keywords()), _classLoader);
    }

    @Override
    public PendingErrorStrategy forPendingSteps() {
        return PendingErrorStrategy.FAILING;
    }

    /**
     * Override {@link UnderscoredCamelCaseResolver} to load resources from classpath root. This means we can collect
     * scenario files in a single resource directory instead of in packages.
     */
    class ResourceNameResolver extends UnderscoredCamelCaseResolver {
        public ResourceNameResolver(final String extension) {
            super(extension);
        }

        @java.lang.Override
        protected String resolveDirectoryName(final Class<? extends RunnableScenario> scenarioClass) {
            return "";
        }
    }
}

The function forDefiningScenarios() is the important part – it sets the resolver to use the .scenario extension, but also strips out ‘Scenario’ from the class name.

Also, to force the resolver to look at the classpath root, the resolveDirectoryName() function is overridden to return an empty string.

The testcases using this Configuration object would call:

public class InvalidLoginScenario extends Scenario {
    public InvalidLoginScenario() {
        super(new ScenarioConfiguration(InvalidLoginScenario.class.getClassLoader()), new LoginScenarioSteps());
    }
}

References

Building a simple project using behavior-driven development with JBehave

IMPORTANT!
This post has moved to http://blog.brasskazoo.com!

In a previous post introducing BDD I outlined a simple bus ticket application. Since then I’ve been looking at JBehave, and thought I’d try building a simple version of the bus tickets project, using JBehave to support behavior-driven development of the application.

About JBehave

JBehave is a testing framework that takes the BDD concepts and applies it to JUnit tests, so you can effectively test your acceptance criteria directly using automated unit tests.

It uses BDD acceptance criteria as steps in a test case, and each Given/When/Then statement is interpreted and executed as a method. The acceptance criteria is simply stored in a text file which is read by JBehave when the test case is run.

Setting up JBehave

Download the distribution jars from http://jbehave.org (at the time of writing, version 2.3.2 is the latest), and extract it to a convenient location!

Set up references to the jars in your project (you need to reference at least hamcrest, junit-dep and jbehave-core for this example to work).

New project: ‘bus-tix’

For the purposes of this example, I’m creating a project I’ll call ‘bus-tix’, with production and test code packages.

The scenarios

Lets take one of the scenarios from my previous post (slightly modified):

Scenario: Inserting initial coins

Given that the application is operating
When a coin is inserted
Then ensure the time indicator displays the purchased time

I will use this as the behavioural spec for the initial work on the application. From the description of the scenario, we can instantly tell that we’re going to need a coin handler and time indicator as part of the application.

Now before I put this into a text file, I must point out that the default configuration of JBehave requires that the scenario files are named to match the class names of the test case – so if we had the test case InsertingInitialCoins, then the scenario text file must be named inserting_initial_coins (with no extension). I don’t particularly like this, so as you’ll see I modify the constructor so that I can call my scenario ‘inserting_initial_coins.scenario‘, to make it clear the purpose of the file.

We’ll need to create a test case, which extends org.jbehave.scenario.Scenario, and a ‘step’ class which extends  org.jbehave.scenario.steps.Steps. The step class will contain all the corresponding steps for the scenario file(s).

Scenario: Inserting initial coins

Given that the application is operating
 When a coin is inserted
 Then ensure the time indicator displays the purchased time

Create the test case class InsertingInitialCoins

package com.brass.bustix;

import org.jbehave.scenario.PropertyBasedConfiguration;
import org.jbehave.scenario.Scenario;
import org.jbehave.scenario.parser.ClasspathScenarioDefiner;
import org.jbehave.scenario.parser.PatternScenarioParser;
import org.jbehave.scenario.parser.ScenarioDefiner;
import org.jbehave.scenario.parser.UnderscoredCamelCaseResolver;

public class InsertingInitialCoins extends Scenario {
    public InsertingInitialCoins() {
        this(Thread.currentThread().getContextClassLoader());
    }

    public InsertingInitialCoins(final ClassLoader classLoader) {
        super(new PropertyBasedConfiguration() {
            public ScenarioDefiner forDefiningScenarios() {
                return new ClasspathScenarioDefiner(
                    new UnderscoredCamelCaseResolver(".scenario"),
                    new PatternScenarioParser(this), classLoader);
                }
            }, new BustixSteps());
        }
    }

The default constructor calls the custom scenario file loader, so that we can append ‘.scenario’ to your scenario files. If I were creating several of these test scenarios, I would pull all that up to an abstract class to avoid re-writing those constructors.

Steps class (empty for now):

package com.brass.bustix;

import static org.jbehave.Ensure.ensureThat;
import org.jbehave.scenario.annotations.Given;
import org.jbehave.scenario.annotations.Then;
import org.jbehave.scenario.annotations.When;
import org.jbehave.scenario.steps.Steps;

public class BustixSteps extends Steps {
    // TODO – fill in steps
}

Make sure that the .scenario file is copied to your compile directory! Otherwise you will get the error like:

org.jbehave.scenario.errors.ScenarioNotFoundException: Scenario com/brass/bustix/inserting_initial_coins.scenario could not be found by classloader sun.misc.Launcher$AppClassLoader@133056f

When you run the test for the first time, you will see this output:

Scenario: Inserting initial coins
Given that the application is operating (PENDING)
When a coin is inserted (PENDING)
Then ensure the time indicator displays the purchased time (PENDING)

Perhaps confusingly, the test has passed despite the steps not being executed – PENDING means that the step hasn’t been executed because no corresponding method has been found.

Defining Steps

JBehave uses annotations to mark a method as a particular step. The annotation is the first word of the step we’re using, with the rest of the text as a parameter to that annotation. So for our first step ‘Given that the application is operating’ we reformat it as an @Given annotation:

@Given(“that the application is operating”)

And then JBehave knows that the function following that annotation is what needs to be executed for that step.

So now in BustixSteps we add:

@Given(“that the application is operating”)
public void startApplication() {
    ensureThat(false);
}

The assertion statement ensures that the test will fail – an empty test will pass (as it usually does with JUnit). The ‘ensureThat’ function comes from JBehave’s wrapping of hamcrest matchers – an interesting alternative to Junit’s assert statements. I won’t go into them right now.

Rerunning the test, we get the failure we want!

Scenario: Inserting initial coins
Given that the application is operating (FAILED)
 When a coin is inserted (PENDING)
 Then ensure the time indicator displays the purchased time (PENDING)

java.lang.AssertionError:
Expected: is <true>
got: <false>
...

Lets fill in the rest of the steps for this scenario while we’re here:

package com.brass.bustix;

import static org.jbehave.Ensure.ensureThat;
import org.jbehave.scenario.annotations.Given;
import org.jbehave.scenario.annotations.Then;
import org.jbehave.scenario.annotations.When;
import org.jbehave.scenario.steps.Steps;

public class BustixSteps extends Steps {

    @Given("that the application is operating")
        public void startApplication() {
        ensureThat(false);
    }

    @When("a coin is inserted")
    public void insertCoin() {
        ensureThat(false);
    }

    @Then("ensure the time indicator displays the purchased time")
    public void testThatTimeIsDisplayed() {
        ensureThat(false);
    }
}

At this stage, we’ve got our steps outlined for our basic scenario, and we can begin the test-driven  development of the application!

Note: when you have a scenario step that begins with an ‘And’, it is still considered as one of given/when/then, e.g.

Given that the application is operating
  And the time indicator displays nothing

..are both @Given annotations.

Next: check out the hamcrest matchers to make sure you’re getting the full potential from your asserts!

References