SlideShare a Scribd company logo
1 of 100
Download to read offline
Driving Design through Examples
Ciaran McNulty at PHPNW 2015
Modelling by
Example
Combining BDD and DDD
concepts
Behaviour
Driven
Development
BDD is the art of using
examples in
conversations to
illustrate behaviour
— Liz Keogh
Why
Examples?
Requirements as Rules
We are starting a new budget airline flying between
London and Manchester
→ Travellers can collect 1 point for every £1 they
spend on flights
→ 100 points can be redeemed for £10 off a future
flight
→ Flights are taxed at 20%
Rules are
Ambiguous
Ambiguity
→ When redeeming points do I still earn points?
→ Can I redeem more than 100 points on one flight?
→ Does tax apply to the discounted fare or the full
fare?
Examples are
Unambiguous
Examples
If a flight from London to Manchester costs £50:
→ If you pay cash it will cost £50 + £10 tax, and you
will earn 50 new points
→ If you pay entirely with points it will cost 500
points + £10 tax and you will earn 0 new points
→ If you pay with 100 points it will cost 100 points +
£40 + £10 tax and you will earn 0 new points
Examples are
Objectively
Testable
Gherkin
A formal language for
examples
Feature: Earning and spending points on flights
Rules:
- Travellers can collect 1 point for every £1 they spend on flights
- 100 points can be redeemed for £10 off a future flight
Scenario: Earning points when paying cash
Given ...
Scenario: Redeeming points for a discount on a flight
Given ...
Scenario: Paying for a flight entirely using points
Given ...
Gherkin steps
→ Given sets up context for a behaviour
→ When specifies some action
→ Then specifies some outcome
Action + Outcome = Behaviour
Scenario: Earning points when paying cash
Given a flight costs £50
When I pay with cash
Then I should pay £50 for the flight
And I should pay £10 tax
And I should get 50 points
Scenario: Redeeming points for a discount on a flight
Given a flight costs £50
When I pay with cash plus 100 points
Then I should pay £40 for the flight
And I should pay £10 tax
And I should pay 100 points
Scenario: Paying for a flight entirely using points
Given a flight costs £50
When I pay with points only
Then I should pay £0 for the flight
And I should pay £10 tax
And I should pay 500 points
Who writes examples?
Business expert
Testing expert
Development expert
All discussing the feature together
When to write scenarios
→ Before you start work on the feature
→ Not too long before!
→ Whenever you have access to the right people
Refining scenarios
→ When would this outcome not be true?
→ What other outcomes are there?
→ But what would happen if...?
→ Does this implementation detail matter?
Scenarios are
not Contracts
Scenarios
→ Create a shared understanding of a feature
→ Give a starting definition of done
→ Provide an objective indication of how to test a
feature
Domain
Driven Design
DDD tackles complexity
by focusing the team's
attention on knowledge
of the domain
— Eric Evans
Invest time in
understanding
the business
Ubiquitous Language
→ A shared way of speaking about domain concepts
→ Reduces the cost of translation when business
and development communicate
→ Try to establish and use terms the business will
understand
Modelling by
Example
By embedding Ubiquitous
Language in your
scenarios, your scenarios
naturally become your
domain model
— Konstantin Kudryashov (@everzet)
Principles
→ The best way to understand the domain is by
discussing examples
→ Write scenarios that capture ubiquitous language
→ Write scenarios that illustrate real situations
→ Directly drive the code model from those
examples
Directly
driving code
with Behat?
Layered architecture
UI testing with Behat
UI tests
→ Slow to execute
→ Brittle
→ Don't let you think about the code
Test the domain first
Scenario: Earning points when paying cash
Given a flight costs £50
When I pay with cash
Then I should pay £50 for the flight
And I should pay £10 tax
And I should get 50 points
Scenario: Redeeming points for a discount on a flight
Given a flight costs £50
When I pay with cash plus 100 points
Then I should pay £40 for the flight
And I should pay £10 tax
And I should pay 100 points
Scenario: Paying for a flight entirely using points
Given a flight costs £50
When I pay with points only
Then I should pay £0 for the flight
And I should pay £10 tax
And I should pay 500 points
Add realistic
details
Background:
Given a flight from "London" to "Manchester" costs £50
Scenario: Earning points when paying cash
When I fly from "London" to "Manchester"
And I pay with cash
Then I should pay £50 for the flight
And I should pay £10 tax
And I should get 50 points
Actively seek
terms from
the domain
→ What words do you use to talk about these
things?
→ Points? Paying? Cash Fly?
→ Is the cost really attached to a flight?
→ Do you call this thing "tax"?
→ How do you think about these things?
Background:
Given a flight "XX-100" flies the "LHR" to "MAN" route
And the current listed fare for the "LHR" to "MAN" route is £50
Scenario: Earning points when paying cash
When I am issued a ticket on flight "XX-100"
And I pay £50 cash for the ticket
Then the ticket should be completely paid
And the ticket should be worth 50 loyalty points
Lessons from the conversation
→ Price belongs to a Fare for a specific Route
→ Flight is independently assigned to a Route
→ Some sort of fare listing system controls Fares
→ I get quoted a cost at the point I purchase a ticket
This is really useful to know!
Driving the
domain model
with Behat
Configure a Behat suite
default:
suites:
core:
contexts: [ FlightsContext ]
Create a context
class FlightsContext implements Context
{
/**
* @Given a flight :arg1 flies the :arg2 to :arg3 route
*/
public function aFlightGoesFromTo($arg1, $arg2, $arg3)
{
throw new PendingException();
}
// ...
}
Run Behat
Model values
as Value
Objects
class FlightsContext implements Context
{
/**
* @Given a flight :flightnumber flies the :origin to :destination route
*/
public function aFlightGoesFromTo($flightnumber, $origin, $destination)
{
$this->flight = new Flight(
FlightNumber::fromString($flightnumber),
Route::between(
Airport::fromCode($origin),
Airport::fromCode($destination)
)
);
}
// ...
}
Transformations
/**
* @Transform :flightnumber
*/
public function transformFlightNumber($number)
{
return FlightNumber::fromString($number);
}
/**
* @Transform :origin
* @Transform :destination
*/
public function transformAirport($code)
{
return Airport::fromCode($code);
}
/**
* @Given a flight :flightnumber flies the :origin to :destination route
*/
public function aFlightGoesFromTo(
FlightNumber $flightnumber,
Airport $origin,
Airport $destination
)
{
$this->flight = new Flight(
$flightnumber, Route::fromTo($origin, $destination)
);
}
> vendor/bin/behat
PHP Fatal error: Class 'Flight' not found
Describe
objects with
PhpSpec
class AirportSpec extends ObjectBehavior
{
function it_can_be_represented_as_a_string()
{
$this->beConstructedFromCode('LHR');
$this->asCode()->shouldReturn('LHR');
}
function it_cannot_be_created_with_invalid_code()
{
$this->beConstructedFromCode('1234566XXX');
$this->shouldThrow(Exception::class)->duringInstantiation();
}
}
class Airport
{
private $code;
private function __construct($code)
{
if (!preg_match('/^[A-Z]{3}$/', $code)) {
throw new InvalidArgumentException('Code is not valid');
}
$this->code = $code;
}
public static function fromCode($code)
{
return new Airport($code);
}
public function asCode()
{
return $this->code;
}
}
/**
* @Given the current listed fare for the :arg1 to :arg2 route is £:arg3
*/
public function theCurrentListedFareForTheToRouteIsPs($arg1, $arg2, $arg3)
{
throw new PendingException();
}
Model boundaries
with Interfaces
interface FareList
{
public function listFare(Route $route, Fare $fare);
}
Create in-memory versions for testing
namespace Fake;
class FareList implements FareList
{
private $fares = [];
public function listFare(Route $route, Fare $fare)
{
$this->fares[$route->asString()] = $fare;
}
}
/**
* @Given the current listed fare for the :origin to :destination route is £:fare
*/
public function theCurrentListedFareForTheToRouteIsPs(
Airport $origin,
Airport $destination,
Fare $fare
)
{
$this->fareList = new FakeFareList();
$this->fareList->listFare(
Route::fromTo($origin,$destination),
Fare::fromString($fare)
);
}
Run Behat
/**
* @When Iam issued a ticket on flight :arg1
*/
public function iAmIssuedATicketOnFlight($arg1)
{
throw new PendingException();
}
/**
* @When I am issued a ticket on flight :flight
*/
public function iAmIssuedATicketOnFlight()
{
$ticketIssuer = new TicketIssuer($this->fareList);
$this->ticket = $ticketIssuer->issueOn($this->flight);
}
> vendor/bin/behat
PHP Fatal error: Class 'TicketIssuer' not found
class TicketIssuerSpec extends ObjectBehavior
{
function it_can_issue_a_ticket_for_a_flight(Flight $flight)
{
$this->issueOn($flight)->shouldHaveType(Ticket::class);
}
}
class TicketIssuer
{
public function issueOn(Flight $flight)
{
return Ticket::costing(Fare::fromString('10000.00'));
}
}
Run Behat
/**
* @When I pay £:fare cash for the ticket
*/
public function iPayPsCashForTheTicket(Fare $fare)
{
$this->ticket->pay($fare);
}
PHP Fatal error: Call to undefined method Ticket::pay()
class TicketSpec extends ObjectBehavior
{
function it_can_be_paid()
{
$this->pay(Fare::fromString("10.00"));
}
}
class Ticket
{
public function pay(Fare $fare)
{
}
}
Run Behat
The model will be
anaemicUntil you get to Then
/**
* @Then the ticket should be completely paid
*/
public function theTicketShouldBeCompletelyPaid()
{
throw new PendingException();
}
/**
* @Then the ticket should be completely paid
*/
public function theTicketShouldBeCompletelyPaid()
{
assert($this->ticket->isCompletelyPaid() == true);
}
PHP Fatal error: Call to undefined method Ticket::isCompletelyPaid()
class TicketSpec extends ObjectBehavior
{
function let()
{
$this->beConstructedCosting(Fare::fromString("50.00"));
}
function it_is_not_completely_paid_initially()
{
$this->shouldNotBeCompletelyPaid();
}
function it_can_be_paid_completely()
{
$this->pay(Fare::fromString("50.00"));
$this->shouldBeCompletelyPaid();
}
}
class Ticket
{
private $fare;
// ...
public function pay(Fare $fare)
{
$this->fare = $fare->deduct($fare);
}
public function isCompletelyPaid()
{
return $this->fare->isZero();
}
}
class FareSpec extends ObjectBehavior
{
function let()
{
$this->beConstructedFromString('100.00');
}
function it_can_deduct_an_amount()
{
$this->deduct(Fare::fromString('10'))->shouldBeLike(Fare::fromString('90.00'));
}
}
class Fare
{
private $pence;
private function __construct($pence)
{
$this->pence = $pence;
}
// ...
public function deduct(Fare $amount)
{
return new self($this->pence - $amount->pence);
}
}
class FareSpec extends ObjectBehavior
{
// ...
function it_knows_when_it_is_zero()
{
$this->beConstructedFromString('0.00');
$this->isZero()->shouldReturn(true);
}
function it_is_not_zero_when_it_has_a_value()
{
$this->beConstructedFromString('10.00');
$this->isZero()->shouldReturn(false);
}
}
class Fare
{
private $pence;
private function __construct($pence)
{
$this->pence = $pence;
}
// ...
public function isZero()
{
return $this->pence == 0;
}
}
Run Behat
class TicketIssuerSpec extends ObjectBehavior
{
function it_issues_a_ticket_with_the_correct_fare(FareList $fareList)
{
$route = Route::fromTo(Airport::fromCode('LHR'), Airport::fromCode('MAN'));
$flight = new Flight(FlightNumber::fromString('XX001'), $route);
$fareList->findFareFor($route)->willReturn(Fare::fromString('50'));
$this->beConstructedWith($fareList);
$this->issueOn($flight)->shouldBeLike(Ticket::costing(Fare::fromString('50')));
}
}
class TicketIssuer
{
private $fareList;
public function __construct(FareList $fareList)
{
$this->fareList = $fareList;
}
public function issueOn(Flight $flight)
{
return Ticket::costing($this->fareList->findFareFor($flight->getRoute()));
}
}
interface FareList
{
public function listFare(Route $route, Fare $fare);
public function findFareFor(Route $route);
}
class FareList implements FareList
{
private $fares = [];
public function listFare(Route $route, Fare $fare)
{
$this->fares[$route->asString()] = $fare;
}
public function findFareFor(Route $route)
{
return $this->fares[$route->asString()];
}
}
Run Behat
/**
* @Then I the ticket should be worth :points loyalty points
*/
public function iTheTicketShouldBeWorthLoyaltyPoints(Points $points)
{
assert($this->ticket->getPoints() == $points);
}
class FareSpec extends ObjectBehavior
{
function let()
{
$this->beConstructedFromString('100.00');
}
// ...
function it_calculates_points()
{
$this->getPoints()->shouldBeLike(Points::fromString('100'));
}
}
class TicketSpec extends ObjectBehavior
{
function let()
{
$this->beConstructedCosting(Fare::fromString("100.00"));
}
// ...
function it_gets_points_from_original_fare()
{
$this->pay(Fare::fromString("50"));
$this->getPoints()->shouldBeLike(Points::fromString('100'));
}
}
<?php
class Ticket
{
private $revenueFare;
private $fare;
private function __construct(Fare $fare)
{
$this->revenueFare = $fare;
$this->fare = $fare;
}
// ...
public function getPoints()
{
return $this->revenueFare->getPoints();
}
}
Run Behat
Where is our
domain
model?
Feature: Earning and spending points on flights
Rules:
- Travellers can collect 1 point for every £1 they spend on flights
- 100 points can be redeemed for £10 off a future flight
Background:
Given a flight "XX-100" flies the "LHR" to "MAN" route
And the current listed fare for the "LHR" to "MAN" route is £50
Scenario: Earning points when paying cash
When I am issued a ticket on flight "XX-100"
And I pay £50 cash for the ticket
Then the ticket should be completely paid
And I the ticket should be worth 50 loyalty points
> bin/phpspec run -f pretty
Airport
10 ✔ can be represented as a string
16 ✔ cannot be created with invalid code
Fare
15 ✔ can deduct an amount
20 ✔ knows when it is zero
26 ✔ is not zero when it has a value
31 ✔ calculates points
FlightNumber
10 ✔ can be represented as a string
Flight
13 ✔ exposes route
Points
10 ✔ is constructed from string
Route
12 ✔ has a string representation
TicketIssuer
16 ✔ issues a ticket with the correct fare
Ticket
15 ✔ is not completely paid initially
20 ✔ is not paid completely if it is partly paid
27 ✔ can be paid completely
34 ✔ gets points from original fare
UI Testing
default:
suites:
core:
contexts: [ FlightsContext ]
web:
contexts: [ WebFlightsContext ]
filters: { tags: @ui }
With the domain already modelled
→ UI tests do not have to be comprehensive
→ Can focus on intractions and UX
→ Actual UI code is easier to write!
Modelling by Example
→ Focuses attention on use cases
→ Helps developers understand core business
domains
→ Encourages layered architecture
→ Speeds up test suites
Use it when
→ Project is core to your business
→ You are likely to support business changes in
future
→ You can have conversations with stakeholders
Do not use when...
→ Not core to the business
→ Prototype or short-term project
→ It can be thrown away when the business
changes
→ You have no access to business experts (but try
and change this)
Thank you!
→ @ciaranmcnulty
→ Lead Maintainer of PhpSpec
→ Senior Trainer at:
Inviqa / Sensio Labs UK / Session Digital / iKOS
→ https://joind.in/talk/view/15436
Questions?

More Related Content

What's hot

Урок 03.7 Підготовка вогневої позиції
Урок 03.7 Підготовка вогневої позиціїУрок 03.7 Підготовка вогневої позиції
Урок 03.7 Підготовка вогневої позиціїКовпитська ЗОШ
 
10-day MBA by Nay Zin Latt.pdf
10-day MBA by Nay Zin Latt.pdf10-day MBA by Nay Zin Latt.pdf
10-day MBA by Nay Zin Latt.pdfNay Aung
 
Cardiovascular Embryology and Congenital abnormalities
Cardiovascular Embryology and Congenital abnormalitiesCardiovascular Embryology and Congenital abnormalities
Cardiovascular Embryology and Congenital abnormalitiesmeducationdotnet
 
Development of liver, pancreas, spleen and extrahepatic biliary apparatus
Development of liver, pancreas, spleen and extrahepatic biliary apparatusDevelopment of liver, pancreas, spleen and extrahepatic biliary apparatus
Development of liver, pancreas, spleen and extrahepatic biliary apparatusSaachiGupta4
 
Blood Supply of Long Bone and Osteomyelitis by Aakash Pandit
Blood Supply of Long Bone and Osteomyelitis by Aakash PanditBlood Supply of Long Bone and Osteomyelitis by Aakash Pandit
Blood Supply of Long Bone and Osteomyelitis by Aakash PanditChitwan Medical College
 

What's hot (13)

structure of heart
structure of heartstructure of heart
structure of heart
 
Урок 03.7 Підготовка вогневої позиції
Урок 03.7 Підготовка вогневої позиціїУрок 03.7 Підготовка вогневої позиції
Урок 03.7 Підготовка вогневої позиції
 
Pectoral region
Pectoral regionPectoral region
Pectoral region
 
6 development of the heart akd
6 development of the  heart akd6 development of the  heart akd
6 development of the heart akd
 
Anatomy of heart
Anatomy of heartAnatomy of heart
Anatomy of heart
 
Cvs dev
Cvs devCvs dev
Cvs dev
 
Hip bone part 1
Hip bone part  1Hip bone part  1
Hip bone part 1
 
10-day MBA by Nay Zin Latt.pdf
10-day MBA by Nay Zin Latt.pdf10-day MBA by Nay Zin Latt.pdf
10-day MBA by Nay Zin Latt.pdf
 
Expt b1 diffusion in beetroot modified
Expt b1   diffusion in beetroot modifiedExpt b1   diffusion in beetroot modified
Expt b1 diffusion in beetroot modified
 
Cardiovascular Embryology and Congenital abnormalities
Cardiovascular Embryology and Congenital abnormalitiesCardiovascular Embryology and Congenital abnormalities
Cardiovascular Embryology and Congenital abnormalities
 
Development of liver, pancreas, spleen and extrahepatic biliary apparatus
Development of liver, pancreas, spleen and extrahepatic biliary apparatusDevelopment of liver, pancreas, spleen and extrahepatic biliary apparatus
Development of liver, pancreas, spleen and extrahepatic biliary apparatus
 
Shoulder joint
Shoulder jointShoulder joint
Shoulder joint
 
Blood Supply of Long Bone and Osteomyelitis by Aakash Pandit
Blood Supply of Long Bone and Osteomyelitis by Aakash PanditBlood Supply of Long Bone and Osteomyelitis by Aakash Pandit
Blood Supply of Long Bone and Osteomyelitis by Aakash Pandit
 

Viewers also liked

Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)James Titcumb
 
Hexagonal architecture message-oriented software design
Hexagonal architecture   message-oriented software designHexagonal architecture   message-oriented software design
Hexagonal architecture message-oriented software designMatthias Noback
 
Get Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP StreamsGet Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP StreamsDavey Shafik
 
Techniques d'accélération des pages web
Techniques d'accélération des pages webTechniques d'accélération des pages web
Techniques d'accélération des pages webJean-Pierre Vincent
 
Automation using-phing
Automation using-phingAutomation using-phing
Automation using-phingRajat Pandit
 
Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP GeneratorsMark Baker
 
The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)Matthias Noback
 
Top tips my_sql_performance
Top tips my_sql_performanceTop tips my_sql_performance
Top tips my_sql_performanceafup Paris
 
Understanding Craftsmanship SwanseaCon2015
Understanding Craftsmanship SwanseaCon2015Understanding Craftsmanship SwanseaCon2015
Understanding Craftsmanship SwanseaCon2015Marcello Duarte
 
Why elasticsearch rocks!
Why elasticsearch rocks!Why elasticsearch rocks!
Why elasticsearch rocks!tlrx
 
Si le tdd est mort alors pratiquons une autopsie mix-it 2015
Si le tdd est mort alors pratiquons une autopsie mix-it 2015Si le tdd est mort alors pratiquons une autopsie mix-it 2015
Si le tdd est mort alors pratiquons une autopsie mix-it 2015Bruno Boucard
 
Writing infinite scalability web applications with PHP and PostgreSQL
Writing infinite scalability web applications with PHP and PostgreSQLWriting infinite scalability web applications with PHP and PostgreSQL
Writing infinite scalability web applications with PHP and PostgreSQLGabriele Bartolini
 
L'ABC du BDD (Behavior Driven Development)
L'ABC du BDD (Behavior Driven Development)L'ABC du BDD (Behavior Driven Development)
L'ABC du BDD (Behavior Driven Development)Arnauld Loyer
 
Performance serveur et apache
Performance serveur et apachePerformance serveur et apache
Performance serveur et apacheafup Paris
 
TDD with PhpSpec - Lone Star PHP 2016
TDD with PhpSpec - Lone Star PHP 2016TDD with PhpSpec - Lone Star PHP 2016
TDD with PhpSpec - Lone Star PHP 2016CiaranMcNulty
 

Viewers also liked (20)

Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
 
Hexagonal architecture message-oriented software design
Hexagonal architecture   message-oriented software designHexagonal architecture   message-oriented software design
Hexagonal architecture message-oriented software design
 
Elastic Searching With PHP
Elastic Searching With PHPElastic Searching With PHP
Elastic Searching With PHP
 
Get Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP StreamsGet Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP Streams
 
Techniques d'accélération des pages web
Techniques d'accélération des pages webTechniques d'accélération des pages web
Techniques d'accélération des pages web
 
Diving deep into twig
Diving deep into twigDiving deep into twig
Diving deep into twig
 
PHP5.5 is Here
PHP5.5 is HerePHP5.5 is Here
PHP5.5 is Here
 
Automation using-phing
Automation using-phingAutomation using-phing
Automation using-phing
 
Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP Generators
 
The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)
 
Top tips my_sql_performance
Top tips my_sql_performanceTop tips my_sql_performance
Top tips my_sql_performance
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
 
Understanding Craftsmanship SwanseaCon2015
Understanding Craftsmanship SwanseaCon2015Understanding Craftsmanship SwanseaCon2015
Understanding Craftsmanship SwanseaCon2015
 
Why elasticsearch rocks!
Why elasticsearch rocks!Why elasticsearch rocks!
Why elasticsearch rocks!
 
Si le tdd est mort alors pratiquons une autopsie mix-it 2015
Si le tdd est mort alors pratiquons une autopsie mix-it 2015Si le tdd est mort alors pratiquons une autopsie mix-it 2015
Si le tdd est mort alors pratiquons une autopsie mix-it 2015
 
Writing infinite scalability web applications with PHP and PostgreSQL
Writing infinite scalability web applications with PHP and PostgreSQLWriting infinite scalability web applications with PHP and PostgreSQL
Writing infinite scalability web applications with PHP and PostgreSQL
 
L'ABC du BDD (Behavior Driven Development)
L'ABC du BDD (Behavior Driven Development)L'ABC du BDD (Behavior Driven Development)
L'ABC du BDD (Behavior Driven Development)
 
Performance serveur et apache
Performance serveur et apachePerformance serveur et apache
Performance serveur et apache
 
Behat 3.0 meetup (March)
Behat 3.0 meetup (March)Behat 3.0 meetup (March)
Behat 3.0 meetup (March)
 
TDD with PhpSpec - Lone Star PHP 2016
TDD with PhpSpec - Lone Star PHP 2016TDD with PhpSpec - Lone Star PHP 2016
TDD with PhpSpec - Lone Star PHP 2016
 

Similar to Driving Design through Examples

Driving Design through Examples - PhpCon PL 2015
Driving Design through Examples - PhpCon PL 2015Driving Design through Examples - PhpCon PL 2015
Driving Design through Examples - PhpCon PL 2015CiaranMcNulty
 
Driving Design through Examples
Driving Design through ExamplesDriving Design through Examples
Driving Design through ExamplesCiaranMcNulty
 
Driving Design through Examples
Driving Design through ExamplesDriving Design through Examples
Driving Design through ExamplesCiaranMcNulty
 
Assignment 1 Expository Essay IntroductionIn this assignment, .docx
Assignment 1 Expository Essay IntroductionIn this assignment, .docxAssignment 1 Expository Essay IntroductionIn this assignment, .docx
Assignment 1 Expository Essay IntroductionIn this assignment, .docxsherni1
 
Modelling by Example Workshop - PHPNW 2016
Modelling by Example Workshop - PHPNW 2016Modelling by Example Workshop - PHPNW 2016
Modelling by Example Workshop - PHPNW 2016CiaranMcNulty
 
A Self Replicating Serverless Function
A Self Replicating Serverless FunctionA Self Replicating Serverless Function
A Self Replicating Serverless FunctionMichael Adda
 
Refactor your Specs - 2017 Edition
Refactor your Specs - 2017 EditionRefactor your Specs - 2017 Edition
Refactor your Specs - 2017 EditionCyrille Martraire
 
Rethinking flight shopping with conversational search with chatbots
Rethinking flight shopping with conversational search with chatbotsRethinking flight shopping with conversational search with chatbots
Rethinking flight shopping with conversational search with chatbotsmisaello
 
BDD - you're doing it all wrong!
BDD - you're doing it all wrong!BDD - you're doing it all wrong!
BDD - you're doing it all wrong!Andrew Larcombe
 
Fly In Style (without splashing out)
Fly In Style (without splashing out)Fly In Style (without splashing out)
Fly In Style (without splashing out)CiaranMcNulty
 
4Developers 2015: Jak (w końcu) zacząć pracować z DDD wykorzystując BDD - Kac...
4Developers 2015: Jak (w końcu) zacząć pracować z DDD wykorzystując BDD - Kac...4Developers 2015: Jak (w końcu) zacząć pracować z DDD wykorzystując BDD - Kac...
4Developers 2015: Jak (w końcu) zacząć pracować z DDD wykorzystując BDD - Kac...PROIDEA
 
Rethinking Flight Shopping With Conversational Search... With Chatbots
Rethinking Flight Shopping With Conversational Search... With ChatbotsRethinking Flight Shopping With Conversational Search... With Chatbots
Rethinking Flight Shopping With Conversational Search... With ChatbotsNearsoft
 
Code is not text! How graph technologies can help us to understand our code b...
Code is not text! How graph technologies can help us to understand our code b...Code is not text! How graph technologies can help us to understand our code b...
Code is not text! How graph technologies can help us to understand our code b...Andreas Dewes
 

Similar to Driving Design through Examples (17)

Driving Design through Examples - PhpCon PL 2015
Driving Design through Examples - PhpCon PL 2015Driving Design through Examples - PhpCon PL 2015
Driving Design through Examples - PhpCon PL 2015
 
Driving Design through Examples
Driving Design through ExamplesDriving Design through Examples
Driving Design through Examples
 
Driving Design through Examples
Driving Design through ExamplesDriving Design through Examples
Driving Design through Examples
 
Cucumber and Spock Primer
Cucumber and Spock PrimerCucumber and Spock Primer
Cucumber and Spock Primer
 
Feature Mapping Workshop
Feature Mapping WorkshopFeature Mapping Workshop
Feature Mapping Workshop
 
Assignment 1 Expository Essay IntroductionIn this assignment, .docx
Assignment 1 Expository Essay IntroductionIn this assignment, .docxAssignment 1 Expository Essay IntroductionIn this assignment, .docx
Assignment 1 Expository Essay IntroductionIn this assignment, .docx
 
Modelling by Example Workshop - PHPNW 2016
Modelling by Example Workshop - PHPNW 2016Modelling by Example Workshop - PHPNW 2016
Modelling by Example Workshop - PHPNW 2016
 
A Self Replicating Serverless Function
A Self Replicating Serverless FunctionA Self Replicating Serverless Function
A Self Replicating Serverless Function
 
Refactor your Specs - 2017 Edition
Refactor your Specs - 2017 EditionRefactor your Specs - 2017 Edition
Refactor your Specs - 2017 Edition
 
Rethinking flight shopping with conversational search with chatbots
Rethinking flight shopping with conversational search with chatbotsRethinking flight shopping with conversational search with chatbots
Rethinking flight shopping with conversational search with chatbots
 
BDD - you're doing it all wrong!
BDD - you're doing it all wrong!BDD - you're doing it all wrong!
BDD - you're doing it all wrong!
 
PHP and COM
PHP and COMPHP and COM
PHP and COM
 
Fly In Style (without splashing out)
Fly In Style (without splashing out)Fly In Style (without splashing out)
Fly In Style (without splashing out)
 
Oops presentation
Oops presentationOops presentation
Oops presentation
 
4Developers 2015: Jak (w końcu) zacząć pracować z DDD wykorzystując BDD - Kac...
4Developers 2015: Jak (w końcu) zacząć pracować z DDD wykorzystując BDD - Kac...4Developers 2015: Jak (w końcu) zacząć pracować z DDD wykorzystując BDD - Kac...
4Developers 2015: Jak (w końcu) zacząć pracować z DDD wykorzystując BDD - Kac...
 
Rethinking Flight Shopping With Conversational Search... With Chatbots
Rethinking Flight Shopping With Conversational Search... With ChatbotsRethinking Flight Shopping With Conversational Search... With Chatbots
Rethinking Flight Shopping With Conversational Search... With Chatbots
 
Code is not text! How graph technologies can help us to understand our code b...
Code is not text! How graph technologies can help us to understand our code b...Code is not text! How graph technologies can help us to understand our code b...
Code is not text! How graph technologies can help us to understand our code b...
 

More from CiaranMcNulty

Greener web development at PHP London
Greener web development at PHP LondonGreener web development at PHP London
Greener web development at PHP LondonCiaranMcNulty
 
Doodle Driven Development
Doodle Driven DevelopmentDoodle Driven Development
Doodle Driven DevelopmentCiaranMcNulty
 
Behat Best Practices with Symfony
Behat Best Practices with SymfonyBehat Best Practices with Symfony
Behat Best Practices with SymfonyCiaranMcNulty
 
Behat Best Practices
Behat Best PracticesBehat Best Practices
Behat Best PracticesCiaranMcNulty
 
Behat Best Practices with Symfony
Behat Best Practices with SymfonyBehat Best Practices with Symfony
Behat Best Practices with SymfonyCiaranMcNulty
 
Finding the Right Testing Tool for the Job
Finding the Right Testing Tool for the JobFinding the Right Testing Tool for the Job
Finding the Right Testing Tool for the JobCiaranMcNulty
 
Conscious Decoupling - Lone Star PHP
Conscious Decoupling - Lone Star PHPConscious Decoupling - Lone Star PHP
Conscious Decoupling - Lone Star PHPCiaranMcNulty
 
Why Your Test Suite Sucks - PHPCon PL 2015
Why Your Test Suite Sucks - PHPCon PL 2015Why Your Test Suite Sucks - PHPCon PL 2015
Why Your Test Suite Sucks - PHPCon PL 2015CiaranMcNulty
 
Building a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing StrategiesBuilding a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing StrategiesCiaranMcNulty
 
Why Your Test Suite Sucks
Why Your Test Suite SucksWhy Your Test Suite Sucks
Why Your Test Suite SucksCiaranMcNulty
 
Driving Design with PhpSpec
Driving Design with PhpSpecDriving Design with PhpSpec
Driving Design with PhpSpecCiaranMcNulty
 
Using HttpKernelInterface for Painless Integration
Using HttpKernelInterface for Painless IntegrationUsing HttpKernelInterface for Painless Integration
Using HttpKernelInterface for Painless IntegrationCiaranMcNulty
 

More from CiaranMcNulty (14)

Greener web development at PHP London
Greener web development at PHP LondonGreener web development at PHP London
Greener web development at PHP London
 
Doodle Driven Development
Doodle Driven DevelopmentDoodle Driven Development
Doodle Driven Development
 
Behat Best Practices with Symfony
Behat Best Practices with SymfonyBehat Best Practices with Symfony
Behat Best Practices with Symfony
 
Behat Best Practices
Behat Best PracticesBehat Best Practices
Behat Best Practices
 
Behat Best Practices with Symfony
Behat Best Practices with SymfonyBehat Best Practices with Symfony
Behat Best Practices with Symfony
 
Conscious Coupling
Conscious CouplingConscious Coupling
Conscious Coupling
 
Finding the Right Testing Tool for the Job
Finding the Right Testing Tool for the JobFinding the Right Testing Tool for the Job
Finding the Right Testing Tool for the Job
 
Conscious Decoupling - Lone Star PHP
Conscious Decoupling - Lone Star PHPConscious Decoupling - Lone Star PHP
Conscious Decoupling - Lone Star PHP
 
Why Your Test Suite Sucks - PHPCon PL 2015
Why Your Test Suite Sucks - PHPCon PL 2015Why Your Test Suite Sucks - PHPCon PL 2015
Why Your Test Suite Sucks - PHPCon PL 2015
 
Building a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing StrategiesBuilding a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing Strategies
 
TDD with PhpSpec
TDD with PhpSpecTDD with PhpSpec
TDD with PhpSpec
 
Why Your Test Suite Sucks
Why Your Test Suite SucksWhy Your Test Suite Sucks
Why Your Test Suite Sucks
 
Driving Design with PhpSpec
Driving Design with PhpSpecDriving Design with PhpSpec
Driving Design with PhpSpec
 
Using HttpKernelInterface for Painless Integration
Using HttpKernelInterface for Painless IntegrationUsing HttpKernelInterface for Painless Integration
Using HttpKernelInterface for Painless Integration
 

Recently uploaded

From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
Manual 508 Accessibility Compliance Audit
Manual 508 Accessibility Compliance AuditManual 508 Accessibility Compliance Audit
Manual 508 Accessibility Compliance AuditSkynet Technologies
 
Time Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsTime Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsNathaniel Shimoni
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxLoriGlavin3
 
Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rick Flair
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024Lonnie McRorey
 
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxLoriGlavin3
 
Scale your database traffic with Read & Write split using MySQL Router
Scale your database traffic with Read & Write split using MySQL RouterScale your database traffic with Read & Write split using MySQL Router
Scale your database traffic with Read & Write split using MySQL RouterMydbops
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersNicole Novielli
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity PlanDatabarracks
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteDianaGray10
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfpanagenda
 
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024BookNet Canada
 
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...Scott Andery
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersRaghuram Pandurangan
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024BookNet Canada
 
Testing tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesTesting tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesKari Kakkonen
 
Modern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better StrongerModern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better Strongerpanagenda
 
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Alkin Tezuysal
 
A Framework for Development in the AI Age
A Framework for Development in the AI AgeA Framework for Development in the AI Age
A Framework for Development in the AI AgeCprime
 

Recently uploaded (20)

From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
Manual 508 Accessibility Compliance Audit
Manual 508 Accessibility Compliance AuditManual 508 Accessibility Compliance Audit
Manual 508 Accessibility Compliance Audit
 
Time Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsTime Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directions
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptx
 
Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024
 
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
 
Scale your database traffic with Read & Write split using MySQL Router
Scale your database traffic with Read & Write split using MySQL RouterScale your database traffic with Read & Write split using MySQL Router
Scale your database traffic with Read & Write split using MySQL Router
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software Developers
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity Plan
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test Suite
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
 
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
 
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information Developers
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
 
Testing tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesTesting tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examples
 
Modern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better StrongerModern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
 
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
 
A Framework for Development in the AI Age
A Framework for Development in the AI AgeA Framework for Development in the AI Age
A Framework for Development in the AI Age
 

Driving Design through Examples

  • 1. Driving Design through Examples Ciaran McNulty at PHPNW 2015
  • 4. BDD is the art of using examples in conversations to illustrate behaviour — Liz Keogh
  • 6. Requirements as Rules We are starting a new budget airline flying between London and Manchester → Travellers can collect 1 point for every £1 they spend on flights → 100 points can be redeemed for £10 off a future flight → Flights are taxed at 20%
  • 8. Ambiguity → When redeeming points do I still earn points? → Can I redeem more than 100 points on one flight? → Does tax apply to the discounted fare or the full fare?
  • 10. Examples If a flight from London to Manchester costs £50: → If you pay cash it will cost £50 + £10 tax, and you will earn 50 new points → If you pay entirely with points it will cost 500 points + £10 tax and you will earn 0 new points → If you pay with 100 points it will cost 100 points + £40 + £10 tax and you will earn 0 new points
  • 13. Feature: Earning and spending points on flights Rules: - Travellers can collect 1 point for every £1 they spend on flights - 100 points can be redeemed for £10 off a future flight Scenario: Earning points when paying cash Given ... Scenario: Redeeming points for a discount on a flight Given ... Scenario: Paying for a flight entirely using points Given ...
  • 14. Gherkin steps → Given sets up context for a behaviour → When specifies some action → Then specifies some outcome Action + Outcome = Behaviour
  • 15. Scenario: Earning points when paying cash Given a flight costs £50 When I pay with cash Then I should pay £50 for the flight And I should pay £10 tax And I should get 50 points Scenario: Redeeming points for a discount on a flight Given a flight costs £50 When I pay with cash plus 100 points Then I should pay £40 for the flight And I should pay £10 tax And I should pay 100 points Scenario: Paying for a flight entirely using points Given a flight costs £50 When I pay with points only Then I should pay £0 for the flight And I should pay £10 tax And I should pay 500 points
  • 16. Who writes examples? Business expert Testing expert Development expert All discussing the feature together
  • 17. When to write scenarios → Before you start work on the feature → Not too long before! → Whenever you have access to the right people
  • 18. Refining scenarios → When would this outcome not be true? → What other outcomes are there? → But what would happen if...? → Does this implementation detail matter?
  • 20. Scenarios → Create a shared understanding of a feature → Give a starting definition of done → Provide an objective indication of how to test a feature
  • 22. DDD tackles complexity by focusing the team's attention on knowledge of the domain — Eric Evans
  • 24. Ubiquitous Language → A shared way of speaking about domain concepts → Reduces the cost of translation when business and development communicate → Try to establish and use terms the business will understand
  • 26. By embedding Ubiquitous Language in your scenarios, your scenarios naturally become your domain model — Konstantin Kudryashov (@everzet)
  • 27. Principles → The best way to understand the domain is by discussing examples → Write scenarios that capture ubiquitous language → Write scenarios that illustrate real situations → Directly drive the code model from those examples
  • 31. UI tests → Slow to execute → Brittle → Don't let you think about the code
  • 32. Test the domain first
  • 33. Scenario: Earning points when paying cash Given a flight costs £50 When I pay with cash Then I should pay £50 for the flight And I should pay £10 tax And I should get 50 points Scenario: Redeeming points for a discount on a flight Given a flight costs £50 When I pay with cash plus 100 points Then I should pay £40 for the flight And I should pay £10 tax And I should pay 100 points Scenario: Paying for a flight entirely using points Given a flight costs £50 When I pay with points only Then I should pay £0 for the flight And I should pay £10 tax And I should pay 500 points
  • 35. Background: Given a flight from "London" to "Manchester" costs £50 Scenario: Earning points when paying cash When I fly from "London" to "Manchester" And I pay with cash Then I should pay £50 for the flight And I should pay £10 tax And I should get 50 points
  • 37. → What words do you use to talk about these things? → Points? Paying? Cash Fly? → Is the cost really attached to a flight? → Do you call this thing "tax"? → How do you think about these things?
  • 38. Background: Given a flight "XX-100" flies the "LHR" to "MAN" route And the current listed fare for the "LHR" to "MAN" route is £50 Scenario: Earning points when paying cash When I am issued a ticket on flight "XX-100" And I pay £50 cash for the ticket Then the ticket should be completely paid And the ticket should be worth 50 loyalty points
  • 39. Lessons from the conversation → Price belongs to a Fare for a specific Route → Flight is independently assigned to a Route → Some sort of fare listing system controls Fares → I get quoted a cost at the point I purchase a ticket This is really useful to know!
  • 41. Configure a Behat suite default: suites: core: contexts: [ FlightsContext ]
  • 42. Create a context class FlightsContext implements Context { /** * @Given a flight :arg1 flies the :arg2 to :arg3 route */ public function aFlightGoesFromTo($arg1, $arg2, $arg3) { throw new PendingException(); } // ... }
  • 45. class FlightsContext implements Context { /** * @Given a flight :flightnumber flies the :origin to :destination route */ public function aFlightGoesFromTo($flightnumber, $origin, $destination) { $this->flight = new Flight( FlightNumber::fromString($flightnumber), Route::between( Airport::fromCode($origin), Airport::fromCode($destination) ) ); } // ... }
  • 46. Transformations /** * @Transform :flightnumber */ public function transformFlightNumber($number) { return FlightNumber::fromString($number); } /** * @Transform :origin * @Transform :destination */ public function transformAirport($code) { return Airport::fromCode($code); }
  • 47. /** * @Given a flight :flightnumber flies the :origin to :destination route */ public function aFlightGoesFromTo( FlightNumber $flightnumber, Airport $origin, Airport $destination ) { $this->flight = new Flight( $flightnumber, Route::fromTo($origin, $destination) ); }
  • 48. > vendor/bin/behat PHP Fatal error: Class 'Flight' not found
  • 50. class AirportSpec extends ObjectBehavior { function it_can_be_represented_as_a_string() { $this->beConstructedFromCode('LHR'); $this->asCode()->shouldReturn('LHR'); } function it_cannot_be_created_with_invalid_code() { $this->beConstructedFromCode('1234566XXX'); $this->shouldThrow(Exception::class)->duringInstantiation(); } }
  • 51. class Airport { private $code; private function __construct($code) { if (!preg_match('/^[A-Z]{3}$/', $code)) { throw new InvalidArgumentException('Code is not valid'); } $this->code = $code; } public static function fromCode($code) { return new Airport($code); } public function asCode() { return $this->code; } }
  • 52.
  • 53. /** * @Given the current listed fare for the :arg1 to :arg2 route is £:arg3 */ public function theCurrentListedFareForTheToRouteIsPs($arg1, $arg2, $arg3) { throw new PendingException(); }
  • 55. interface FareList { public function listFare(Route $route, Fare $fare); }
  • 56. Create in-memory versions for testing namespace Fake; class FareList implements FareList { private $fares = []; public function listFare(Route $route, Fare $fare) { $this->fares[$route->asString()] = $fare; } }
  • 57. /** * @Given the current listed fare for the :origin to :destination route is £:fare */ public function theCurrentListedFareForTheToRouteIsPs( Airport $origin, Airport $destination, Fare $fare ) { $this->fareList = new FakeFareList(); $this->fareList->listFare( Route::fromTo($origin,$destination), Fare::fromString($fare) ); }
  • 59. /** * @When Iam issued a ticket on flight :arg1 */ public function iAmIssuedATicketOnFlight($arg1) { throw new PendingException(); }
  • 60. /** * @When I am issued a ticket on flight :flight */ public function iAmIssuedATicketOnFlight() { $ticketIssuer = new TicketIssuer($this->fareList); $this->ticket = $ticketIssuer->issueOn($this->flight); }
  • 61. > vendor/bin/behat PHP Fatal error: Class 'TicketIssuer' not found
  • 62. class TicketIssuerSpec extends ObjectBehavior { function it_can_issue_a_ticket_for_a_flight(Flight $flight) { $this->issueOn($flight)->shouldHaveType(Ticket::class); } }
  • 63. class TicketIssuer { public function issueOn(Flight $flight) { return Ticket::costing(Fare::fromString('10000.00')); } }
  • 65. /** * @When I pay £:fare cash for the ticket */ public function iPayPsCashForTheTicket(Fare $fare) { $this->ticket->pay($fare); }
  • 66. PHP Fatal error: Call to undefined method Ticket::pay()
  • 67. class TicketSpec extends ObjectBehavior { function it_can_be_paid() { $this->pay(Fare::fromString("10.00")); } }
  • 68. class Ticket { public function pay(Fare $fare) { } }
  • 70. The model will be anaemicUntil you get to Then
  • 71. /** * @Then the ticket should be completely paid */ public function theTicketShouldBeCompletelyPaid() { throw new PendingException(); }
  • 72. /** * @Then the ticket should be completely paid */ public function theTicketShouldBeCompletelyPaid() { assert($this->ticket->isCompletelyPaid() == true); }
  • 73. PHP Fatal error: Call to undefined method Ticket::isCompletelyPaid()
  • 74. class TicketSpec extends ObjectBehavior { function let() { $this->beConstructedCosting(Fare::fromString("50.00")); } function it_is_not_completely_paid_initially() { $this->shouldNotBeCompletelyPaid(); } function it_can_be_paid_completely() { $this->pay(Fare::fromString("50.00")); $this->shouldBeCompletelyPaid(); } }
  • 75. class Ticket { private $fare; // ... public function pay(Fare $fare) { $this->fare = $fare->deduct($fare); } public function isCompletelyPaid() { return $this->fare->isZero(); } }
  • 76. class FareSpec extends ObjectBehavior { function let() { $this->beConstructedFromString('100.00'); } function it_can_deduct_an_amount() { $this->deduct(Fare::fromString('10'))->shouldBeLike(Fare::fromString('90.00')); } }
  • 77. class Fare { private $pence; private function __construct($pence) { $this->pence = $pence; } // ... public function deduct(Fare $amount) { return new self($this->pence - $amount->pence); } }
  • 78. class FareSpec extends ObjectBehavior { // ... function it_knows_when_it_is_zero() { $this->beConstructedFromString('0.00'); $this->isZero()->shouldReturn(true); } function it_is_not_zero_when_it_has_a_value() { $this->beConstructedFromString('10.00'); $this->isZero()->shouldReturn(false); } }
  • 79. class Fare { private $pence; private function __construct($pence) { $this->pence = $pence; } // ... public function isZero() { return $this->pence == 0; } }
  • 81. class TicketIssuerSpec extends ObjectBehavior { function it_issues_a_ticket_with_the_correct_fare(FareList $fareList) { $route = Route::fromTo(Airport::fromCode('LHR'), Airport::fromCode('MAN')); $flight = new Flight(FlightNumber::fromString('XX001'), $route); $fareList->findFareFor($route)->willReturn(Fare::fromString('50')); $this->beConstructedWith($fareList); $this->issueOn($flight)->shouldBeLike(Ticket::costing(Fare::fromString('50'))); } }
  • 82. class TicketIssuer { private $fareList; public function __construct(FareList $fareList) { $this->fareList = $fareList; } public function issueOn(Flight $flight) { return Ticket::costing($this->fareList->findFareFor($flight->getRoute())); } }
  • 83. interface FareList { public function listFare(Route $route, Fare $fare); public function findFareFor(Route $route); }
  • 84. class FareList implements FareList { private $fares = []; public function listFare(Route $route, Fare $fare) { $this->fares[$route->asString()] = $fare; } public function findFareFor(Route $route) { return $this->fares[$route->asString()]; } }
  • 86. /** * @Then I the ticket should be worth :points loyalty points */ public function iTheTicketShouldBeWorthLoyaltyPoints(Points $points) { assert($this->ticket->getPoints() == $points); }
  • 87. class FareSpec extends ObjectBehavior { function let() { $this->beConstructedFromString('100.00'); } // ... function it_calculates_points() { $this->getPoints()->shouldBeLike(Points::fromString('100')); } }
  • 88. class TicketSpec extends ObjectBehavior { function let() { $this->beConstructedCosting(Fare::fromString("100.00")); } // ... function it_gets_points_from_original_fare() { $this->pay(Fare::fromString("50")); $this->getPoints()->shouldBeLike(Points::fromString('100')); } }
  • 89. <?php class Ticket { private $revenueFare; private $fare; private function __construct(Fare $fare) { $this->revenueFare = $fare; $this->fare = $fare; } // ... public function getPoints() { return $this->revenueFare->getPoints(); } }
  • 92. Feature: Earning and spending points on flights Rules: - Travellers can collect 1 point for every £1 they spend on flights - 100 points can be redeemed for £10 off a future flight Background: Given a flight "XX-100" flies the "LHR" to "MAN" route And the current listed fare for the "LHR" to "MAN" route is £50 Scenario: Earning points when paying cash When I am issued a ticket on flight "XX-100" And I pay £50 cash for the ticket Then the ticket should be completely paid And I the ticket should be worth 50 loyalty points
  • 93. > bin/phpspec run -f pretty Airport 10 ✔ can be represented as a string 16 ✔ cannot be created with invalid code Fare 15 ✔ can deduct an amount 20 ✔ knows when it is zero 26 ✔ is not zero when it has a value 31 ✔ calculates points FlightNumber 10 ✔ can be represented as a string Flight 13 ✔ exposes route Points 10 ✔ is constructed from string Route 12 ✔ has a string representation TicketIssuer 16 ✔ issues a ticket with the correct fare Ticket 15 ✔ is not completely paid initially 20 ✔ is not paid completely if it is partly paid 27 ✔ can be paid completely 34 ✔ gets points from original fare
  • 95. default: suites: core: contexts: [ FlightsContext ] web: contexts: [ WebFlightsContext ] filters: { tags: @ui }
  • 96. With the domain already modelled → UI tests do not have to be comprehensive → Can focus on intractions and UX → Actual UI code is easier to write!
  • 97. Modelling by Example → Focuses attention on use cases → Helps developers understand core business domains → Encourages layered architecture → Speeds up test suites
  • 98. Use it when → Project is core to your business → You are likely to support business changes in future → You can have conversations with stakeholders
  • 99. Do not use when... → Not core to the business → Prototype or short-term project → It can be thrown away when the business changes → You have no access to business experts (but try and change this)
  • 100. Thank you! → @ciaranmcnulty → Lead Maintainer of PhpSpec → Senior Trainer at: Inviqa / Sensio Labs UK / Session Digital / iKOS → https://joind.in/talk/view/15436 Questions?