SlideShare a Scribd company logo
Cucumber and Spock Primer
BDD Masterclass
Building software that makes a difference, and building it well
@wakaleo
www.johnfergusonsmart.com
reachme@johnfergusonsmart.com
Writing Acceptance Criteria

in Gherkin
Overview
Overview
Basic Gherkin
Feature: Earning Frequent Flyer points through flights
Scenario: Economy flights earn points by distance
Given the distance from London to Paris is 344 km
When I fly from London to Paris in Economy
Then I should earn 344 points
A feature represents a
deliverable piece of functionality
Each feature should
have a title
A feature contains one
or more scenarios
Features and Scenarios
Overview
Overview
Basic Gherkin
Feature: Earning Frequent Flyer points through flights
In order to encourage repeat business
As an Airline Sales Manager
I want customers to be able to cumulate points when they
fly with us
Scenario: Economy flights earn points by distance
Economy class earns 1 point per kilometre
Given the distance from London to Paris is 344 km
When I fly from London to Paris in Economy
Then I should earn 344 points
Free-text description 

(great for living documentation)
You can also put a description
for a Scenario
Free text descriptions
Overview
Overview
Basic Gherkin
Scenario: Economy flights earn points by distance
Economy class earns 1 point per kilometre
Given the distance from London to Paris is 344 km
When I fly from London to Paris in Economy
Then I should earn 344 points
GIVEN: Preconditions
WHEN: Action to illustrate
THEN: Expected outcome
Scenarios
Overview
Overview
Basic Gherkin
Scenario: Economy flights over several legs
Given the distance from London to Paris is 344 km
And the distance from Paris to Krakow is 1500 km
When I fly from London to Krakow via Paris in Economy
Then I should earn 1844 points
And I should earn 18 status points
Multiple preconditions
Scenarios
Multiple outcomes
Overview
Overview
Basic Gherkin
Given steps
✓ Defines preconditions and context
✓ Typically something that happened in the past
✓ Can interact with the system
✓ Should not perform any actions being tested by the
scenario
Given a user registers on the site
And the user logs in
When the home page appears
Then the user’s name should be visible
Given a user has registered on the site
When the user logs in
Then the user’s name should be visible on
the home page
What are we testing here?
Overview
Overview
Basic Gherkin
When steps
✓ The action under test
✓ Avoid chaining too many clauses
Given a user has registered on the site
When the user enters 'Scott' in the user name
And the user enters 'Tiger' in the password
And the user selects 'UK' as the country
And the user clicks on the Login button
And the user validates the Cookies alert
Then the user’s name should be visible on the home page
Too much detail
To implementation-specific
Overview
Overview
Basic Gherkin
Then steps
✓ The expected outcome
✓ Can query the system
✓ Should not perform actions on the system
Given a batch file needs to be processed
When the user uploads the batch file
Then the result should be visible
And a notification message should be sent to the user
Overview
Overview
Basic Gherkin
Background steps
✓ Common steps for all the scenarios in a feature file
✓ Avoid duplication
✓ More focused scenarios
Feature: Earning Frequent Flyer points through flights
In order to encourage repeat business
As an Airline Sales Manager
I want customers to be able to cumulate points when
they fly with us
Background:
Given the distance from London to Paris is 344 km
And the distance from Paris to Krakow is 1500km
Scenario: Economy flights earn points by distance
When I fly from London to Paris in Economy
Then I should earn 344 points
And I should earn 3 status points
Scenario: Economy flights over several legs
When I fly from London to Krakow via Paris in Economy
Then I should earn 1844 points
And I should earn 18 status points
Overview
Overview
Basic Gherkin
Scenario Outlines
✓ Use a table to simplify duplicated but similar scenarios
Background:
Given the distance from London to Paris is 344 km
And the distance from Paris to Krakow is 1500km
And the distance from London to Budapest is 1600km
Scenario: Economy flights earn points by distance
When I fly from London to Paris in Economy
Then I should earn 344 points
And I should earn 3 status points
Scenario: Economy flights to Krakow
When I fly from London to Krakow in Economy
Then I should earn 1500 points
And I should earn 15 status points
Scenario: Economy flights to Budapest
When I fly from London to Budapest in Economy
Then I should earn 1600 points
And I should earn 16 status points
Overview
Overview
Basic Gherkin
Scenario Outlines
✓ Use a table to simplify duplicated but similar scenarios
Background:
Given the distance from London to Paris is 344 km
And the distance from Paris to Krakow is 1500km
And the distance from London to Budapest is 1600km
Scenario Outline:
When I fly from <departure> to <destination> in Economy
Then I should earn <points> points
And I should earn <status-points> status points
Examples:
| departure | destination | points | status-points |
| London | Paris | 344 | 3 |
| Paris | Krakow | 1500 | 15 |
| London | Budapest | 1600 | 16 |
Overview
Overview
Basic Gherkin
Embedded tables
✓ Use tables in steps to pass tabular data to the step
Scenario: Deposit into a current account
Given Joe has the following accounts:
| Number | Type | Balance |
| 123456 | Current | 1000 |
When he deposits €100 into his Current account
Then he should have the following balances:
| Number | Type | Balance |
| 123456 | Current | 1100 |
Overview
Overview
Basic Gherkin
Embedded tables
✓ Use tables in steps to pass tabular data to the step
Feature: Earning Frequent Flyer points through flights
In order to encourage repeat business
As an Airline Sales Manager
I want customers to be able to cumulate points when they fly with us
Background:
Given the following flight routes:
| departure | destination | distance |
| London | Paris | 344 |
| Paris | Krakow | 1500 |
| London | Budapest | 1600 |
Scenario Outline:
When I fly from <departure> to <destination> in Economy
Then I should earn <points> points
And I should earn <status-points> status points
Examples:
| departure | destination | points | status-points |
| London | Paris | 344 | 3 |
| Paris | Krakow | 1500 | 15 |
| London | Budapest | 1600 | 16 |
Overview
Overview
Basic Gherkin
Multi-line string parameters
✓ Pass in multi-line strings as parameters
Given a travel provides feedback about the flight
"""
It was OK
The food was rubbish but the staff were nice
The flight was delayed by 15 minutes
"""
Cucumber Step Definitions
Step Definitions
in Java with
Cucumber and
Serenity BDD
The Cucumber Test Runner
import cucumber.api.CucumberOptions;
import net.serenitybdd.cucumber.CucumberWithSerenity;
import org.junit.runner.RunWith;
@RunWith(CucumberWithSerenity.class)
@CucumberOptions(features = "src/test/resources/features/deposit_funds",
glue = "com.bddinaction.serenitybank")
public class DepositFunds {}
A Cucumber test runner
Which feature files to run
Where to find the step definition classes
Step Definitions
in Java with
Cucumber and
Serenity BDD
Step Definitions
Feature: Earning Frequent Flyer points through flights
Scenario: Economy flights earn points by distance
Given the distance from London to Paris is 344 km
When I fly from London to Paris in Economy
Then I should earn 344 points
@Given("^the distance from (.*) to (.*) is (d+) km$")
public void pointsEarnedBetweenCities(String departure,
String destination,
int distance)
throws Throwable {
}
@Given, @When, or @Then annotation
Matching regular expression
Matched expressions are passed in as parameters
Step Definitions
in Java with
Cucumber and
Serenity BDD
Matching Lists
Given the following possible destinations: London, Paris, Amsterdam
@Given("^the following possible destinations: (.*)$")
public void theFollowingPossibleDestinations(List<String> destinations) {
}
Declare a list of Strings
Given the following possible destinations:
| London |
| Paris |
| Amsterdam |
alternative notation
Step Definitions
in Java with
Cucumber and
Serenity BDD
Matching enums
When I fly from London to Paris in Economy
public enum City {
London, Paris, Amsterdam
}
public enum CabinClass {
Economy, Business, First
}
@When("^I fly from (.*) to (.*) in (.*)$")
public void iFly(City departure, City destination, CabinClass cabinClass){
}
Enums are matched automatically
Step Definitions
in Java with
Cucumber and
Serenity BDD
Matching tables
Given the following flight routes:
| departure | destination | distance |
| London | Paris | 344 |
| Paris | Krakow | 1500 |
| London | Budapest | 1600 |
public void flightRoutes(Map<String, String> flightRoutes) {}
Tables can be passed as a map of Strings
Step Definitions
in Java with
Cucumber and
Serenity BDD
Transformers
When I fly from San Francisco to Paris in Economy
public enum City {
London("London"),
Paris("Paris"),
SanFrancisco("San Francisco"),
SaintPetersbuug("St Petersburg");
public final String name;
City(String name) { this.name = name; }
}
@When("^I fly from (.*) to (.*) in (.*)$")
public void iFly(@Transform(CityConverter.class) City departure,
@Transform(CityConverter.class) City destination,
CabinClass cabinClass) {}
public class CityConverter extends Transformer<City> {
@Override
public City transform(String cityName) {
return Arrays.stream(City.values())
.filter(city -> city.name.equalsIgnoreCase(cityName))
.findFirst()
.orElseThrow(() -> new UnknownCityException(cityName));
}
}
BDD Unit Testing with Spock
Overview
Overview
Introduction
to Spock
http://spockframework.org/
import spock.lang.Specification
class WhenManagingAnAccount extends Specification {
def "depositing a sum into the account should update the balance"() {
given:
def currentAccount = new BankAccount("123456", AccountType.Current)
when:
currentAccount.deposit(1000)
then:
currentAccount.balance == 1000
}
}
All Spock tests extend Specification
Plain English test names
Arrange
Act
Assert
Acts as an assertion
Overview
Overview
Using Fields
class WhenWithdrawingFunds extends Specification {
def accountService = new AccountService();
def "withdrawing funds from a current account"() {
given:
def accountNumber = accountService.createNewAccount(Current, 100.00)
when:
accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now())
then:
accountService.getBalance(accountNumber) == 50.00
}
def "withdrawing funds from a savings account"() {
given:
def accountNumber = accountService.createNewAccount(BigSaver, 1000.00)
when:
accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now())
then:
thrown(MinimumBalanceRequiredException)
and:
accountService.getBalance(accountNumber) == 1000.00
}
}
Instantiated once before each test
Overview
Overview
Using Fields
class WhenWithdrawingFunds extends Specification {
@Shared def accountService = new AccountService()
def "withdrawing funds from a current account"() {
given:
def accountNumber = accountService.createNewAccount(Current, 100.00)
when:
accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now())
then:
accountService.getBalance(accountNumber) == 50.00
}
def "withdrawing funds from a savings account"() {
given:
def accountNumber = accountService.createNewAccount(BigSaver, 1000.00)
when:
accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now())
then:
thrown(MinimumBalanceRequiredException)
and:
accountService.getBalance(accountNumber) == 1000.00
}
}
Instantiated once and shared between the tests
Overview
Overview
Using Fields
class WhenWithdrawingFunds extends Specification {
def accountService
def setup() {
accountService = new AccountService()
}
def "withdrawing funds from a current account"() {
given:
def accountNumber = accountService.createNewAccount(Current, 100.00)
when:
accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now())
then:
accountService.getBalance(accountNumber) == 50.00
}
def "withdrawing funds from a savings account"() {
given:
def accountNumber = accountService.createNewAccount(BigSaver, 1000.00)
when:
accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now())
then:
thrown(MinimumBalanceRequiredException)
and:
accountService.getBalance(accountNumber) == 1000.00
}
}
Runs before each test
Overview
Overview
Using Fields
class WhenWithdrawingFunds extends Specification {
@Shared def accountService = new AccountService()
def setupSpec() {
accountService = new AccountService()
}
def "withdrawing funds from a current account"() {
given:
def accountNumber = accountService.createNewAccount(Current, 100.00)
when:
accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now())
then:
accountService.getBalance(accountNumber) == 50.00
}
def "withdrawing funds from a savings account"() {
given:
def accountNumber = accountService.createNewAccount(BigSaver, 1000.00)
when:
accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now())
then:
thrown(MinimumBalanceRequiredException)
and:
accountService.getBalance(accountNumber) == 1000.00
}
}
Runs once at the start of the Specification
See also cleanup() and cleanupSpec()
Overview
Overview
Exceptions
class WhenWithdrawingFunds extends Specification {
@Shared def accountService = new AccountService()
def "withdrawing funds from a savings account"() {
given:
def accountNumber = accountService.createNewAccount(BigSaver, 1000.00)
when:
accountService.makeWithdrawal(accountNumber, 50.00,
LocalDate.now())
then:
thrown(MinimumBalanceRequiredException)
and:
accountService.getBalance(accountNumber) == 1000.00
}
}
Expect this exception to be thrown
Also, the balance should be unchanged
Overview
Overview
Exceptions
def "withdrawing funds from a savings account"() {
given:
def accountNumber = accountService.createNewAccount(BigSaver, 1000.00)
when:
accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now())
then:
MinimumBalanceRequiredException exception = thrown()
and:
exception.message == "Minimum balance for this account is €1000.00"
and:
accountService.getBalance(accountNumber) == 1000.00
}
Expect this exception to be thrown
Check exception attributes
Overview
Overview
Data-Driven
Specifications
class WhenCalculatingDepositFees extends Specification {
def “correct deposit should be calculated"() {
expect:
DepositFee.forAccountType(accountType).apply(amount) == expectedDepositFee
where:
accountType | amount | expectedDepositFee
Current | 1000.0 | 0.0
BasicSavings | 100.00 | 0.50
BigSaver | 10.00 | 0.50
BigSaver | 100.01 | 0.75
BigSaver | 1000.01 | 1.25
}
}
Use these variables in the test
Overview
Overview
Data-Driven
Specifications
class WhenCalculatingDepositFees extends Specification {
@Unroll
def “A deposit of #amount in an #accountType account"() {
expect:
DepositFee.forAccountType(accountType).apply(amount) == expectedDepositFee
where:
accountType | amount | expectedDepositFee
Current | 1000.0 | 0.0
BasicSavings | 100.00 | 0.50
BigSaver | 10.00 | 0.50
BigSaver | 100.01 | 0.75
BigSaver | 1000.01 | 1.25
}
}
Table variables can be placed in the test title
Report a separate test for each row of data
Overview
Overview
Test Doubles
✓ Test Doubles
✓ Stand-ins for dependencies
✓ Transparently replace objects used by 

the class under test
✓ Make tests faster and easier to write
Overview
Overview
Test Doubles
✓ Test Doubles stand in for objects that are
✓ Unavailable or unfinished
✓ Slow
✓ Needs something that is unavailable
✓ Hard to instantiate
Overview
Overview
Test Doubles A complex remote service
Overview
Overview
Test Doubles def "should apply correct discount for a valid discount code"() {
given:
PriceService priceService = ???
OrderProcessor processor = new OrderProcessor(priceService);
and:
def mouse = new Product("Wireless Mouse", 20.00);
def tenPercentOffPrice = 18.00;
when:
def order = ProductOrder.discountedOrder(mouse, “MINUS_10_PERCENT");
def discountedPrice = processor.calculateTotalPriceFor(order)
then:
discountedPrice == tenPercentOffPrice;
}
How do we get a PriceService?
Overview
Overview
Test Doubles class PriceServiceTestDouble extends PriceService {
private final double discount;
TestPriceService(double discount) {
this.discount = discount;
}
@Override
public double percentageDiscountForCode(String discountCode) {
return discount;
}
}
A test double
Overview
Overview
Test Doubles def "should apply correct discount for a valid discount code"() {
given:
PriceService priceService = new PriceServiceTestDouble(0.20)
OrderProcessor processor = new OrderProcessor(priceService);
and:
def mouse = new Product("Wireless Mouse", 20.00);
def tenPercentOffPrice = 18.00;
when:
def order = ProductOrder.discountedOrder(mouse, “MINUS_10_PERCENT");
def discountedPrice = processor.calculateTotalPriceFor(order)
then:
discountedPrice == tenPercentOffPrice;
}
Now use the test double
Overview
Overview
Test Doubles
✓ There are several types of Test Doubles
✓ Stubs
✓ Fakes
✓ Mocks
Overview
Overview
Test Doubles
✓ Stubs
✓ Minimal implementation
✓ Returns hard-coded values
✓ State-based testing
class PriceServiceTestDouble extends PriceService {
@Override
public double percentageDiscountForCode(String discountCode) {
return 10;
}
}
Overview
Overview
Test Doubles
✓ Fakes
✓ Smarter stubs
✓ Some simple behaviour
✓ State-based testing
public class MockEnvironmentVariables implements EnvironmentVariables {
private Properties properties = new Properties();
public String getProperty(String name) {
return properties.getProperty(name);
}
public String getProperty(String name, String defaultValue) {
return properties.getProperty(name, defaultValue);
}
public void setProperty(String name, String value) {
properties.setProperty(name, value);
}
public void setValue(String name, String value) {
values.setProperty(name, value);
}
}
Overview
Overview
Test Doubles
✓ Mocks
✓ Sophisticated stubs
✓ Record and verify interactions
✓ Need a mocking framework
Overview
Overview
Test Doubles
✓ Mocks
✓ Only mock what you own
✓ Verify specification, not implementation
✓ Specify as little as possible in a test
✓ Only mock your immediate neighbours
✓ Don’t mock boundary objects
✓ Don’t mock domain objects
✓ Keep it simple
Overview
Overview
Test Doubles
in Spock
def vatService = Mock(VATService)
def seller = new Seller(vatService);
def “VAT should apply on ordinary articles"() {
given: "the VAT rate for shirts is 20%"
seller = new Seller(vatService);
vatService.getRateFor("shirt") >> 0.2
and: "we are selling a shirt"
def sale = seller.sells(1, "shirt").forANetPriceOf(10.00)
when: "we calculate the price including VAT"
def totalPrice = sale.totalPrice
then: "the price should include GST of 20%"
totalPrice == 12.00
}
The seller thinks he is using a real VATService
Create a mocked version of the VATService class
Stubbed return value for the getRateFor() method
Overview
Overview
Test Doubles
in Spock
def vatService = Mock(VATService)
…
VATService vatService = Mock()
You can create a mock object in two ways
(This way is more IDE-friendly)
def vatService = Mock(VATService)
…
VATService vatService = Mock()
Creating Mocks
Overview
Overview
Test Doubles
in Spock
def vatService = Stub(VATService)
…
VATService vatService = Stub()
A Stub returns empty or “dummy” responses for all method calls
(This way is more IDE-friendly)
Creating Stubs
Overview
Overview
Test Doubles
in Spock
vatService.getRateFor("shirt") >> 0.2
Mock the result of a particular method call
Return different values on successive calls
vatService.getRateFor("shirt") >>> [0.2, 0.1]
vatService.getRateFor("shirt") >>
{ throw new UnknownProductException()}
Do something in a closure
Mocking interactions
VATService vatService = Mock() {
getRateFor("shirt") >> 0.2
getRateFor("banana") >> 0.1
}
Mock the interactions at creation time
Overview
Overview
Test Doubles
in Spock
def "The VAT Service should be used to obtain VAT values"() {
given: "the GST rate for shirts is 10%"
seller = new Seller(vatService);
vatService.getRateFor("shirt") >> 0.1
when: "we sell a shirt"
seller.sells(1, "shirt").forANetPriceOf(10.00)
then: "the VAT service should be used to determine the GST rate"
1 * vatService.getRateFor("shirt")
}
This method should be called exactly once
Checking interactions
Overview
Overview
Interactions in
Spock
1 * vatService.getRateFor("shirt") // Exactly once with this parameter
(1.._) * vatService.getRateFor("shirt") // At least once
(_..2) * vatService.getRateFor("shirt") // No more than twice
vatService.getRateFor(_) // Called with any parameter
vatService.getRateFor({it != "banana"}) // Custom constraint
Checking interactions
Overview
Overview
Documentation
def "withdrawing funds from a current account"() {
given: "a current account with €100"
def accountNumber = accountService.createNewAccount(Current, 100.00)
when: "we withdraw €50"
accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now())
then: "the balance should be €50"
accountService.getBalance(accountNumber) == 50.00
}
Descriptive texts go after the labels
Thank You!
AUTHOR OF ‘BDD IN ACTION’
@wakaleo
www.johnfergusonsmart.com
reachme@johnfergusonsmart.com

More Related Content

PDF
Apache Click
PPT
A Deeper look into Javascript Basics
PPT
Spring Core
PDF
[216]네이버 검색 사용자를 만족시켜라! 의도파악과 의미검색
PDF
A Basic Django Introduction
PPTX
Haystack 2018 - Algorithmic Extraction of Keywords Concepts and Vocabularies
PDF
TypeScript Introduction
Apache Click
A Deeper look into Javascript Basics
Spring Core
[216]네이버 검색 사용자를 만족시켜라! 의도파악과 의미검색
A Basic Django Introduction
Haystack 2018 - Algorithmic Extraction of Keywords Concepts and Vocabularies
TypeScript Introduction

What's hot (20)

PDF
An Introduction to Redux
PPTX
LINQ in C#
PDF
Spring MVC
PDF
TypeScript - An Introduction
PPTX
[Final] ReactJS presentation
PPTX
Lab #2: Introduction to Javascript
PPT
PDF
Angular Observables & RxJS Introduction
PDF
Ksug2015 - JPA3, JPA 내부구조
PPTX
Typescript in 30mins
PDF
React Router: React Meetup XXL
PDF
Django Rest Framework - Building a Web API
PDF
Php introduction
PDF
엘라스틱서치, 로그스태시, 키바나
PPTX
SpringBoot with MyBatis, Flyway, QueryDSL
PDF
Functional Programming Patterns (NDC London 2014)
PPTX
Regular Expressions in Java
PPTX
Introduction to Django
PPTX
Dart ppt
An Introduction to Redux
LINQ in C#
Spring MVC
TypeScript - An Introduction
[Final] ReactJS presentation
Lab #2: Introduction to Javascript
Angular Observables & RxJS Introduction
Ksug2015 - JPA3, JPA 내부구조
Typescript in 30mins
React Router: React Meetup XXL
Django Rest Framework - Building a Web API
Php introduction
엘라스틱서치, 로그스태시, 키바나
SpringBoot with MyBatis, Flyway, QueryDSL
Functional Programming Patterns (NDC London 2014)
Regular Expressions in Java
Introduction to Django
Dart ppt
Ad

Similar to Cucumber and Spock Primer (20)

PDF
Feature Mapping Workshop
PDF
Driving Design through Examples
PDF
Driving Design through Examples - PhpCon PL 2015
PDF
Driving Design through Examples
PDF
Journey to Testable Requirements
PPT
Cucumber presentation
PDF
Driving Design through Examples
PPTX
Continuous Quality
PPTX
Cucumber With Selenium
PDF
BDD, Gherkin, Cucumber and why we need it.
PDF
«BDD, Gherkin, Cucumber and why we need it for successful product development»
PPTX
Cucumber BDD
PPTX
1501 meetup
PDF
Bdd From The Trenches
PDF
Why BDD is our BFF
PDF
Advanced Test Automation: WDIO with BDD Cucumber
PPTX
The Art of Gherkin Scripting - Matt Eakin
Feature Mapping Workshop
Driving Design through Examples
Driving Design through Examples - PhpCon PL 2015
Driving Design through Examples
Journey to Testable Requirements
Cucumber presentation
Driving Design through Examples
Continuous Quality
Cucumber With Selenium
BDD, Gherkin, Cucumber and why we need it.
«BDD, Gherkin, Cucumber and why we need it for successful product development»
Cucumber BDD
1501 meetup
Bdd From The Trenches
Why BDD is our BFF
Advanced Test Automation: WDIO with BDD Cucumber
The Art of Gherkin Scripting - Matt Eakin
Ad

More from John Ferguson Smart Limited (20)

PPTX
My Reading Specs - Refactoring Patterns for Gherkin Scenarios
PDF
Artisti e Condotierri - How can your team become artists of the 21st century ...
PDF
Engage! Bringing teams together to deliver software that makes a difference
PDF
BE A POD OF DOLPHINS, NOT A DANCING ELEPHANT
PDF
Sustainable Test Automation with Serenity BDD and Screenplay
PDF
Engage! Bringing teams together to deliver software that makes a difference
PDF
Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...
PDF
Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...
PDF
Shift left-devoxx-pl
PDF
Screenplay - Next generation automated acceptance testing
PDF
All the world's a stage – the next step in automated testing practices
PDF
CukeUp 2016 Agile Product Planning Workshop
PDF
BDD Anti-patterns
PDF
Serenity and the Journey Pattern
PDF
BDD - Collaborate like you mean it!
PDF
BDD-Driven Microservices
PDF
BDD Anti-patterns
PDF
It's Testing, Jim, but not as we know it - BDD for Testers
PDF
BDD in Action - Automated Web Testing with WebDriver and Serenity
PDF
BDD in Action - Devoxx 2014
My Reading Specs - Refactoring Patterns for Gherkin Scenarios
Artisti e Condotierri - How can your team become artists of the 21st century ...
Engage! Bringing teams together to deliver software that makes a difference
BE A POD OF DOLPHINS, NOT A DANCING ELEPHANT
Sustainable Test Automation with Serenity BDD and Screenplay
Engage! Bringing teams together to deliver software that makes a difference
Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...
Beyond Given/When/Then - why diving into Cucumber is the wrong approach to ad...
Shift left-devoxx-pl
Screenplay - Next generation automated acceptance testing
All the world's a stage – the next step in automated testing practices
CukeUp 2016 Agile Product Planning Workshop
BDD Anti-patterns
Serenity and the Journey Pattern
BDD - Collaborate like you mean it!
BDD-Driven Microservices
BDD Anti-patterns
It's Testing, Jim, but not as we know it - BDD for Testers
BDD in Action - Automated Web Testing with WebDriver and Serenity
BDD in Action - Devoxx 2014

Recently uploaded (20)

PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 41
PPTX
CHAPTER 2 - PM Management and IT Context
PDF
PTS Company Brochure 2025 (1).pdf.......
PPTX
Introduction to Artificial Intelligence
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PPTX
CHAPTER 12 - CYBER SECURITY AND FUTURE SKILLS (1) (1).pptx
PPTX
ai tools demonstartion for schools and inter college
PDF
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
PDF
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PPTX
L1 - Introduction to python Backend.pptx
PDF
Odoo Companies in India – Driving Business Transformation.pdf
PDF
medical staffing services at VALiNTRY
PDF
top salesforce developer skills in 2025.pdf
PDF
AI in Product Development-omnex systems
PDF
How Creative Agencies Leverage Project Management Software.pdf
PDF
Flood Susceptibility Mapping Using Image-Based 2D-CNN Deep Learnin. Overview ...
PPTX
history of c programming in notes for students .pptx
PDF
Audit Checklist Design Aligning with ISO, IATF, and Industry Standards — Omne...
PDF
System and Network Administration Chapter 2
Internet Downloader Manager (IDM) Crack 6.42 Build 41
CHAPTER 2 - PM Management and IT Context
PTS Company Brochure 2025 (1).pdf.......
Introduction to Artificial Intelligence
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
Design an Analysis of Algorithms II-SECS-1021-03
CHAPTER 12 - CYBER SECURITY AND FUTURE SKILLS (1) (1).pptx
ai tools demonstartion for schools and inter college
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
L1 - Introduction to python Backend.pptx
Odoo Companies in India – Driving Business Transformation.pdf
medical staffing services at VALiNTRY
top salesforce developer skills in 2025.pdf
AI in Product Development-omnex systems
How Creative Agencies Leverage Project Management Software.pdf
Flood Susceptibility Mapping Using Image-Based 2D-CNN Deep Learnin. Overview ...
history of c programming in notes for students .pptx
Audit Checklist Design Aligning with ISO, IATF, and Industry Standards — Omne...
System and Network Administration Chapter 2

Cucumber and Spock Primer

  • 1. Cucumber and Spock Primer BDD Masterclass Building software that makes a difference, and building it well @wakaleo www.johnfergusonsmart.com reachme@johnfergusonsmart.com
  • 3. Overview Overview Basic Gherkin Feature: Earning Frequent Flyer points through flights Scenario: Economy flights earn points by distance Given the distance from London to Paris is 344 km When I fly from London to Paris in Economy Then I should earn 344 points A feature represents a deliverable piece of functionality Each feature should have a title A feature contains one or more scenarios Features and Scenarios
  • 4. Overview Overview Basic Gherkin Feature: Earning Frequent Flyer points through flights In order to encourage repeat business As an Airline Sales Manager I want customers to be able to cumulate points when they fly with us Scenario: Economy flights earn points by distance Economy class earns 1 point per kilometre Given the distance from London to Paris is 344 km When I fly from London to Paris in Economy Then I should earn 344 points Free-text description 
 (great for living documentation) You can also put a description for a Scenario Free text descriptions
  • 5. Overview Overview Basic Gherkin Scenario: Economy flights earn points by distance Economy class earns 1 point per kilometre Given the distance from London to Paris is 344 km When I fly from London to Paris in Economy Then I should earn 344 points GIVEN: Preconditions WHEN: Action to illustrate THEN: Expected outcome Scenarios
  • 6. Overview Overview Basic Gherkin Scenario: Economy flights over several legs Given the distance from London to Paris is 344 km And the distance from Paris to Krakow is 1500 km When I fly from London to Krakow via Paris in Economy Then I should earn 1844 points And I should earn 18 status points Multiple preconditions Scenarios Multiple outcomes
  • 7. Overview Overview Basic Gherkin Given steps ✓ Defines preconditions and context ✓ Typically something that happened in the past ✓ Can interact with the system ✓ Should not perform any actions being tested by the scenario Given a user registers on the site And the user logs in When the home page appears Then the user’s name should be visible Given a user has registered on the site When the user logs in Then the user’s name should be visible on the home page What are we testing here?
  • 8. Overview Overview Basic Gherkin When steps ✓ The action under test ✓ Avoid chaining too many clauses Given a user has registered on the site When the user enters 'Scott' in the user name And the user enters 'Tiger' in the password And the user selects 'UK' as the country And the user clicks on the Login button And the user validates the Cookies alert Then the user’s name should be visible on the home page Too much detail To implementation-specific
  • 9. Overview Overview Basic Gherkin Then steps ✓ The expected outcome ✓ Can query the system ✓ Should not perform actions on the system Given a batch file needs to be processed When the user uploads the batch file Then the result should be visible And a notification message should be sent to the user
  • 10. Overview Overview Basic Gherkin Background steps ✓ Common steps for all the scenarios in a feature file ✓ Avoid duplication ✓ More focused scenarios Feature: Earning Frequent Flyer points through flights In order to encourage repeat business As an Airline Sales Manager I want customers to be able to cumulate points when they fly with us Background: Given the distance from London to Paris is 344 km And the distance from Paris to Krakow is 1500km Scenario: Economy flights earn points by distance When I fly from London to Paris in Economy Then I should earn 344 points And I should earn 3 status points Scenario: Economy flights over several legs When I fly from London to Krakow via Paris in Economy Then I should earn 1844 points And I should earn 18 status points
  • 11. Overview Overview Basic Gherkin Scenario Outlines ✓ Use a table to simplify duplicated but similar scenarios Background: Given the distance from London to Paris is 344 km And the distance from Paris to Krakow is 1500km And the distance from London to Budapest is 1600km Scenario: Economy flights earn points by distance When I fly from London to Paris in Economy Then I should earn 344 points And I should earn 3 status points Scenario: Economy flights to Krakow When I fly from London to Krakow in Economy Then I should earn 1500 points And I should earn 15 status points Scenario: Economy flights to Budapest When I fly from London to Budapest in Economy Then I should earn 1600 points And I should earn 16 status points
  • 12. Overview Overview Basic Gherkin Scenario Outlines ✓ Use a table to simplify duplicated but similar scenarios Background: Given the distance from London to Paris is 344 km And the distance from Paris to Krakow is 1500km And the distance from London to Budapest is 1600km Scenario Outline: When I fly from <departure> to <destination> in Economy Then I should earn <points> points And I should earn <status-points> status points Examples: | departure | destination | points | status-points | | London | Paris | 344 | 3 | | Paris | Krakow | 1500 | 15 | | London | Budapest | 1600 | 16 |
  • 13. Overview Overview Basic Gherkin Embedded tables ✓ Use tables in steps to pass tabular data to the step Scenario: Deposit into a current account Given Joe has the following accounts: | Number | Type | Balance | | 123456 | Current | 1000 | When he deposits €100 into his Current account Then he should have the following balances: | Number | Type | Balance | | 123456 | Current | 1100 |
  • 14. Overview Overview Basic Gherkin Embedded tables ✓ Use tables in steps to pass tabular data to the step Feature: Earning Frequent Flyer points through flights In order to encourage repeat business As an Airline Sales Manager I want customers to be able to cumulate points when they fly with us Background: Given the following flight routes: | departure | destination | distance | | London | Paris | 344 | | Paris | Krakow | 1500 | | London | Budapest | 1600 | Scenario Outline: When I fly from <departure> to <destination> in Economy Then I should earn <points> points And I should earn <status-points> status points Examples: | departure | destination | points | status-points | | London | Paris | 344 | 3 | | Paris | Krakow | 1500 | 15 | | London | Budapest | 1600 | 16 |
  • 15. Overview Overview Basic Gherkin Multi-line string parameters ✓ Pass in multi-line strings as parameters Given a travel provides feedback about the flight """ It was OK The food was rubbish but the staff were nice The flight was delayed by 15 minutes """
  • 17. Step Definitions in Java with Cucumber and Serenity BDD The Cucumber Test Runner import cucumber.api.CucumberOptions; import net.serenitybdd.cucumber.CucumberWithSerenity; import org.junit.runner.RunWith; @RunWith(CucumberWithSerenity.class) @CucumberOptions(features = "src/test/resources/features/deposit_funds", glue = "com.bddinaction.serenitybank") public class DepositFunds {} A Cucumber test runner Which feature files to run Where to find the step definition classes
  • 18. Step Definitions in Java with Cucumber and Serenity BDD Step Definitions Feature: Earning Frequent Flyer points through flights Scenario: Economy flights earn points by distance Given the distance from London to Paris is 344 km When I fly from London to Paris in Economy Then I should earn 344 points @Given("^the distance from (.*) to (.*) is (d+) km$") public void pointsEarnedBetweenCities(String departure, String destination, int distance) throws Throwable { } @Given, @When, or @Then annotation Matching regular expression Matched expressions are passed in as parameters
  • 19. Step Definitions in Java with Cucumber and Serenity BDD Matching Lists Given the following possible destinations: London, Paris, Amsterdam @Given("^the following possible destinations: (.*)$") public void theFollowingPossibleDestinations(List<String> destinations) { } Declare a list of Strings Given the following possible destinations: | London | | Paris | | Amsterdam | alternative notation
  • 20. Step Definitions in Java with Cucumber and Serenity BDD Matching enums When I fly from London to Paris in Economy public enum City { London, Paris, Amsterdam } public enum CabinClass { Economy, Business, First } @When("^I fly from (.*) to (.*) in (.*)$") public void iFly(City departure, City destination, CabinClass cabinClass){ } Enums are matched automatically
  • 21. Step Definitions in Java with Cucumber and Serenity BDD Matching tables Given the following flight routes: | departure | destination | distance | | London | Paris | 344 | | Paris | Krakow | 1500 | | London | Budapest | 1600 | public void flightRoutes(Map<String, String> flightRoutes) {} Tables can be passed as a map of Strings
  • 22. Step Definitions in Java with Cucumber and Serenity BDD Transformers When I fly from San Francisco to Paris in Economy public enum City { London("London"), Paris("Paris"), SanFrancisco("San Francisco"), SaintPetersbuug("St Petersburg"); public final String name; City(String name) { this.name = name; } } @When("^I fly from (.*) to (.*) in (.*)$") public void iFly(@Transform(CityConverter.class) City departure, @Transform(CityConverter.class) City destination, CabinClass cabinClass) {} public class CityConverter extends Transformer<City> { @Override public City transform(String cityName) { return Arrays.stream(City.values()) .filter(city -> city.name.equalsIgnoreCase(cityName)) .findFirst() .orElseThrow(() -> new UnknownCityException(cityName)); } }
  • 23. BDD Unit Testing with Spock
  • 24. Overview Overview Introduction to Spock http://spockframework.org/ import spock.lang.Specification class WhenManagingAnAccount extends Specification { def "depositing a sum into the account should update the balance"() { given: def currentAccount = new BankAccount("123456", AccountType.Current) when: currentAccount.deposit(1000) then: currentAccount.balance == 1000 } } All Spock tests extend Specification Plain English test names Arrange Act Assert Acts as an assertion
  • 25. Overview Overview Using Fields class WhenWithdrawingFunds extends Specification { def accountService = new AccountService(); def "withdrawing funds from a current account"() { given: def accountNumber = accountService.createNewAccount(Current, 100.00) when: accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now()) then: accountService.getBalance(accountNumber) == 50.00 } def "withdrawing funds from a savings account"() { given: def accountNumber = accountService.createNewAccount(BigSaver, 1000.00) when: accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now()) then: thrown(MinimumBalanceRequiredException) and: accountService.getBalance(accountNumber) == 1000.00 } } Instantiated once before each test
  • 26. Overview Overview Using Fields class WhenWithdrawingFunds extends Specification { @Shared def accountService = new AccountService() def "withdrawing funds from a current account"() { given: def accountNumber = accountService.createNewAccount(Current, 100.00) when: accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now()) then: accountService.getBalance(accountNumber) == 50.00 } def "withdrawing funds from a savings account"() { given: def accountNumber = accountService.createNewAccount(BigSaver, 1000.00) when: accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now()) then: thrown(MinimumBalanceRequiredException) and: accountService.getBalance(accountNumber) == 1000.00 } } Instantiated once and shared between the tests
  • 27. Overview Overview Using Fields class WhenWithdrawingFunds extends Specification { def accountService def setup() { accountService = new AccountService() } def "withdrawing funds from a current account"() { given: def accountNumber = accountService.createNewAccount(Current, 100.00) when: accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now()) then: accountService.getBalance(accountNumber) == 50.00 } def "withdrawing funds from a savings account"() { given: def accountNumber = accountService.createNewAccount(BigSaver, 1000.00) when: accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now()) then: thrown(MinimumBalanceRequiredException) and: accountService.getBalance(accountNumber) == 1000.00 } } Runs before each test
  • 28. Overview Overview Using Fields class WhenWithdrawingFunds extends Specification { @Shared def accountService = new AccountService() def setupSpec() { accountService = new AccountService() } def "withdrawing funds from a current account"() { given: def accountNumber = accountService.createNewAccount(Current, 100.00) when: accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now()) then: accountService.getBalance(accountNumber) == 50.00 } def "withdrawing funds from a savings account"() { given: def accountNumber = accountService.createNewAccount(BigSaver, 1000.00) when: accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now()) then: thrown(MinimumBalanceRequiredException) and: accountService.getBalance(accountNumber) == 1000.00 } } Runs once at the start of the Specification See also cleanup() and cleanupSpec()
  • 29. Overview Overview Exceptions class WhenWithdrawingFunds extends Specification { @Shared def accountService = new AccountService() def "withdrawing funds from a savings account"() { given: def accountNumber = accountService.createNewAccount(BigSaver, 1000.00) when: accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now()) then: thrown(MinimumBalanceRequiredException) and: accountService.getBalance(accountNumber) == 1000.00 } } Expect this exception to be thrown Also, the balance should be unchanged
  • 30. Overview Overview Exceptions def "withdrawing funds from a savings account"() { given: def accountNumber = accountService.createNewAccount(BigSaver, 1000.00) when: accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now()) then: MinimumBalanceRequiredException exception = thrown() and: exception.message == "Minimum balance for this account is €1000.00" and: accountService.getBalance(accountNumber) == 1000.00 } Expect this exception to be thrown Check exception attributes
  • 31. Overview Overview Data-Driven Specifications class WhenCalculatingDepositFees extends Specification { def “correct deposit should be calculated"() { expect: DepositFee.forAccountType(accountType).apply(amount) == expectedDepositFee where: accountType | amount | expectedDepositFee Current | 1000.0 | 0.0 BasicSavings | 100.00 | 0.50 BigSaver | 10.00 | 0.50 BigSaver | 100.01 | 0.75 BigSaver | 1000.01 | 1.25 } } Use these variables in the test
  • 32. Overview Overview Data-Driven Specifications class WhenCalculatingDepositFees extends Specification { @Unroll def “A deposit of #amount in an #accountType account"() { expect: DepositFee.forAccountType(accountType).apply(amount) == expectedDepositFee where: accountType | amount | expectedDepositFee Current | 1000.0 | 0.0 BasicSavings | 100.00 | 0.50 BigSaver | 10.00 | 0.50 BigSaver | 100.01 | 0.75 BigSaver | 1000.01 | 1.25 } } Table variables can be placed in the test title Report a separate test for each row of data
  • 33. Overview Overview Test Doubles ✓ Test Doubles ✓ Stand-ins for dependencies ✓ Transparently replace objects used by 
 the class under test ✓ Make tests faster and easier to write
  • 34. Overview Overview Test Doubles ✓ Test Doubles stand in for objects that are ✓ Unavailable or unfinished ✓ Slow ✓ Needs something that is unavailable ✓ Hard to instantiate
  • 35. Overview Overview Test Doubles A complex remote service
  • 36. Overview Overview Test Doubles def "should apply correct discount for a valid discount code"() { given: PriceService priceService = ??? OrderProcessor processor = new OrderProcessor(priceService); and: def mouse = new Product("Wireless Mouse", 20.00); def tenPercentOffPrice = 18.00; when: def order = ProductOrder.discountedOrder(mouse, “MINUS_10_PERCENT"); def discountedPrice = processor.calculateTotalPriceFor(order) then: discountedPrice == tenPercentOffPrice; } How do we get a PriceService?
  • 37. Overview Overview Test Doubles class PriceServiceTestDouble extends PriceService { private final double discount; TestPriceService(double discount) { this.discount = discount; } @Override public double percentageDiscountForCode(String discountCode) { return discount; } } A test double
  • 38. Overview Overview Test Doubles def "should apply correct discount for a valid discount code"() { given: PriceService priceService = new PriceServiceTestDouble(0.20) OrderProcessor processor = new OrderProcessor(priceService); and: def mouse = new Product("Wireless Mouse", 20.00); def tenPercentOffPrice = 18.00; when: def order = ProductOrder.discountedOrder(mouse, “MINUS_10_PERCENT"); def discountedPrice = processor.calculateTotalPriceFor(order) then: discountedPrice == tenPercentOffPrice; } Now use the test double
  • 39. Overview Overview Test Doubles ✓ There are several types of Test Doubles ✓ Stubs ✓ Fakes ✓ Mocks
  • 40. Overview Overview Test Doubles ✓ Stubs ✓ Minimal implementation ✓ Returns hard-coded values ✓ State-based testing class PriceServiceTestDouble extends PriceService { @Override public double percentageDiscountForCode(String discountCode) { return 10; } }
  • 41. Overview Overview Test Doubles ✓ Fakes ✓ Smarter stubs ✓ Some simple behaviour ✓ State-based testing public class MockEnvironmentVariables implements EnvironmentVariables { private Properties properties = new Properties(); public String getProperty(String name) { return properties.getProperty(name); } public String getProperty(String name, String defaultValue) { return properties.getProperty(name, defaultValue); } public void setProperty(String name, String value) { properties.setProperty(name, value); } public void setValue(String name, String value) { values.setProperty(name, value); } }
  • 42. Overview Overview Test Doubles ✓ Mocks ✓ Sophisticated stubs ✓ Record and verify interactions ✓ Need a mocking framework
  • 43. Overview Overview Test Doubles ✓ Mocks ✓ Only mock what you own ✓ Verify specification, not implementation ✓ Specify as little as possible in a test ✓ Only mock your immediate neighbours ✓ Don’t mock boundary objects ✓ Don’t mock domain objects ✓ Keep it simple
  • 44. Overview Overview Test Doubles in Spock def vatService = Mock(VATService) def seller = new Seller(vatService); def “VAT should apply on ordinary articles"() { given: "the VAT rate for shirts is 20%" seller = new Seller(vatService); vatService.getRateFor("shirt") >> 0.2 and: "we are selling a shirt" def sale = seller.sells(1, "shirt").forANetPriceOf(10.00) when: "we calculate the price including VAT" def totalPrice = sale.totalPrice then: "the price should include GST of 20%" totalPrice == 12.00 } The seller thinks he is using a real VATService Create a mocked version of the VATService class Stubbed return value for the getRateFor() method
  • 45. Overview Overview Test Doubles in Spock def vatService = Mock(VATService) … VATService vatService = Mock() You can create a mock object in two ways (This way is more IDE-friendly) def vatService = Mock(VATService) … VATService vatService = Mock() Creating Mocks
  • 46. Overview Overview Test Doubles in Spock def vatService = Stub(VATService) … VATService vatService = Stub() A Stub returns empty or “dummy” responses for all method calls (This way is more IDE-friendly) Creating Stubs
  • 47. Overview Overview Test Doubles in Spock vatService.getRateFor("shirt") >> 0.2 Mock the result of a particular method call Return different values on successive calls vatService.getRateFor("shirt") >>> [0.2, 0.1] vatService.getRateFor("shirt") >> { throw new UnknownProductException()} Do something in a closure Mocking interactions VATService vatService = Mock() { getRateFor("shirt") >> 0.2 getRateFor("banana") >> 0.1 } Mock the interactions at creation time
  • 48. Overview Overview Test Doubles in Spock def "The VAT Service should be used to obtain VAT values"() { given: "the GST rate for shirts is 10%" seller = new Seller(vatService); vatService.getRateFor("shirt") >> 0.1 when: "we sell a shirt" seller.sells(1, "shirt").forANetPriceOf(10.00) then: "the VAT service should be used to determine the GST rate" 1 * vatService.getRateFor("shirt") } This method should be called exactly once Checking interactions
  • 49. Overview Overview Interactions in Spock 1 * vatService.getRateFor("shirt") // Exactly once with this parameter (1.._) * vatService.getRateFor("shirt") // At least once (_..2) * vatService.getRateFor("shirt") // No more than twice vatService.getRateFor(_) // Called with any parameter vatService.getRateFor({it != "banana"}) // Custom constraint Checking interactions
  • 50. Overview Overview Documentation def "withdrawing funds from a current account"() { given: "a current account with €100" def accountNumber = accountService.createNewAccount(Current, 100.00) when: "we withdraw €50" accountService.makeWithdrawal(accountNumber, 50.00, LocalDate.now()) then: "the balance should be €50" accountService.getBalance(accountNumber) == 50.00 } Descriptive texts go after the labels
  • 51. Thank You! AUTHOR OF ‘BDD IN ACTION’ @wakaleo www.johnfergusonsmart.com reachme@johnfergusonsmart.com