Behavioral Driven Design (BDD) with jBehave

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. TDD 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, although 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 in response to 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 a 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 the 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, and 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 system's users. However, that is not a reason for doing it. 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 – greatly enhances the ability to automate tests. While there will always be testing that cannot be automated, such as exploratory testing, testing using a BDD model increases productivity, reduces risk, and increases the 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 automated teller machine (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 one story with only three scenarios.


Story

As an account holder, I want to withdraw cash from my account when the bank is closed, so I use the ATM.

Narrative

In order to get money when the bank is closed
As an account holder
Want to withdraw cash from an ATM

Scenario 1

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 2

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 3

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 match 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



Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.


secret