I started with Introducing BDD and What's in a Story? articles by Dan North, and Alex Soto blog entry about Spring Framework, JBehave and Selenium 2 integration.
Then I asked myself: "What I need for the BDD experiment? - The Story :) - I borrowed one from Dan's article What's in a Story?. Suppose that we have an Account, Card bound to this account, and ATM which will dispense some money. In order to get money when the bank is closed, as an account holder I want to withdraw cash from an ATM - this is the story :) - This story will have 3 possible scenarios in my example:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Narrative: | |
In order to get money when the bank is closed | |
As a Account Holder | |
I 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 see, this story and the scenarios included are written in human language :) - no programming here, it can be written by anyone! OK, but where is the development here?! - Give me few minutes, and I'll explain that :)
Each Scenario consist of 3 steps: Given, When, Then. First one (Given) defines the context of the specific scenario (ex. "the card is disabled"), second (When) defines the event which occurred in this specific context (ex. "the account holder requests 20"), and third one (Then) defines the state of the context after the event processing (ex. "the ATM should retain the card").
Each scenario's step is bound to the Java code responsible for "implementing" it, in our example:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package warlock.stories; | |
import java.math.BigDecimal; | |
import junit.framework.Assert; | |
import org.jbehave.core.annotations.BeforeScenario; | |
import org.jbehave.core.annotations.Given; | |
import org.jbehave.core.annotations.Named; | |
import org.jbehave.core.annotations.Then; | |
import org.jbehave.core.annotations.When; | |
import org.springframework.stereotype.Component; | |
import warlock.entities.domain.ATM; | |
import warlock.entities.domain.Account; | |
import warlock.entities.domain.Card; | |
import warlock.lang.CardRetainedException; | |
import warlock.lang.InsufficientFundsException; | |
/** | |
* Defines cash withdrawing steps. | |
* | |
* @author warlock | |
*/ | |
@Component | |
public class CashWithdrawingSteps { | |
private Account account; | |
private ATM atm; | |
private Card card; | |
private BigDecimal dispense; | |
private Throwable throwable; | |
/** | |
* Callback method triggered before each scenario. | |
*/ | |
@BeforeScenario | |
public void beforeScenario() { | |
this.card = null; | |
this.account = null; | |
this.atm = null; | |
this.dispense = null; | |
this.throwable = null; | |
} | |
@Given("the card is disabled") | |
public void givenCardIsDisabled() { | |
card = new Card(); | |
card.setValid(false); | |
atm = new ATM(); | |
} | |
@Then("the ATM should retain the card") | |
public void thenATMShouldRetainCard() { | |
Assert.assertNull(dispense); | |
Assert.assertTrue(throwable instanceof CardRetainedException); | |
} | |
/** | |
* @param amount | |
* the amount of requested money | |
*/ | |
@When("the account holder requests $amount") | |
public void whenAccountHolderRequestsMoney(@Named("amount") BigDecimal amount) { | |
try { | |
dispense = atm.withdraw(card, amount); | |
} catch (CardRetainedException exception) { | |
throwable = exception; | |
} catch (InsufficientFundsException exception) { | |
throwable = exception; | |
} | |
} | |
} |
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 human language - and this is the point where human language used by your Business Development Team meets the "Real Developers" ;)
Now what? - the rest is easy :) - Alex Soto proposed in his article (Spring Framework, JBehave and Selenium 2 integration) that we can use the Spring Framework driven Components for holding the Steps, this is very nice idea, and sufficient in my example (I would worry about it only in multi-threaded environment, because Component build by Spring is a Singleton by default, and that's usually not a good idea in such environment).
Spring Framework configuration:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<beans xmlns="http://www.springframework.org/schema/beans" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xmlns:context="http://www.springframework.org/schema/context" | |
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd | |
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> | |
<context:component-scan base-package="warlock" /> | |
<bean class="org.jbehave.core.configuration.spring.SpringStoryReporterBuilder" init-method="withDefaultFormats"> | |
<property name="formats"> | |
<list> | |
<value>HTML</value> | |
</list> | |
</property> | |
</bean> | |
</beans> |
We perform the component scan here (which will register bean for our Steps holding class), and define only one bean related to JBehave, configuring its report builder.
Now we need a code which will allow us to run the Story processing as JUnit test:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package warlock.entities.domain.test; | |
import java.util.List; | |
import org.jbehave.core.annotations.Configure; | |
import org.jbehave.core.annotations.UsingEmbedder; | |
import org.jbehave.core.annotations.UsingSteps; | |
import org.jbehave.core.annotations.spring.UsingSpring; | |
import org.jbehave.core.embedder.Embedder; | |
import org.jbehave.core.io.CodeLocations; | |
import org.jbehave.core.io.StoryFinder; | |
import org.jbehave.core.junit.JUnitStories; | |
import org.jbehave.core.junit.spring.SpringAnnotatedEmbedderRunner; | |
import org.junit.runner.RunWith; | |
/** | |
* JUnit entry point to run stories. | |
* | |
* @author warlock | |
*/ | |
@RunWith(SpringAnnotatedEmbedderRunner.class) | |
@Configure | |
@UsingEmbedder(embedder = Embedder.class, generateViewAfterStories = true, ignoreFailureInStories = true, ignoreFailureInView = false, stepsFactory = true) | |
@UsingSpring(resources = "classpath:warlock/config.xml") | |
@UsingSteps | |
public class AccountStories extends JUnitStories { | |
protected List<String> storyPaths() { | |
return new StoryFinder().findPaths(CodeLocations.codeLocationFromPath("src/test/resources"), | |
"warlock/stories/*.story", ""); | |
} | |
} |
When you run this test, all scenarios will be verified, and along with the JUnit test result, JBehave report will be produced:
Few links for the dessert:
- Source code for this experiment - https://github.com/vardlokkur/bdd-01
- Behavior Driven Development
- Dan North - Introducing BDD
- Dan North - What's in a Story?
- Alex Soto - Spring Framework, JBehave and Selenium 2 integration
- JBehave