behavior-driven development with jgiven
TRANSCRIPT
Behavior drivendevelopment with JGivenAchim Wiedemann | 24.10.2017
24.10.2017 Achim WiedemannSeite 2
Who am I?
Achim Wiedemann
Software developer
Joined Haufe-Lexware in April 2015
Working on lexoffice
24.10.2017 Achim WiedemannSeite 3
Sorry, but…
No Cloud
No Microservices
No Docker
No Big Data
24.10.2017 Achim WiedemannSeite 4
Agenda
Part I – Introduction to BDD
Part II – Traditional BDD tools
Part III – BDD with JGiven
24.10.2017 Achim WiedemannSeite 5
Part I – Introduction to BDD
24.10.2017 Achim WiedemannSeite 6
What is Behavior Driven Development?
Method of writing software tests
• Started around 2004 by Dan North
• TDD for business functionality
Goals for tests:
• Focus on business value
• Make writing tests simple
• Use domain specific language
• Provide value for devs and business people
• Make acceptance criteria executable
24.10.2017 Achim WiedemannSeite 7
BDD - where does it fit in?
Unit tests:
• Focus on technical components
• Traditional driver of TDD
Acceptance tests:
• Focus on business components
• Driver of BDD
• Usually UI or API tests
http://martinfowler.com/bliki/TestPyramid.html
24.10.2017 Achim WiedemannSeite 8
Acceptance criteria
States requirements
Needs to be met for implementation to be accepted
Can be written in the form:
As a [role]
I want [feature]
so that [benefit]
Example:
As a customer,
I want to withdraw cash from an ATM,
so that I don’t have to wait in line at the bank.
Involved roles User intent Business value
24.10.2017 Achim WiedemannSeite 9
Acceptance criteria – 2
Writing acceptance criteria can be hard
Don‘t fall into this trap:
As a user,
I want to withdraw cash from an ATM,
so that I can withdraw cash from an ATM
Simpler to write, but:
• Doesn‘t show the involved roles
• Doesn‘t show the business value for the user
24.10.2017 Achim WiedemannSeite 10
Validating acceptance criteria with scenarios
Specify tests against acceptance criteria
Scenarios can be derived from acceptance criteria:
As a customer,
I want to withdraw cash from an ATM,
so that I don’t have to wait in line at the bank.
Derived scenario „Account is in credit“:
Given the account is in credit
And the card is valid
And the dispenser contains cash
When the customer requests cash
Then ensure the account is debited
And ensure cash is dispensed
And ensure the card is returned
24.10.2017 Achim WiedemannSeite 11
Validating acceptance criteria with scenarios - 2
Use Given-When-Then form:
• Given [context]
When [events]
Then [expected outcomes]
Simple template
Easy to read
Easy to write
Central part of BDD
24.10.2017 Achim WiedemannSeite 12
BDD summary
Validate against AC with Scenarios
• Use Given – When – Then form
Goals for tests:
• Focus on business value
• Make writing tests simple
• Use domain specific language
• Provide value for devs and business people
• Make acceptance criteria executable
24.10.2017 Achim WiedemannSeite 13
Part II – Traditional BDD tools
24.10.2017 Achim WiedemannSeite 14
JBehave
The missing part: „Make acceptance criteria executable“
• JBehave was born
Uses a *.story file for scenarios (plain text)
Scenarios are mapped to test methods (Java)
Run scenario tests like JUnit tests
View results as report (text, HTML, XML, …)
24.10.2017 Achim WiedemannSeite 15
JBehave
Scenario: trader is not alerted below threshold
Given a stock of symbol STK1 and a threshold of 10.0
When the stock is traded at 5.0
Then the alert status should be OFF
Scenario: trader is alerted above threshold
Given a stock of symbol STK1 and a threshold of 10.0
When the stock is traded at 11.0
Then the alert status should be ON
public class TraderSteps { // look, Ma, I'm a POJO!!
private Stock stock;
@Given("a stock of symbol $symbol and a threshold of $threshold")
public void aStock(String symbol, double threshold) {
stock = new Stock(symbol, threshold);
}
@When("the stock is traded at $price")
public void theStockIsTradedAt(double price) {
stock.tradeAt(price);
}
@Then("the alert status should be $status")
public void theAlertStatusShouldBe(String status) {
ensureThat(stock.getStatus().name(), equalTo(status));
}
}
24.10.2017 Achim WiedemannSeite 16
JBehave summary
The good:
• Plain english
• Focus on business value
• Use of domain specific language
• Business people can write scenarios
The bad:
• Plain english
• String matching
• No guidance with domain specific language
• Usually devs end up writing scenarios
24.10.2017 Achim WiedemannSeite 17
Part III – BDD with JGiven
24.10.2017 Achim WiedemannSeite 18
JGiven
Created in 2014 by Jan Schäfer (TNG Technology Consulting)
Main goal:
• Remove disconnect between code and plain text in BDD
JGiven:
• Write everything in Java code
• Promote code reuse
• Leverage existing tools (JUnit)
• Great reports
24.10.2017 Achim WiedemannSeite 19
Scenario example: place order in shop
@Test
public void logged_in_customer_should_be_able_to_place_an_order() {
given()
.user_is_logged_in_as(USER_TEST.username, USER_TEST.password)
.and().user_is_on_catalog_page();
when()
.user_adds_item_to_cart(PRODUCT_ID_BOOK, 1)
.and().user_opens_cart_page()
.and().user_follows_shipping_address_link()
.and().user_enters_some_shipping_address()
.and().user_places_order();
then()
.user_should_be_on_order_success_page();
}Logged in customer should be able to place an order
Given user is logged in as test test
And user is on catalog page
When user adds item to cart 1 1
And user opens cart page
And user follows shipping address link
And user enters some shipping address
And user places order
Then user should be on order success page
24.10.2017 Achim WiedemannSeite 20
Dissecting a scenario test
public class OrderScenarioTest extends ScenarioTest<GivenStage, WhenOnCatalogPageStage, ThenStage> {
private static final String PRODUCT_ID_BOOK = "1";
@Test
public void logged_in_customer_should_be_able_to_place_an_order() {
given()
.user_is_logged_in_as(USER_TEST.username, USER_TEST.password)
.and().user_is_on_catalog_page();
when()
.user_adds_item_to_cart(PRODUCT_ID_BOOK, 1)
.and().user_opens_cart_page()
.and().user_follows_shipping_address_link()
.and().user_enters_some_shipping_address()
.and().user_places_order();
then()
.user_should_be_on_order_success_page();
}
}
Stepmethod
StageScenario
24.10.2017 Achim WiedemannSeite 21
Scenarios
Methods in normal JUnit test classes
Derive JUnit test from ScenarioTest
• given()
• when()
• then()
Specify stages as type parameters
Use snake_case for test methods for proper casing
24.10.2017 Achim WiedemannSeite 22
Scenarios - 2
public class OrderScenarioTest extends ScenarioTest<GivenStage, WhenOnCatalogPageStage, ThenStage> {
private static final String PRODUCT_ID_BOOK = "1";
@Test
public void logged_in_customer_should_be_able_to_place_an_order() {
given()
.user_is_logged_in_as(USER_TEST.username, USER_TEST.password)
.and().user_is_on_catalog_page();
when()
.user_adds_item_to_cart(PRODUCT_ID_BOOK, 1)
.and().user_opens_cart_page()
.and().user_follows_shipping_address_link()
.and().user_enters_some_shipping_address()
.and().user_places_order();
then()
.user_should_be_on_order_success_page();
}
}
:GivenStage
:WhenOnCatalogPageStage
:ThenStage
24.10.2017 Achim WiedemannSeite 23
Creating a DSL with stages
Building blocks of scenarios
Model states and actions
• States: stages
• Actions: step methods
Form DSL with a fluid interface
Given on catalog pageWhen add item to cartand view cartand order itemsand confirm orderand continue shoppingand view past ordersThen order history should not be empty
24.10.2017 Achim WiedemannSeite 24
Creating a DSL with stages - 2
Derive from Stage
• and()
• with()
• but()
Fluid interface:
• Step methods which return stages
Allows sentences like:given().user_is_on_catalog_page().and().user_adds_item_to_cart("4711", 3);
public class WhenOnCatalogPageStage extends Stage<WhenOnCatalogPageStage> {
@ScenarioState
protected CatalogPage catalogPage;
public WhenOnCatalogPageStage user_adds_item_to_cart(String productId, int quantity) {
for (int i = 0; i < quantity; i++)
catalogPage.addProductToCart(productId);
return this;
}
}
24.10.2017 Achim WiedemannSeite 25
Creating a DSL with stages - 3
public class WhenOnCatalogPageStage extends Stage<WhenOnCatalogPageStage> {
@ScenarioState
protected CatalogPage catalogPage;
@ScenarioState
protected CartPage cartPage;
@ScenarioState
protected OrderHistoryPage orderHistoryPage;
@ScenarioStage
protected WhenOnCartPageStage cartStage;
public WhenOnCatalogPageStage user_adds_item_to_cart(String productId, int quantity) {
for (int i = 0; i < quantity; i++)
catalogPage.addProductToCart(productId);
return this; // State machine: transition to same state (on catalog page)
}
public WhenOnCartPageStage user_opens_cart_page() {
cartPage.openPage();
return cartStage; // State machine: transition to different state (on cart page)
}
public void user_opnes_order_history_page() {
orderHistoryPage.openPage(); // State machine: end
}
}
24.10.2017 Achim WiedemannSeite 26
Creating a DSL with stages - 4
Model states with stages
Model actions with step methods
Naming is key: focus on business language for your DSL
public class OrderScenarioTest extends ScenarioTest<GivenStage, WhenOnCatalogPageStage, ThenStage> {
private static final String PRODUCT_ID_BOOK = "1";
@Test
public void logged_in_customer_should_be_able_to_place_an_order() {
given()
.user_is_logged_in_as(USER_TEST.username, USER_TEST.password)
.and().user_is_on_catalog_page();
when()
.user_adds_item_to_cart(PRODUCT_ID_BOOK, 1)
.and().user_opens_cart_page()
.and().user_follows_shipping_address_link()
.and().user_enters_some_shipping_address()
.and().user_places_order();
then()
.user_should_be_on_order_success_page();
}
}
24.10.2017 Achim WiedemannSeite 27
Step methods
Actions of our DSL
Make up sentences in report
Use snake_case for correct casing in report
24.10.2017 Achim WiedemannSeite 28
Sharing state
State can be shared accross stages
• Page objects
• Services
• Stages
Injection via @ScenarioState annotations
public abstract class WhenBaseStage<SELF extends WhenBaseStage<SELF>> extends Stage<SELF> {
@ScenarioState
protected LoginPage loginPage;
@ScenarioStage
protected WhenOnCatalogPageStage catalogStage;
...
public WhenOnCatalogPageStage user_logs_in_as(TestUser user) {
if (!loginPage.isUserOnPage())
loginPage.openPage();
loginPage.loginWithCredentials(user.username, user.password);
return catalogStage;
}
...
}
24.10.2017 Achim WiedemannSeite 29
Sharing state - 2
State is passed from stage to stage
Initialization best in Given stage
Alternative to @ScenarioState annotations:
• @ProvidedScenarioState / @ExpectedScenarioState
public class GivenStage extends Stage<GivenStage> {
private final PageFactoryResource pageFactory = new PageFactoryResource();
@ScenarioState
private CatalogPage catalogPage;
@ScenarioState
private LoginPage loginPage;
@ScenarioState
private CartPage cartPage;
@BeforeScenario
public void setUp() {
catalogPage = pageFactory.createCatalogPage();
loginPage = pageFactory.createLoginPage();
cartPage = pageFactory.createCartPage();
}
...
}
24.10.2017 Achim WiedemannSeite 30
Reports
24.10.2017 Achim WiedemannSeite 31
Reports - 2
Correct casing when snake_case is used in methods
• Example: item_is_registered_in_SAP()
Scenarios can be grouped via tags
• Use custom annotations to create tags
Custom formatters for improved readability
• Step methods
• Method parameters
Formats: HTML and JSON
24.10.2017 Achim WiedemannSeite 32
Tags
@IsTag(name="Critical", description="Features that are critical for the user")
@Retention(RetentionPolicy.RUNTIME)
public @interface CriticalFeature {
}
@CriticalFeature
@Test
public void added_item_appears_in_cart() {
...
}
24.10.2017 Achim WiedemannSeite 33
Formatters
Improve readability with formatters
• Use $ in method names
• Custom formatters via @Format
Code:
when().user_adds_item_to_cart("4711", 3)
Generated report:
When user adds item to cart 4711 3
Code (using placeholders):
when().user_adds_item_$_to_cart_of_quantity_$("4711", 3)
Generated report (using placeholders):
When user adds item 4711 to cart of quantity 3
24.10.2017 Achim WiedemannSeite 34
Formatters - 2
@Test
public void added_item_appears_in_cart() {
given()
.user_is_logged_in_as(USER_TEST);
when()
.user_adds_item_$_to_cart_of_quantity_$(PRODUCT_ID_BOOK, 1)
.and().user_opens_cart_page();
then()
.user_should_see_cart_item_count_of(1);
}
24.10.2017 Achim WiedemannSeite 35
JGiven - Summary
The good:
• Simple JUnit tests
• Pure Java – no mapping from text to code
• Business focused DSL
• Developer friendly
The bad:
• Documentation quite good but could be even better
• Relatively unknown
• Business people can‘t write scenarios
24.10.2017 Achim WiedemannSeite 36
DEMO
https://github.com/achwie/hystrix-demo