Driving Design with PhpSpec

92
Driving Development with PhpSpec with Ciaran McNulty PHPLondon November 2014

description

What is BDD? Is it the same as TDD, or something quite different? This talk will answer these questions, and show how PhpSpec can be integrated into your development workflow to drive your Object Oriented design. Plus: a sneak peak at some of the new features in the forthcoming 2.1 release.

Transcript of Driving Design with PhpSpec

Page 1: Driving Design with PhpSpec

Driving Development with PhpSpec

with Ciaran McNulty

PHPLondon November 2014

Page 2: Driving Design with PhpSpec

My experiences4 Unit testing since 2004

4 Test Driven Development since 2005(ish)

4 Behaviour Driven Development since 2012

Page 3: Driving Design with PhpSpec

TDD vs BDD(or are they the same?)

Page 4: Driving Design with PhpSpec

BDD is a second-generation, outside-in,

pull-based, multiple-stakeholder…

1Dan North

Page 5: Driving Design with PhpSpec

…multiple-scale, high-automation, agile

methodology.1

Dan North

Page 6: Driving Design with PhpSpec

BDD is the art of using examples in conversation to

illustrate behaviour1

Liz Keogh

Page 7: Driving Design with PhpSpec

Test Driven Development4 Before you write your code,

write a test that validates how it should behave

4 After you have written the code, see if it passes the test

Page 8: Driving Design with PhpSpec

Behaviour Driven Development4 Before you write your code,

describe how it should behave using examples

4 Then, Implement the behaviour you you described

Page 9: Driving Design with PhpSpec
Page 10: Driving Design with PhpSpec
Page 11: Driving Design with PhpSpec
Page 12: Driving Design with PhpSpec
Page 13: Driving Design with PhpSpec

SpecBDD with PhpSpecDescribing individual classes

Page 14: Driving Design with PhpSpec

History1.0 - Inspired by RSpec

4 Pádraic Brady and Travis Swicegood

Page 15: Driving Design with PhpSpec

History2.0beta - Inspired by 1.0

4 Marcello Duarte and Konstantin Kudryashov (Everzet)

4 Ground-up rewrite

4 No BC in specs

Page 16: Driving Design with PhpSpec

History2.0 stable - The boring bits

4 Me

4 Christophe Coevoet

4 Jakub Zalas

4 Richard Miller

4 Gildas Quéméner

Page 17: Driving Design with PhpSpec

Installation via Composer{ "require-dev": { "phpspec/phpspec": "~2.1-RC1" }, "config": { "bin-dir": "bin" }, "autoload": {"psr-0": {"": "src"}}}

Page 18: Driving Design with PhpSpec
Page 19: Driving Design with PhpSpec

A requirement:

We need something that says hello to people

Page 20: Driving Design with PhpSpec

Describing object behaviour4 We describe an object using a Specification

4 A specification is made up of Examples illustrating different scenarios

Usage:phpspec describe [Class]

Page 21: Driving Design with PhpSpec
Page 22: Driving Design with PhpSpec

/spec/PhpLondon/HelloWorld/GreeterSpec.php

namespace spec\PhpLondon\HelloWorld;

use PhpSpec\ObjectBehavior;use Prophecy\Argument;

class GreeterSpec extends ObjectBehavior{ function it_is_initializable() { $this->shouldHaveType('PhpLondon\HelloWorld\Greeter'); }}

Page 23: Driving Design with PhpSpec

Verifying object behaviour4 Compare the real objects' behaviours with the

examples

Usage:phpspec run

Page 24: Driving Design with PhpSpec
Page 25: Driving Design with PhpSpec
Page 26: Driving Design with PhpSpec

/src/PhpLondon/HelloWorld/Greeter.phpnamespace PhpLondon\HelloWorld;

class Greeter{}

Page 27: Driving Design with PhpSpec

An example for Greeter:

When this greets, it should return "Hello"

Page 28: Driving Design with PhpSpec

/spec/PhpLondon/HelloWorld/GreeterSpec.php

class GreeterSpec extends ObjectBehavior{ function it_greets_by_saying_hello() { $this->greet()->shouldReturn('Hello'); }}

Page 29: Driving Design with PhpSpec
Page 30: Driving Design with PhpSpec
Page 31: Driving Design with PhpSpec

/src/PhpLondon/HelloWorld/Greeter.phpclass Greeter{ public function greet() { // TODO: write logic here }}

Page 32: Driving Design with PhpSpec

So now I write some code?

Page 33: Driving Design with PhpSpec

Fake it till you make it4 Do the simplest thing that works

4 Only add complexity later when more examples drive it

phpspec run --fake

Page 34: Driving Design with PhpSpec
Page 35: Driving Design with PhpSpec
Page 36: Driving Design with PhpSpec

/src/PhpLondon/HelloWorld/Greeter.phpclass Greeter{ public function greet() { return 'Hello'; }}

Page 37: Driving Design with PhpSpec

Describing valuesMatchers

Page 38: Driving Design with PhpSpec

Describing values - Equality$this->greet()->shouldReturn('Hello');

$this->sum(3,3)->shouldEqual(6);

$user = $this->findById(1234);$user->shouldBe($expectedUser);

$this->numberList() ->shouldBeLike(new ArrayObject([1,2,3]));

Page 39: Driving Design with PhpSpec

Describing values - Type$this->address()->shouldHaveType('EmailAddress');

$this->getTime()->shouldReturnAnInstanceOf('DateTime');

$user = $this->findById(1234);$user->shouldBeAnInstanceOf('User');

$this->shouldImplement('Countable');

Page 40: Driving Design with PhpSpec

Describing values - Strings$this->getStory()->shouldStartWith('A long time ago');$this->getStory()->shouldEndWith('happily ever after');

$this->getSlug()->shouldMatch('/^[0-9a-z]+$/');

Page 41: Driving Design with PhpSpec

Describing values - Arrays$this->getNames()->shouldContain('Tom');

$this->getNames()->shouldHaveKey(0);

$this->getNames()->shouldHaveCount(1);

Page 42: Driving Design with PhpSpec

Describing values - object state// calls isAdmin()$this->getUser()->shouldBeAdmin();

// calls hasLoggedInUser()$this->shouldHaveLoggedInUser();

Page 43: Driving Design with PhpSpec

Describing custom valuesfunction it_gets_json_with_user_details(){ $this->getResponseData()->shouldHaveJsonKey('username');}

public function getMatchers(){ return [ 'haveJsonKey' => function ($subject, $key) { return array_key_exists($key, json_decode($subject)); } ];}

Page 44: Driving Design with PhpSpec

Another example for Greeter:

When this greets Bob, it should return "Hello, Bob"

Page 45: Driving Design with PhpSpec

Wait, what is Bob?

Bob is a PersonWhat is a Person?

Page 46: Driving Design with PhpSpec
Page 47: Driving Design with PhpSpec

An example for a Person:

When you ask a person named "Alice" for their name, they return "Alice"

Page 48: Driving Design with PhpSpec

/spec/PhpLondon/HelloWorld/PersonSpec.php

class PersonSpec extends ObjectBehavior{ function it_returns_the_name_it_is_created_with() { $this->beConstructedWith('Alice');

$this->getName()->shouldReturn('Alice'); }}

Page 49: Driving Design with PhpSpec
Page 50: Driving Design with PhpSpec
Page 51: Driving Design with PhpSpec
Page 52: Driving Design with PhpSpec
Page 53: Driving Design with PhpSpec

/src/PhpLondon/HelloWorld/Person.phpclass Person{ public function __construct($argument1) { // TODO: write logic here }

public function getName() { // TODO: write logic here }}

Page 54: Driving Design with PhpSpec

So now I write some code!

Page 55: Driving Design with PhpSpec

/src/PhpLondon/HelloWorld/Person.phpclass Person{ private $name;

public function __construct($name) { $this->name = $name; }

public function getName() { return $this->name; }}

Page 56: Driving Design with PhpSpec
Page 57: Driving Design with PhpSpec

Another example for a Person:

When a person named "Alice" changes their name

to "Bob", when you ask their name they return

"Bob"

Page 58: Driving Design with PhpSpec

/spec/PhpLondon/HelloWorld/PersonSpec.php

class PersonSpec extends ObjectBehavior{ function it_returns_the_name_it_is_created_with() { $this->beConstructedWith('Alice'); $this->getName()->shouldReturn('Alice'); }}

Page 59: Driving Design with PhpSpec

/spec/PhpLondon/HelloWorld/PersonSpec.php

class PersonSpec extends ObjectBehavior{ function let() { $this->beConstructedWith('Alice'); }

function it_returns_the_name_it_is_created_with() { $this->getName()->shouldReturn('Alice'); }}

Page 60: Driving Design with PhpSpec

/spec/PhpLondon/HelloWorld/PersonSpec.php

class PersonSpec extends ObjectBehavior{ function let() { $this->beConstructedWith('Alice'); }

// …

function it_returns_its_new_name_when_the_name_has_been_changed() { $this->changeNameTo('Bob');

$this->getName()->shouldReturn('Bob'); }}

Page 61: Driving Design with PhpSpec
Page 62: Driving Design with PhpSpec
Page 63: Driving Design with PhpSpec

/src/PhpLondon/HelloWorld/Person.phpclass Person{ private $name;

// …

public function changeNameTo($argument1) { // TODO: write logic here }}

Page 64: Driving Design with PhpSpec

/src/PhpLondon/HelloWorld/Person.phpclass Person{ private $name;

// …

public function changeNameTo($name) { $this->name = $name; }}

Page 65: Driving Design with PhpSpec
Page 66: Driving Design with PhpSpec

Another example for Greeter:

When this greets Bob, it should return "Hello, Bob"

Page 67: Driving Design with PhpSpec

Describing collaboration - StubsStubs are used to describe how we interact with objects we query

4 Maybe it is hard to get the real collaborator to return the value we want

4 Maybe using the real collaborator is expensive

Page 68: Driving Design with PhpSpec

/spec/PhpLondon/HelloWorld/GreeterSpec.php

class GreeterSpec extends ObjectBehavior{ //…

function it_greets_people_by_name(Person $bob) { $bob->getName()->willReturn('Bob');

$this->greet($bob)->shouldReturn('Hello, Bob'); }}

Page 69: Driving Design with PhpSpec
Page 70: Driving Design with PhpSpec

/src/PhpLondon/HelloWorld/Greeter.phpclass Greeter{ public function greet() { return 'Hello'; }}

Page 71: Driving Design with PhpSpec

/src/PhpLondon/HelloWorld/Greeter.phpclass Greeter{ public function greet() { $greeting = 'Hello';

return $greeting; }}

Page 72: Driving Design with PhpSpec

/src/PhpLondon/HelloWorld/Greeter.phpclass Greeter{ public function greet(Person $person = null) { $greeting = 'Hello';

if ($person) { $greeting .= ', ' . $person->getName(); }

return $greeting; }}

Page 73: Driving Design with PhpSpec
Page 74: Driving Design with PhpSpec

Final example for Greeter:

When it greets Bob, the message "Hello Bob" should

be logged

Page 75: Driving Design with PhpSpec

What's a log?

Let's not worry yet

Page 76: Driving Design with PhpSpec

/src/PhpLondon/HelloWorld/Logger.phpinterface Logger{ public function log($message);}

Page 77: Driving Design with PhpSpec

Describing collaboration - Mocks and SpiesMocks or Spies are used to describe how we interact with objects we command

4 Maybe the real command is has side effects

4 Maybe using the real collaborator is expensive

Page 78: Driving Design with PhpSpec

/spec/PhpLondon/HelloWorld/GreeterSpec.php

class GreeterSpec extends ObjectBehavior{ //…

function it_greets_people_by_name(Person $bob) { $bob->getName()->willReturn('Bob'); $this->greet($bob)->shouldReturn('Hello, Bob'); }}

Page 79: Driving Design with PhpSpec

/spec/PhpLondon/HelloWorld/GreeterSpec.php

class GreeterSpec extends ObjectBehavior{ function let(Person $bob) { $bob->getName()->willReturn('Bob'); }

//…

function it_greets_people_by_name(Person $bob) { $this->greet($bob)->shouldReturn('Hello, Bob'); }}

Page 80: Driving Design with PhpSpec

/spec/PhpLondon/HelloWorld/GreeterSpec.php

class GreeterSpec extends ObjectBehavior{ function let(Person $bob, Logger $logger) { $this->beConstructedWith($logger); $bob->getName()->willReturn('Bob'); }

//…

function it_logs_the_greetings(Person $bob, Logger $logger) { $this->greet($bob); $logger->log('Hello, Bob')->shouldHaveBeenCalled(); }}

Page 81: Driving Design with PhpSpec
Page 82: Driving Design with PhpSpec
Page 83: Driving Design with PhpSpec

/src/PhpLondon/HelloWorld/Greeter.phpclass Greeter{ public function __construct($argument1) { // TODO: write logic here }

public function greet(Person $person = null) { $greeting = 'Hello'; if ($person) { $greeting .= ', ' . $person->getName(); }

return $greeting; }}

Page 84: Driving Design with PhpSpec

/src/PhpLondon/HelloWorld/Greeter.phpclass Greeter{ private $logger;

public function __construct(Logger $logger) { $this->logger = $logger; }

public function greet(Person $person = null) { $greeting = 'Hello'; if ($person) { $greeting .= ', ' . $person->getName(); }

$this->logger->log($greeting);

return $greeting; }}

Page 85: Driving Design with PhpSpec
Page 86: Driving Design with PhpSpec

What have we built?

Page 87: Driving Design with PhpSpec

The domain model

Page 88: Driving Design with PhpSpec

Specs as documentation

Page 89: Driving Design with PhpSpec

PhpSpec4 Focuses on being descriptive

4 Makes common dev activities easier or automated

4 Drives your design

Page 90: Driving Design with PhpSpec

2.1 release - soon!4 Rerun after failure

4 --fake option

4 Named constructors: User::named('Bob')

4 PSR-4 support (+ other autoloaders)

4 + lots of small improvements

Page 91: Driving Design with PhpSpec

Me4 Senior Trainer at Inviqa / Sensio Labs UK / Session

Digital

4 Contributor to PhpSpec

4 @ciaranmcnulty

4 https://github.com/ciaranmcnulty/phplondon-phpspec-talk

Page 92: Driving Design with PhpSpec

Questions?