Download - Driving Design with PhpSpec

Transcript
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?