Download - Driving Design through Examples
![Page 1: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/1.jpg)
Driving Design through ExamplesCiaran McNulty at Dutch PHP Conference 2016
![Page 2: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/2.jpg)
Modelling by ExampleCombining BDD and
DDD concepts
![Page 3: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/3.jpg)
BehaviourDriven
Development
![Page 4: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/4.jpg)
BDD helps with1. Building things well
2. Building the right things3. Building things for the right reason
... we will focus on 1 & 2
![Page 5: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/5.jpg)
BDD is the art of using examples in conversations to
illustrate behaviour— Liz Keogh
![Page 6: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/6.jpg)
Why Examples?
![Page 7: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/7.jpg)
Requirements as RulesWe 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%
![Page 8: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/8.jpg)
Rules are Ambiguous
![Page 9: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/9.jpg)
Ambiguity→ When spending points do I still earn
new points?→ Can I redeem more than 100 points on
one flight?→ Is tax based on the discounted fare or
the original price of the fare?
![Page 10: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/10.jpg)
Examples areUnambiguous
![Page 11: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/11.jpg)
ExamplesIf 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
![Page 12: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/12.jpg)
Examples are
Objectively Testable
![Page 13: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/13.jpg)
GherkinA formal language
for examples
![Page 14: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/14.jpg)
You do not have to use
Gherkin
![Page 15: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/15.jpg)
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 ...
![Page 16: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/16.jpg)
Gherkin steps→ Given sets up context for a behaviour
→ When specifies some action→ Then specifies some outcome
Action + Outcome = Behaviour
![Page 17: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/17.jpg)
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
![Page 18: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/18.jpg)
Who writes examples?Business expertTesting expert
Development expertAll discussing the feature together
![Page 19: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/19.jpg)
When to write scenarios→ Before you start work on the feature
→ Not too long before!→ Whenever you have access to the right
people
![Page 20: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/20.jpg)
Refining scenarios→ When would this outcome not be true?
→ What other outcomes are there?→ But what would happen if...?
→ Does this implementation detail matter?
![Page 21: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/21.jpg)
Scenarios are not Contracts
![Page 22: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/22.jpg)
Scenarios→ Create a shared understanding of a
feature→ Give a starting definition of done
→ Provide an objective indication of how to test a feature
![Page 23: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/23.jpg)
Domain Driven Design
![Page 24: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/24.jpg)
DDD tackles complexity by focusing
the team's attention on knowledge of the
domain— Eric Evans
![Page 25: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/25.jpg)
Invest time inunderstandingthe business
![Page 26: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/26.jpg)
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
![Page 27: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/27.jpg)
Modelling by Example
![Page 28: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/28.jpg)
By embedding Ubiquitous Language in
your scenarios, your scenarios naturally
become your domain model
— Konstantin Kudryashov (@everzet)
![Page 29: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/29.jpg)
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
![Page 30: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/30.jpg)
Directly driving
code with Behat?
![Page 31: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/31.jpg)
Layered architecture
![Page 32: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/32.jpg)
UI testing with Behat
![Page 33: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/33.jpg)
Testing through the UI→ Slow to execute
→ Brittle→ Makes you design the domain and UI at
the same time
![Page 34: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/34.jpg)
Test the domain first
![Page 35: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/35.jpg)
Testing with real infrastructure→ Slow to execute
→ Brittle→ Makes you design the domain and
infrastructure at the same time
![Page 36: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/36.jpg)
Test with fake infrastructure first
![Page 37: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/37.jpg)
Improving scenarios
![Page 38: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/38.jpg)
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
![Page 39: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/39.jpg)
Add realistic details
![Page 40: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/40.jpg)
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
![Page 41: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/41.jpg)
Actively seek terms
from the domain
![Page 42: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/42.jpg)
→ 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?
![Page 43: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/43.jpg)
Get good at listening
![Page 44: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/44.jpg)
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!
![Page 45: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/45.jpg)
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
![Page 46: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/46.jpg)
Driving the domain
model with Behat
![Page 47: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/47.jpg)
Configure a Behat suitedefault: suites: core: contexts: [ FlightsContext ]
![Page 48: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/48.jpg)
Create a contextclass FlightsContext implements Context{ /** * @Given a flight :arg1 flies the :arg2 to :arg3 route */ public function aFlightFliesTheRoute($arg1, $arg2, $arg3) { throw new PendingException(); }
// ...}
![Page 49: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/49.jpg)
Run Behat
![Page 50: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/50.jpg)
Model values as
Value Objects
![Page 51: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/51.jpg)
class FlightsContext implements Context{ /** * @Given a flight :flightnumber flies the :origin to :destination route */ public function aFlightFliesTheRoute($flightnumber, $origin, $destination) { $this->flight = new Flight( FlightNumber::fromString($flightnumber), Route::between( Airport::fromCode($origin), Airport::fromCode($destination) ) ); }
// ...}
![Page 52: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/52.jpg)
Transformations/** * @Transform :flightnumber */public function transformFlightNumber($number){ return FlightNumber::fromString($number);}
/** * @Transform :origin * @Transform :destination */public function transformAirport($code){ return Airport::fromCode($code);}
![Page 53: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/53.jpg)
/** * @Given a flight :flightnumber flies the :origin to :destination route */public function aFlightFliesTheRoute( FlightNumber $flightnumber, Airport $origin, Airport $destination){ $this->flight = new Flight( $flightnumber, Route::between($origin, $destination) );}
![Page 54: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/54.jpg)
> vendor/bin/behatPHP Fatal error: Class 'Flight' not found
![Page 55: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/55.jpg)
Describe objects
with PhpSpec
![Page 56: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/56.jpg)
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(); }}
![Page 57: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/57.jpg)
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; }}
![Page 58: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/58.jpg)
![Page 59: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/59.jpg)
/** * @Given the current listed fare for the :arg1 to :arg2 route is £:arg3 */public function theCurrentListedFareForTheToRouteIsPs($arg1, $arg2, $arg3){ throw new PendingException();}
![Page 60: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/60.jpg)
Model boundarieswith Interfaces
![Page 61: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/61.jpg)
interface FareList{ public function listFare(Route $route, Fare $fare);}
![Page 62: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/62.jpg)
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; }}
![Page 63: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/63.jpg)
/** * @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 Fake\FareList(); $this->fareList->listFare( Route::between($origin, $destination), Fare::fromString($fare) );}
![Page 64: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/64.jpg)
Run Behat
![Page 65: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/65.jpg)
/** * @When Iam issued a ticket on flight :arg1 */public function iAmIssuedATicketOnFlight($arg1){ throw new PendingException();}
![Page 66: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/66.jpg)
/** * @When I am issued a ticket on flight :flight */public function iAmIssuedATicketOnFlight(){ $ticketIssuer = new TicketIssuer($this->fareList); $this->ticket = $ticketIssuer->issueOn($this->flight);}
![Page 67: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/67.jpg)
> vendor/bin/behatPHP Fatal error: Class 'TicketIssuer' not found
![Page 68: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/68.jpg)
class TicketIssuerSpec extends ObjectBehavior{ function it_can_issue_a_ticket_for_a_flight(\Flight $flight) { $this->issueOn($flight)->shouldHaveType(\Ticket::class); }}
![Page 69: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/69.jpg)
class TicketIssuer{ public function issueOn(Flight $flight) { return Ticket::costing(Fare::fromString('10000.00')); }}
![Page 70: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/70.jpg)
Run Behat
![Page 71: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/71.jpg)
/** * @When I pay £:fare cash for the ticket */public function iPayPsCashForTheTicket(Fare $fare){ $this->ticket->pay($fare);}
![Page 72: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/72.jpg)
PHP Fatal error: Call to undefined method Ticket::pay()
![Page 73: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/73.jpg)
class TicketSpec extends ObjectBehavior{ function it_can_be_paid() { $this->pay(\Fare::fromString("10.00")); }}
![Page 74: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/74.jpg)
class Ticket{ public function pay(Fare $fare) { }}
![Page 75: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/75.jpg)
Run Behat
![Page 76: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/76.jpg)
The model will beanaemicUntil you get to Then
![Page 77: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/77.jpg)
/** * @Then the ticket should be completely paid */public function theTicketShouldBeCompletelyPaid(){ throw new PendingException();}
![Page 78: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/78.jpg)
/** * @Then the ticket should be completely paid */public function theTicketShouldBeCompletelyPaid(){ assert($this->ticket->isCompletelyPaid() == true);}
![Page 79: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/79.jpg)
PHP Fatal error: Call to undefined method Ticket::isCompletelyPaid()
![Page 80: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/80.jpg)
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(); }}
![Page 81: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/81.jpg)
class Ticket{ private $fare;
// ...
public function pay(Fare $fare) { $this->fare = $this->fare->deduct($fare); }
public function isCompletelyPaid() { return $this->fare->isZero(); }}
![Page 82: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/82.jpg)
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')); }}
![Page 83: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/83.jpg)
class Fare{ private $pence;
private function __construct($pence) { $this->pence = $pence; }
// ...
public function deduct(Fare $amount) { return new Fare($this->pence - $amount->pence); }}
![Page 84: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/84.jpg)
class FareSpec extends ObjectBehavior{ // ...
function it_knows_when_it_is_zero() { $this->beConstructedFromString('0.00'); $this->shouldBeZero(); }
function it_is_not_zero_when_it_has_a_value() { $this->beConstructedFromString('10.00'); $this->shouldNotBeZero(); }}
![Page 85: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/85.jpg)
class Fare{ private $pence;
private function __construct($pence) { $this->pence = $pence; }
// ...
public function isZero() { return $this->pence == 0; }}
![Page 86: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/86.jpg)
Run Behat
![Page 87: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/87.jpg)
class TicketIssuerSpec extends ObjectBehavior{ function it_issues_a_ticket_with_the_correct_fare(\FareList $fareList) { $route = Route::between(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'))); }}
![Page 88: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/88.jpg)
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())); }}
![Page 89: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/89.jpg)
interface FareList{ public function listFare(Route $route, Fare $fare);
public function findFareFor(Route $route);}
![Page 90: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/90.jpg)
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()]; }}
![Page 91: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/91.jpg)
Run Behat
![Page 92: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/92.jpg)
/** * @Then I the ticket should be worth :points loyalty points */public function iTheTicketShouldBeWorthLoyaltyPoints(Points $points){ assert($this->ticket->getPoints() == $points);}
![Page 93: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/93.jpg)
class FareSpec extends ObjectBehavior{ function let() { $this->beConstructedFromString('100.00'); }
// ...
function it_calculates_points() { $this->getPoints()->shouldBeLike(\Points::fromString('100')); }}
![Page 94: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/94.jpg)
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')); }}
![Page 95: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/95.jpg)
<?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(); }}
![Page 96: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/96.jpg)
Run Behat
![Page 97: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/97.jpg)
Where is our domain
model?
![Page 98: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/98.jpg)
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
![Page 99: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/99.jpg)
> 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
![Page 100: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/100.jpg)
End to End Testing
![Page 101: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/101.jpg)
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!
![Page 102: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/102.jpg)
default: suites: core: contexts: [ FlightsContext ] web: contexts: [ WebFlightsContext ] filters: { tags: @ui }
![Page 103: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/103.jpg)
Feature: Earning and spending points on flights
Scenario: Earning points when paying cash Given ...
@ui Scenario: Redeeming points for a discount on a flight Given ...
Scenario: Paying for a flight entirely using points Given ...
![Page 104: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/104.jpg)
Modelling by Example→ Focuses attention on use cases
→ Helps developers understand core business domains
→ Encourages layered architecture→ Speeds up test suites
![Page 105: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/105.jpg)
Use it when→ Module is core to your business→ You are likely to support business
changes in the future→ You can have conversations with
stakeholders
![Page 106: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/106.jpg)
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)
![Page 107: Driving Design through Examples](https://reader034.vdocuments.site/reader034/viewer/2022042723/587f3d581a28ab43318b4f91/html5/thumbnails/107.jpg)
→ Rate talk: https://joind.in/talk/304ff→ Join us: http://bit.ly/inviqa-careers→ Get help: http://bit.ly/inviqa-contact