SETT
SETT

Behavioral Driven Design (BDD) with jBehave

by Terry Mork, Ph.D.

May 2015

Introduction

Test-driven development (TDD) is a software development principle and practice, a way of developing valuable software. Test-driven development is closely tied to the test-first programming method of eXtreme Programming (XP). Kent Beck rediscovered and popularized this practice in his book, "Test Driven Development by Example." (1)

Most people agree that TDD is very valuable in software development, though problems still exist. Developers need to know where to start, what to test and what not to test, how much to test, what to call their tests, and how to understand why a test fails.

So let's take TDD one step further with behavioral driven-development (BDD). While TDD focuses on the developer's point of view, on how the feature should work, BDD focuses on the user's perspective, on how the feature should behave. Thus, BDD is an evolution of TDD.

What is BDD?

Behavioral-driven development was introduced by Dan North after issues he continually came across in test-driven development. (2) North suggested that, instead of simply writing tests, developers should think of specifying behaviors: how the features should behave. In BDD you should always start with the features that are most important to the users. Through collaboration and continual feedback, the practice of knowing what is most important becomes more clear.

BDD focuses on how a desired behavior should be specified, implying that the desired behavior has business value. Thus, it is crucial to specify this business value, which has now become the standard for documenting user requirements (stories):

As a (role)...
I want (activity)...
So that (business value)...

Application of BDD

In the past, our focus has been on the users, for whom the feature was developed, and the function that was performed. However, a critical piece was missing: why does this user want to perform this function. If we do not know why, then perhaps the user does not really require this feature.

In 2003, Dan North began emphasizing behaviors over testing, and he started writing a replacement for jUnit, called jBehave. The jBehave framework removes any reference to testing and replaces it with a vocabulary built around verifying behaviors. There are 5 simple steps in this framework, including:

  1. Write a user story
  2. Map the steps to Java
  3. Configure the users stories
  4. Run user stories in tools such as Ant, jUnit, eclipse, maven, IntellijIDEA
  5. View the output

This framework expresses various behavioral scenarios, called acceptance criteria, in the following format:

Given is the state of the application before the test, which may be multiple statements about this state. When represents the programmatic action under test, or changes to the application. Then references the state after the test. The scenarios are written in business terms, with no reference to the UI through which the actions occur. The scenarios should cover positive (happy path), negative, and edge cases. This format is referred to as the Gherkin language, which has a syntax similar to the above example. The term Gherkin is specific to the Cucumber and jBehave software tools. (3)

Why use BDD?

So, why do this at all? As previously mentioned, BDD shifts the thinking from the developer's point of view to that of the users of the system. However, that is not a reason for doing so. I will answer that question, initially, with another question. What enables us to be agile, to deliver software in small increments, to continually incorporate feedback, to refactor with every increment?

Automated tests that run on demand = agility
Expressing test scenarios in the above-mentioned format, and within the construct of BDD, will greatly increase the ability to automate tests. While there will always be testing that cannot be automated, such as exploratory testing, it will increase productivity, reduce risk, and increase overall quality of the software being developed.

Practical Usage

The example I will use is a simple concept for everyone to understand: the use of an ATM. I want to get money from the bank when it is closed. There are many user stories that can be written in this example, but I will focus on 1 story with only 3 scenarios.

Story: As an account holder, I want to withdraw cash from the ATM so that I can get money when the bank is closed.

Narrative:
In order to get money when the bank is closed
As an account holder
Want to withdraw cash from an ATM
Scenario: Account has sufficient funds
Given the account balance is 100
and the card is valid
and the machine contains enough money
When the account holder requests 20
Then the ATM should dispense 20
and the account balance should be 80
and the card should be returned
Scenario: Account has insufficient funds
Given the account balance is 10
and the card is valid
and the machine contains enough money
When the account holder requests 20
Then the ATM should not dispense any money
and the ATM should say there are insufficient funds
and the account balance should be 10
and the card should be returned
Scenario: Card has been disabled
Given the card is disabled
When the account holder requests 20
Then the ATM should retain the card


As you can see, this story and the scenarios are written in simple language, not a programming language. Thus, anyone can write this.

Each step in the scenarios is bound to the Java code responsible for implementing it. In this example:

  1. package xyz.stories;
  2.  
  3. import java.math.BigDecimal;
  4.  
  5. import junit.framework.Assert;
  6.  
  7. import org.jbehave.core.annotations.BeforeScenario;
  8. import org.jbehave.core.annotations.Given;
  9. import org.jbehave.core.annotations.Named;
  10. import org.jbehave.core.annotations.Then;
  11. import org.jbehave.core.annotations.When;
  12. import org.springframework.stereotype.Component;
  13.  
  14. import xyz.entities.domain.ATM;
  15. import xyz.entities.domain.Account;
  16. import xyz.entities.domain.Card;
  17. import xyz.lang.CardRetainedException;
  18. import xyz.lang.InsufficientFundsException;
  19.  
  20. /**
  21.  * Defines cash withdrawing steps.
  22.  *
  23.  * @author xyz
  24.  */
  25. @Component
  26. public class CashWithdrawingSteps {
  27.  
  28.     private Account account;
  29.     private ATM atm;
  30.     private Card card;
  31.     private BigDecimal dispense;
  32.     private Throwable throwable;
  33.  
  34.     /**
  35.      * Callback method triggered before each scenario.
  36.      */
  37.     @BeforeScenario
  38.     public void beforeScenario() {
  39.         this.card = null;
  40.         this.account = null;
  41.         this.atm = null;
  42.         this.dispense = null;
  43.         this.throwable = null;
  44.     }
  45.  
  46.     @Given("the card is disabled")
  47.     public void givenCardIsDisabled() {
  48.         card = new Card();
  49.         card.setValid(false);
  50.  
  51.         atm = new ATM();
  52.     }
  53.     
  54. @Then("the ATM should retain the card")
  55.     public void thenATMShouldRetainCard() {
  56.         Assert.assertNull(dispense);
  57.         Assert.assertTrue(throwable instanceof CardRetainedException);
  58.     }
  59.  
  60.     /**
  61.      * @param amount
  62.      *            the amount of requested money
  63.      */
  64.     @When("the account holder requests $amount")
  65.     public void whenAccountHolderRequestsMoney(@Named("amount") BigDecimal amount) {
  66.         try {
  67.             dispense = atm.withdraw(card, amount);
  68.         } catch (CardRetainedException exception) {
  69.             throwable = exception;
  70.         } catch (InsufficientFundsException exception) {
  71.             throwable = exception;
  72.         }
  73.     }
  74.  
  75. }

When you look at the @Given, @When, and @Then annotations, you'll see that their values are matching the content of scenario steps written in simple language. This is the point where simple human language, written by your Business Analyst or Product Owner, meets the developers.

At this point, you can use the Spring Framework to hold the steps.

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  7. http://www.springframework.org/schema/context
  8. http://www.springframework.org/schema/context/spring-context-3.0.xsd">
  9.  
  10. <context:component-scan base-package="xyz" />
  11.  
  12. <bean class="org.jbehave.core.configuration.spring.SpringStoryReporterBuilder"
  13.  
  14. init-method="withDefaultFormats">
  15. <property name="formats">
  16. <list>
  17. <value>HTML</value>
  18. </list>
  19. </property>
  20. </bean>
  21.  
  22. </beans>

Then you will need code to run the story processing as a jUnit test.

  1. package xyz.entities.domain.test;
  2.  
  3. import java.util.List;
  4.  
  5. import org.jbehave.core.annotations.Configure;
  6. import org.jbehave.core.annotations.UsingEmbedder;
  7. import org.jbehave.core.annotations.UsingSteps;
  8. import org.jbehave.core.annotations.spring.UsingSpring;
  9. import org.jbehave.core.embedder.Embedder;
  10. import org.jbehave.core.io.CodeLocations;
  11. import org.jbehave.core.io.StoryFinder;
  12. import org.jbehave.core.junit.JUnitStories;
  13. import org.jbehave.core.junit.spring.SpringAnnotatedEmbedderRunner;
  14. import org.junit.runner.RunWith;
  15.  
  16. /**
  17.  * JUnit entry point to run stories.
  18.  *
  19.  * @author xyz
  20.  */
  21. @RunWith(SpringAnnotatedEmbedderRunner.class)
  22. @Configure
  23. @UsingEmbedder(embedder = Embedder.class, generateViewAfterStories = true,
  24. ignoreFailureInStories = true, ignoreFailureInView = false, stepsFactory = true)
  25. @UsingSpring(resources = "classpath:xyz/config.xml")
  26. @UsingSteps
  27. public class AccountStories extends JUnitStories {
  28.  
  29. protected List<String> storyPaths() {
  30. return new
  31. StoryFinder().findPaths(CodeLocations.codeLocationFromPath("src/test/resources"),
  32. "xyz/stories/*.story", "");
  33. }
  34.  
  35. }

When this test is run, all the scenarios will be verified, and along with the jUnit test results, a jBehave report will be produced:

jBehave Report

References

 

The Software Engineering Tech Trends is a monthly newsletter featuring emerging trends in software engineering.

Subscribe

© Copyright Object Computing, Inc. 1993, 2016. All rights reserved

WebSanity Top Secret