Sunday, June 2, 2013

jBehave Tutorial: BDD Framework for Verifying User Stories

Overview:
This article looks at jBehave, a popular BDD framework for Java.

There are many ways in which you could configure jBehave. Here, we look at the minimal set of configuration required to get the framework integrated with your BDD environment.

Sample code can be found here.

Project Structure:

org.fazlan.jbehave.exmaple
├── pom.xml
└── src
    ├── main
    │   └── java
    │       └── org
    │           └── fazlan
    │               └── jbehave
    │                   └── exmaple
    │                       └── Calculator.java
    └── test
        ├── java
        │   └── org
        │       └── fazlan
        │           └── jbehave
        │               └── exmaple
        │                   ├── steps
        │                   │   └── AddTwoNumbersSteps.java
        │                   └── stories
        │                       └── CalculatorStories.java
        └── resources
            └── org
                └── fazlan
                    └── jbehave
                        └── exmaple
                            └── stories
                                └── calculator_stories.story

Step 1: Creating the Project

 mvn archetype:generate -DartifactId=org.fazlan.jbehave.exmaple -DgroupId=org.fazlan -Dversion=1.0-SNAPSHOT -DinteractiveMode=false  

Step 2: Updating the Maven dependencies
Update (2013-10-7) : Updated to include maven-jbehave-plugin.

NOTE: This assumes that you run only acceptance tests at maven integration-test phase, any unit tests will be skipped as per the pom.xml configuration.

 <?xml version="1.0" encoding="UTF-8"?>  
 <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/xsd/maven-4.0.0.xsd">  
   <modelVersion>4.0.0</modelVersion>  
   <groupId>org.fazlan</groupId>  
   <artifactId>org.fazlan.jbehave.exmaple</artifactId>  
   <version>1.0-SNAPSHOT</version>  
   <properties>  
     <jbehave.version>3.8</jbehave.version>  
     <junit.version>4.11</junit.version>  
     <failsafe.and.surefire.version>2.16</failsafe.and.surefire.version>  
   </properties>  
   <dependencies>  
     <dependency>  
       <groupId>junit</groupId>  
       <artifactId>junit</artifactId>  
       <version>${junit.version}</version>  
     </dependency>  
     <dependency>  
       <groupId>org.jbehave</groupId>  
       <artifactId>jbehave-core</artifactId>  
       <version>${jbehave.version}</version>  
     </dependency>  
   </dependencies>  
   <build>  
     <plugins>  
       <plugin>  
         <groupId>org.apache.maven.plugins</groupId>  
         <artifactId>maven-surefire-plugin</artifactId>  
         <version>${failsafe.and.surefire.version}</version>  
         <configuration>  
           <!-- Disable unit tests -->  
           <skip>true</skip>  
         </configuration>  
       </plugin>  
       <plugin>  
         <groupId>org.apache.maven.plugins</groupId>  
         <artifactId>maven-failsafe-plugin</artifactId>  
         <version>${failsafe.and.surefire.version}</version>  
         <executions>  
           <execution>  
             <id>integration-test</id>  
             <goals>  
               <goal>integration-test</goal>  
               <goal>verify</goal>  
             </goals>  
           </execution>  
         </executions>  
         <configuration>  
           <includes>  
             <include>**/*Stories.java</include>  
           </includes>  
         </configuration>  
       </plugin>  
       <plugin>  
         <groupId>org.jbehave</groupId>  
         <artifactId>jbehave-maven-plugin</artifactId>  
         <version>${jbehave.version}</version>  
         <executions>  
           <execution>  
             <id>run-stories-as-embeddables</id>  
             <phase>integration-test</phase>  
             <configuration>  
               <testSourceDirectory>${basedir}/src/test/java</testSourceDirectory>  
               <includes>  
                 <include>**/*Stories.java</include>  
               </includes>  
               <ignoreFailureInStories>false</ignoreFailureInStories>  
               <ignoreFailureInView>false</ignoreFailureInView>  
             </configuration>  
             <goals>  
               <goal>run-stories-as-embeddables</goal>  
             </goals>  
           </execution>  
         </executions>  
       </plugin>  
     </plugins>  
   </build>  
 </project>  

Step 3: Defining the User story

A User story describes the behaviour of a piece of software and the expected outcome (acceptance criteria) for a given set of preconditions.

The following contains a simple user story for adding two numbers, which is defined in org/fazlan/jbehave/exmaple/stories/calculator_stories.story

 Narrative:  
 In order to quickly find out the sum of two numbers  
 As a user  
 I want to use a calculator to add two numbers
  
 Scenario: Add two valid numbers
  
 Given a calculator  
 When I add <number1> and <number2>  
 Then the outcome should <result>
  
 Examples:  
 |number1|number2|result|  
 |10|10|20|  

Step 4: Writing the test to verify the story

The following is the JUnit test for verifying and testing our add two numbers story.

 package org.fazlan.jbehave.exmaple.stories; 
 
 import org.fazlan.jbehave.exmaple.steps.AddTwoNumbersSteps; 
 
 import org.jbehave.core.configuration.Configuration;  
 import org.jbehave.core.junit.JUnitStory;  
 import org.jbehave.core.reporters.StoryReporterBuilder;  
 import org.jbehave.core.steps.InjectableStepsFactory;  
 import org.jbehave.core.steps.InstanceStepsFactory;  

 import static org.jbehave.core.reporters.Format.CONSOLE;  
 import static org.jbehave.core.reporters.Format.TXT;
  
 public class CalculatorStories extends JUnitStory {
  
   @Override  
   public Configuration configuration() {  
     return super.configuration()  
         .useStoryReporterBuilder(  
             new StoryReporterBuilder()  
                 .withDefaultFormats()  
                 .withFormats(CONSOLE, TXT));  
   }  

   // Here we specify the steps classes  
   @Override  
   public InjectableStepsFactory stepsFactory() {  
     return new InstanceStepsFactory(configuration(), new AddTwoNumbersSteps());  
   }  
 }  


IMPORTANT NOTE: By default, jBehave framework will look for *.story files defined in the same package as the corresponding *Stories.java file.

e.i: org.fazlan.jbehave.exmaple.stories.CalculatorStories.java will look for a *.story file called org/fazlan/jbehave/exmaple/stories/calculator_stories.story

Assume, you have defined your story in a file called 'CalculatorStory.story' then you can change the default behaviour of discovering stories by overriding the List<String> storyPaths() method in the following way,

 package org.fazlan.jbehave.exmaple.stories;  

 import org.fazlan.jbehave.exmaple.steps.AddTwoNumbersSteps;  

 import org.jbehave.core.configuration.Configuration;  
 import org.jbehave.core.junit.JUnitStory;  
 import org.jbehave.core.reporters.StoryReporterBuilder;  
 import org.jbehave.core.steps.InjectableStepsFactory;  
 import org.jbehave.core.steps.InstanceStepsFactory;  

 import static org.jbehave.core.reporters.Format.CONSOLE;  
 import static org.jbehave.core.reporters.Format.TXT;  

 public class CalculatorStories extends JUnitStory {  

   @Override  
   public Configuration configuration() {  
     return super.configuration()  
         .useStoryReporterBuilder(  
             new StoryReporterBuilder()  
                 .withDefaultFormats()  
                 .withFormats(CONSOLE, TXT));  
   }  

   // Here we specify the steps classes  
   @Override  
   public InjectableStepsFactory stepsFactory() {  
     return new InstanceStepsFactory(configuration(), new AddTwoNumbersSteps());  
   }  

   @Override  
   protected List<String> storyPaths() {  
     return new StoryFinder().findPaths(org.jbehave.core.io.CodeLocations.codeLocationFromPath("src/test/resources"), "**/CalculatorStory.story", "");  
   }  
 }  

Step 5: Writing the jBehave Step that verifies your story
There are multiple ways to write jBehave Steps, but in this article we focus on keeping this simple and write our Steps as POJOs.

 package org.fazlan.jbehave.exmaple.steps;  

 import org.fazlan.jbehave.exmaple.Calculator;  

 import org.jbehave.core.annotations.Given;  
 import org.jbehave.core.annotations.Named;  
 import org.jbehave.core.annotations.Then;  
 import org.jbehave.core.annotations.When;  

 public class AddTwoNumbersSteps {  

   private Calculator calculator;  

   @Given("a calculator")  
   public void givenACalculator() {  
     calculator = new Calculator();  
   }  

   @When("I add <number1> and <number2>")  
   public void whenIAddNumber1AndNumber2(@Named("number1")int number1, @Named("number2")int number2) {  
     calculator.add(number1, number2);  
   }  

   @Then("the outcome should <result>")  
   public void thenTheOutcomeShould(@Named("result")int result) {  
     assert calculator.getResult() == result;  
   }  
 }  

Step 6: Implementing the Calculator.java to meet our User story acceptance criteria
Following is a simple implementation of a Calculator that has a method called 'add' to add two numbers.

 package org.fazlan.jbehave.exmaple;
  
 public class Calculator {  

   private int result;  

   public void add(int number1, int number2) {  
     result = number1 + number2;  
   }  

   public int getResult() {  
     return result;  
   }  
 }  


Step 7: Running your tests
You can run the acceptance tests issuing the following Maven command.

mvn clean integration-test

Summary:
This was a brief article on how to integrate jBehave with Maven for BDD with your application.

Sample code can be found here.

15 comments:

  1. Thats an awesome example with all the steps and tips mentioned. However, I was getting this in console, any ideas or pointers to fix this?
    "Reports view generated with 0 stories (of which 0 pending) containing 0 scenarios (of which 0 pending)"

    ReplyDelete
    Replies
    1. Hi there,

      I reckon your story (e.i: CalculatorStories) is unable to find the *.story in the classpath.

      As I have mentioned,

      IMPORTANT NOTE: By default, jBehave framework will look for *.story files defined in the same package as the corresponding *Stories.java file.

      e.i: org.fazlan.jbehave.exmaple.stories.CalculatorStories.java will look for a *.story file called org/fazlan/jbehave/exmaple/stories/calculator_stories.story

      If your story is named foo.bar.MySampleStory, by default, it'll look for a *.story file called 'foo/bar/my_sample_story.story' where foo.bar is the package structure. However, you can override it.

      According to the article, I have placed them under 'src/test/resources'. Make a note of CalculatorStories.storyPaths() - this tells the jBehave framework where to find the *.story files. If you have placed them under 'src/main/resources', you have to update it accordingly.

      Moreover, please make sure that your story extends JUnitStory, instead of JUnitStories, and ensure the following,

      @Override
      public Configuration configuration() {
      return super.configuration()
      .useStoryReporterBuilder(
      new StoryReporterBuilder()
      .withDefaultFormats()
      .withFormats(CONSOLE, TXT));
      }

      You need the StoryReporterBuilder to find and report on your stories.

      Cheers,

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Hi Fazlan,

    could you please let me know how to run this example.

    Thanks,
    Fouad

    ReplyDelete
    Replies
    1. Hi,

      Sorry., this article assumes you have IntelliJ as you IDE and you're able to import the attached source code to the IDE, and then execute as jBehave tests.

      Please await, I'll update the pom.xml file, so that you can easily run it using Maven.

      Cheers,
      Fazlan

      Delete
  4. I was looking for a step by step guide for JBehave for a beginner. I found this article and its excellent!!! Thank you for sharing this.

    ReplyDelete
  5. After changing some code around to get things to compile, I don't see how to get this to run. Seems like a key thing to include. :) I went to the JBehave page and tried to find this out for myself but it offered very little help. The main thing is I need to be able to run stuff like this from the command line for continuous integration.

    ReplyDelete
    Replies
    1. Yes, I'll update the post to use the maven-jbehave plugin. However, meanwhile, you can refer to http://jbehave.org/reference/stable/maven-goals.html

      Delete
    2. Hi Jeff,

      I have updated the post to run the tests using Maven.

      Cheers,
      Fazlan

      Delete
  6. Fazlan, do you have this example without maven?

    ReplyDelete
  7. Fazlan,

    I see "Reports view generated with 1 stories (of which 1 pending) containing 1 scenarios (of which 1 pending)" message in console instead of running my scenario. Not sure whats the issue.

    -Praveen

    ReplyDelete
    Replies
    1. Check whether the "text" in your @Given("text")/@And("text")/@When("text")/@Then("text") matches the GIven/When/Then statements in your story. Even the slightest of mismatch in text can cause this.

      Delete
  8. Hi Fazlan,

    Thanks for sharing this code, this is very useful to start with JBehave. Could you please give some idea on BDD reporting..as I am looking into "thucydides reporting" but no luck.

    -Gopi

    ReplyDelete
  9. Best blog on setting up Jbehave from scratch.

    ReplyDelete
  10. Best blog on setting Jbehave from scratch

    ReplyDelete