Localizing JBehave Scenarios

I recently battled some more with the JBehave BDD framework, so I decided to write up some of my experiences to try and make it easier on anybody else trying that out. For this article to be useful you need to have a basic understanding of how JBehave works (scenarios, steps and all that).

What I wanted to do this time was to be able to write scenario text files in my native language, Swedish. Why? Well, if you want to really use JBehave (and similar tools) seriously then you most likely want to localize your scenarios. It’s all about communication. You want to try and eliminate all possible obstacles in your quest to really understanding the needs of your users and clients.

For my test project I selected the Mine Sweeper kata. I had already performed the kata, BDD style using JBehave, so I had some scenario files in English, corresponding scenario classes, and one step class. This sample project is used below to illustrate the process of translating the scenario files to another language and still be executable. If you start out fresh, without the English versions, then simply read “write” where I wrote “translate” below.

JBehave configuration

JBehave 2.4 (released in Jan 2010) has been internationalized so it should be quite easy to localize examples to any language. However, it’s really not as easy as it could be. Leading tools like Cucumber has support for different locales built in. For JBehave, at the moment, you have to do a bit more to get it working. Also, the documentation is a bit on the short side. It’s not bad, by any means, just brief. So if you’re reading it and feel a bit confused, you’re in good company.

Anyway, JBehave configuration is mainly programmatic and revolves around the Configuration interface. This interface contains eight methods used to control the behavior of JBehave. These are factory methods for JBehave strategies. Some control how the scenario files are found and parsed; others control how the reporter should work.

There are two default implementations of the interface in JBehave core: The rather aptly named MostUsefulConfiguration and the PropertyBasedConfiguration, which we will use below. By default, if you don’t configure anything on your own, the MostUsefulConfiguration is used. You may override this class to create your own adaptation. For example, if you wish to print out scenarios as they run, even if they pass, you must do some configuration. JBehave is mute about passing scenarios by default (using the beautifully named PassSilentlyDecorator). You can do this like this in a configuration class:

@Override
public ScenarioReporter forReportingScenarios() {
    return new PrintStreamScenarioReporter();
}

This redefines the scenario reporter to use an undecorated PrintStreamScenarioReporter. In the case in point we want to use resource bundles to localize scenario files. We’ll use the PropertyBasedConfiguration variant since we’ll want to use property files as configuration. By default, it has the same behaviour as the MostUsefulConfiguration.

Basically, here’s what we need to do in brief: Create a resource file for our language. Then, create a property-based configuration that uses that resource file. Then, configure JBehave to use that configuration everywhere in a nice way.

Ready? Let’s go.

Step-by-step localization of JBehave scenarios

Here’s a proposed method to localize JBehave scenarios:

  1. Create resource bundle file for your local keywords
  2. Create a JBehave configuration using these keywords
  3. Configure one scenario class with your new configuration
  4. Configure the relevant steps class
  5. Get the scenario working
  6. Refactor to remove infrastructure code
  7. Spread the solution to all scenario classes
  8. Translate the rest of the scenario files and corresponding steps

1. Create keyword resource bundle

Create a file with the correct suffix for your locale and extension “.properties”. Example (keywords in Swedish as spoken in Sweden):

keywords_sv_SE.properties

Put the file on the class path. I put mine in the

$PROJECT_HOME/test/se/adaptiv/minesweeper/scenario

catalog, i.e. the same catalog where I put my scenario files.

In the file, translate all keywords to your language. Here is my file:

Scenario=Scenario:
GivenScenarios=GivetScenarier:
ExamplesTable=Exempeltabell:
ExamplesTableRow=Exempelrad:
Given=Givet
When=När
Then=Så
And=Och
Pending=PÅ GÅNG
NotPerformed=KÖRDES INTE
Failed=MISSLYCKADES

2. Create a new JBehave configuration

Now you have to configure JBehave to use your resource bundle keywords. To do this, create a class that extends PropertyBasedConfiguration:

private static class MineSweeperConfiguration
        extends PropertyBasedConfiguration

I created my configuration class as a private inner class in one of my scenario classes, but of course it could be in a file of its own as well.

The constructor of the configuration class should accept a class loader. This you will send in from your scenarios.

Override these configuration methods: forDefiningScenarios(), forReportingScenarios(), and keywords() to use your new keywords. The first method, shown below, configures the use of camel-case scenario files with extension “.scenario” (default configuration does not have an extension) and parsing with Swedish keywords:

@Override
public ScenarioDefiner forDefiningScenarios() {
    return new ClasspathScenarioDefiner(
            new UnderscoredCamelCaseResolver(".scenario"),
            new PatternScenarioParser(keywords), classLoader);
}

The following configuration method configures output of all scenarios (including passing ones) and the use of Swedish keywords for reporting scenarios:

@Override
public ScenarioReporter forReportingScenarios() {
    return new PrintStreamScenarioReporter(keywords);
}

Finally, this factory method configures the use of Swedish keywords for steps in scenario files.

@Override
public KeyWords keywords() {
    return keywords;
}

All of them use the field “keywords”, which I creates in the constructor like so:

this.keywords = new I18nKeyWords(
        new Locale("sv", "SE"),
        new StringEncoder("ISO-8859-1", "ISO-8859-1"),
        "se/adaptiv/minesweeper/scenario/keywords",
        classLoader);

The class I18nKeyWords is used for localization of keywords. Give the constructor the locale you are using, a string encoder for your character encoding(if your not using the recommended UTF-8), the path to your keywords resource file and the class loader sent in.

3. Use the configuration in one scenario

Typically, the scenario classes of JBehave consist of a constructor calling “super(CandidateStep…)” in the constructor. There is an alternate constructor in the super class (typically “Scenario”), which we’ll use, that takes the configuration as the first argument. Example:

super(new MineSweeperConfiguration(classLoader), candidateSteps);

But where does the class loader come from? Well, if you run your scenarios from Ant or Maven it will be injected into your scenario classes. You can simple create a contructor of your scenario class taking the class loader as its argument. Otherwise, you can use the $CLASS.class.getClassLoader() to get your class loader, e.g.:

PrintingMineFieldsWithHints.class.getClassLoader()

This will do fine if you only want to run your scenarios via JUnit, e.g. in Eclipse.

4. Configure the Steps class

Your scenario class makes use of one (or more) steps classes, typically sub-classes to JBehave’s “Steps”, and they need to be matched to lines in your scenario files. To make the steps class understand your keywords, we also need to tell it to use the correct keywords. Therefore, create a constructor in your steps class, taking the class loader as its only argument, and call super with a StepsConfiguration taking the Keyword instance and the class loader as arguments, like so:

public MineFieldSteps(ClassLoader classLoader) {
    super(new StepsConfiguration(
            keywordsFor(new Locale("sv", "SE"), classLoader)));
}

Here I used a small static helper method (keywordsFor) to get the Swedish keywords the same way I did in the configuration class. Note: This is an annoying duplication which I would be happy to get rid of.

5. Get your modified scenario working

Now we have done enough work to perform a test. Translate the scenario file matching your modified scenario class. Only translate the keywords. Example: “Given” becomes “Givet” in Swedish.

Now run the scenario. I ran mine using the JUnit runner embedded in Eclipse. You should see the scenario copied to the Console (System.out) with keywords in your language. If you run into trouble, make sure the corresponding scenario is configured to use your new configuration. Also, make sure the corresponding steps classes are configured.

6. Remove infrastructure code

Instead of copying the same inner class and constructors to all scenario classes, which would be ugly, extract a base scenario class for all the project scenarios to use. Example:

public abstract class MineSweeperBaseScenario extends Scenario

It should contain a constructor and the private inner configuration class we have seen before. Here is the constructor:

public MineSweeperBaseScenario(
        ClassLoader classLoader, CandidateSteps... candidateSteps) {
    super(new MineSweeperConfiguration(classLoader), candidateSteps);
}

After this, change your scenario class to inherit from the base class and call the new constructor. In my case it looks like this:

public class PrintingMineFieldsWithHints extends MineSweeperBaseScenario {
    public PrintingMineFieldsWithHints() {
        super(PrintingMineFieldsWithHints.class.getClassLoader(),
        new MineFieldSteps(
                PrintingMineFieldsWithHints.class.getClassLoader()));
    }
}

Test again to make sure the scenario still works.

7. Now we can spread this solution to all scenarios.

Simply change them to extend your new base class and change the call to the super constructor. Of course, you also need to change the corresponding keywords in all the scenario text files. Run all the scenarios to make sure everything works.

8. Translate existing steps

Now it’s an open goal. We are in a position to translate the rest of the text scenarios into Swedish (which is welcome since at this point they all look silly with only the starting keywords translated). We couldn’t do this earlier since that would make all the other scenarios fail. For example, “Givet the following mine field definitions: $input” becomes “Givet följande definitioner av minfält: $input”. Of course, I also need to change the matching regular expressions in the steps class(es).

Run all the scenarios to make sure everything works. You should see all scenarios printed to the console in your own language. Now test it a little bit. For example, try changing one of the scenarios so that the setup (@Given) step fails. Run it. Make sure you see the localized versions of the “Pending” and “NotPerformed” messages.

Voila! You can now collaborate with your customer on executable acceptance criteria using your native language. Try it! It’s invigorating.

Source code

The source code of my Mine Sweeper kata with Swedish scenarios can be found here. In this version I collected all scenarios into one file and changed the extension to “.story” instead. My thinking was that these scenarios are all part of the same story, i.e. calculating hints, so they should reside in the same story file. So, many scenarios, one story. Hence, there is really no need for a special scenario base class but I kept it in for pedagogical purposes. Most projects are not this simple. Finally, I used constants for values that were shown in-line in the blog post.