BDD For Zend Framework With PHPSpec

Post on 10-May-2015

5.165 views 1 download

Tags:

description

Talk delivered at PHP Barcelona Conference 2011. Using PHPSpec and Zend Tool together to demonstrate BDD in a PHP MVC context.

Transcript of BDD For Zend Framework With PHPSpec

BDD for with

PHPSpec

29th October 2011

Marcello Duarte

Saturday, 29 October 2011

whoami

Marcello Duarte

Head of Training @ Ibuildings UK

Lead developer @ PHPSpec

Twitter @_md

Marcello Duarte

Head of Training @ Ibuildings UK

Lead developer @ PHPSpec

Twitter @_md

Saturday, 29 October 2011

In the beginningthere was...

TDD

credits: http://www.flickr.com/photos/improveit/1574023621

Saturday, 29 October 2011

They saw that it was good

Saturday, 29 October 2011

$thou->shalt->test$thou->shalt->test

credits: http://www.flickr.com/photos/36829113@N05/3392940179/

Saturday, 29 October 2011

BDD

credits: http://www.flickr.com/photos/psd/424257767/

Saturday, 29 October 2011

BDD

A way of teaching TDD

credits: http://www.flickr.com/photos/psd/424257767/

Saturday, 29 October 2011

What to test?

Where to begin?

How do I name my test?

How much to test in one go?

What to call my test??Saturday, 29 October 2011

credits: http://www.flickr.com/photos/psd/424257767/

BDD

Saturday, 29 October 2011

Offers a common language

credits: http://www.flickr.com/photos/psd/424257767/

BDD

Saturday, 29 October 2011

user developer

business

Saturday, 29 October 2011

behavior

user developer

business

Saturday, 29 October 2011

Sapir-Whorf hypothesis

© M

anusc

ripts

and A

rchiv

es,

Yale

Univ

ersi

ty L

ibra

ry.

Saturday, 29 October 2011

Language to express truth

vs...

Saturday, 29 October 2011

Language to discover truth

Saturday, 29 October 2011

Language influence thought

Saturday, 29 October 2011

credits: http://www.flickr.com/photos/psd/424257767/

BDD

Saturday, 29 October 2011

A way to discoverwhat is useful to deliver

credits: http://www.flickr.com/photos/psd/424257767/

BDD

Saturday, 29 October 2011

Saturday, 29 October 2011

BDD Outside in

Gherkin

Behat

PHPSpec

Saturday, 29 October 2011

Gherkin

Saturday, 29 October 2011

Feature: Organizers can open a call for paper As an event organizer I want a way to publish a centralized cfp form So that it’s easier for speakers to submit

Scenario: Creation form with valid attributes Given I am in on "call-for-papers/add" When I fill in the following: | event | PHPLondon Conference| | start_date | 2012-02-06 | | limit_abstract_wc | 500 | | why_you_field | 1 | | offer_hotel | 1 | | offer_travel | 0 | And I press "Create" Then I should see "The cfp was created successfully"

Feature: Organizers can open a call for paper As an event organizer I want a way to publish a centralized cfp form So that it’s easier for speakers to submit

Scenario: Creation form with valid attributes Given I am in on "call-for-papers/add" When I fill in the following: | event | PHPLondon Conference| | start_date | 2012-02-06 | | limit_abstract_wc | 500 | | why_you_field | 1 | | offer_hotel | 1 | | offer_travel | 0 | And I press "Create" Then I should see "The cfp was created successfully"

Feature: Organizers can open a call for paper As an event organizer I want a way to publish a centralized cfp form So that it’s easier for speakers to submit

Scenario: Creation form with valid attributes Given I am in on "call-for-papers/add" When I fill in the following: | event | PHPLondon Conference| | start_date | 2012-02-06 | | limit_abstract_wc | 500 | | why_you_field | 1 | | offer_hotel | 1 | | offer_travel | 0 | And I press "Create" Then I should see "The cfp was created successfully"

Feature: Organizers can open a call for paper As an event organizer I want a way to publish a centralized cfp form So that it’s easier for speakers to submit

Scenario: Creation form with valid attributes Given I am in on "call-for-papers/add" When I fill in the following: | event | PHPLondon Conference| | start_date | 2012-02-06 | | limit_abstract_wc | 500 | | why_you_field | 1 | | offer_hotel | 1 | | offer_travel | 0 | And I press "Create" Then I should see "The cfp was created successfully"

Saturday, 29 October 2011

behat.org

Saturday, 29 October 2011

Saturday, 29 October 2011

words matter

Saturday, 29 October 2011

$this->assertTrue(...

Saturday, 29 October 2011

$this->assertTrue(...

What am I going to test?

Saturday, 29 October 2011

$report = new Report;$this->assertTrue($report instanceof Report);

Saturday, 29 October 2011

$report = new Report;$this->assertTrue($report instanceof Report);//

Saturday, 29 October 2011

$employee->should->...

Saturday, 29 October 2011

$employee->should->...

What is the expected behavior?

Saturday, 29 October 2011

Use Gherkin and Behat for specifying scenarios

Use PHPSpec for specifying classes∫

Saturday, 29 October 2011

expressiveness

Saturday, 29 October 2011

$employee->should->reportTo($manager);

$this->assertTrue($employee->reportsTo($manager));

Saturday, 29 October 2011

phpspec.net

Saturday, 29 October 2011

$ sudo pear channel-discover pear.phpspec.net$ sudo pear install --alldeps phpspec/PHPSpec

Installing

Saturday, 29 October 2011

“Specification, not verification” (Uncle Bob)

$this->assertEquals(0, $result);

becomes$result->should->be(0);

Saturday, 29 October 2011

class CalculatorTest

becomesclass DescribeCalculator

class CalculatorTest

becomesclass DescribeCalculator

Saturday, 29 October 2011

function testAddWithNoArguments()

becomesfunction itReturnsZeroWithNoArguments()

Saturday, 29 October 2011

All together

class DescribeStringCalculator extends \PHPSpec\Context{

function itReturnsZeroWithNoArguments() { $calculator = $this->spec(new StringCalculator); $result = $calculator->add();

$result->should->be(0); }

}

Saturday, 29 October 2011

Hooks

before()after()

beforeAll()afterAll()

Saturday, 29 October 2011

Setting initial state with before hook

class DescribeStringCalculator extends \PHPSpec\Context{ function before() { $this->calculator = $this->spec(new StringCalculator); }

function itReturnsZeroWithNoArguments() { $result = $this->calculator->add(); $result->should->be(0); }}

Saturday, 29 October 2011

class DescribeStringCalculator extends \PHPSpec\Context{ private $calculator;

function before() { $this->calculator = $this->spec(new StringCalculator); }

function itReturnsZeroWithNoArguments() { $this->calculator->add()->should->equal(0); }

function itReturnsTheBareNumber() { $this->calculator->add('42')->should->equal(42); }}

Saturday, 29 October 2011

Formatters

progressdocumentation

html

coming soon:junit

Saturday, 29 October 2011

$ phpspec StringCalculatorSpec.php -c.*.F

Pending: String Calculator returns the bare number # Waiting to clarify the spec # ./spec/StringCalculatorSpec.php:19

Failures: 1) String Calculator returns the sum of space separate string expected 42, got 0 (using be()) # .spec/StringCalculatorSpec.php:28

2) StringCalculator returns the sum of any white space separated string Failure/Error: Just because

Finished in 0.056134 seconds4 examples, 1 failure, 1 pending

Progress Formatter

Saturday, 29 October 2011

HTML Formatter

Saturday, 29 October 2011

Documentation Formatter

Saturday, 29 October 2011

Matchers

Saturday, 29 October 2011

be($match)equal($match)

beEqualTo($match)beAnInstanceOf($match)

beEmpty()beFalse()

beGreaterThan($match)beGreaterThanOrEqualTo($match)

Saturday, 29 October 2011

And more matchers...

Saturday, 29 October 2011

beInteger()beLessThan($match)

beLessThanOrEqualTo($match) beNull()

beString()beTrue()

throwException($match)

Saturday, 29 October 2011

Predicate Matchers

Saturday, 29 October 2011

$cell = $this->spec(new Cell);$cell->should->beAlive();

class Cell{ protected $alive = true;

public function isAlive() { return $this->alive; } ...}

$cell = $this->spec(new Cell);$cell->should->beAlive();

class Cell{ protected $alive = true;

public function isAlive() { return $this->alive; } ...}

Saturday, 29 October 2011

$newNode = $this->spec(new Node);$newNode->shouldNot->haveChildren();

class Node{ protected $children = array();

public function hasChildren() { return count($this->children) > 0; } ...}

$newNode = $this->spec(new Node);$newNode->shouldNot->haveChildren();

class Node{ protected $children = array();

public function hasChildren() { return count($this->children) > 0; } ...}

Saturday, 29 October 2011

Custom Matchers

Saturday, 29 October 2011

\PHPSpec\Matcher\define('reportTo', function($supervisor) { return array ( 'match' => function($supportEngineer) use ($supervisor) { return $supportEngineer->reportsTo($supervisor); }, 'failure_message_for_should' => function($supportEngineer) use ($supervisor) { return "expected " . $supervisor->getName() . " to report to " . $supervisor->getName(); } );});

class DescribeSupportEngineer extends \PHPSpec\Context{ ... function itAddsNewCourses() { $john = new Supervisor("John Smith"); $john->addToTeam($this->supportEngineer); $this->supportEngineer->should->reportTo($john); }}

\PHPSpec\Matcher\define('reportTo', function($supervisor) { return array ( 'match' => function($supportEngineer) use ($supervisor) { return $supportEngineer->reportsTo($supervisor); }, 'failure_message_for_should' => function($supportEngineer) use ($supervisor) { return "expected " . $supervisor->getName() . " to report to " . $supervisor->getName(); } );});

class DescribeSupportEngineer extends \PHPSpec\Context{ ... function itAddsNewCourses() { $john = new Supervisor("John Smith"); $john->addToTeam($this->supportEngineer); $this->supportEngineer->should->reportTo($john); }}

Saturday, 29 October 2011

PHPSpec &

Saturday, 29 October 2011

Tool

framework.zend.com

Saturday, 29 October 2011

$ sudo pear channel-discover pear.zfcampus.org$ sudo pear install zfcampus/zf$ zf create config$ vi ~/.zf.ini

Installing

Saturday, 29 October 2011

.zf.ini

php.include_path = ".:/usr/share/pear"

basicloader.classes.1 = "Akrabat_Tool_DatabaseSchemaProvider"basicloader.classes.2 = "PHPSpec_Context_Zend_Tool_Provider_Phpspec"basicloader.classes.3 = "PHPSpec_Context_Zend_Tool_Provider_ModelSpec"basicloader.classes.4 = "PHPSpec_Context_Zend_Tool_Provider_ViewSpec"basicloader.classes.5 = "PHPSpec_Context_Zend_Tool_Provider_ControllerSpec"basicloader.classes.6 = "PHPSpec_Context_Zend_Tool_Provider_ActionSpec"basicloader.classes.7 = "PHPSpec_Context_Zend_Tool_Provider_Behat"

php.include_path = ".:/usr/share/pear"

basicloader.classes.1 = "Akrabat_Tool_DatabaseSchemaProvider"basicloader.classes.2 = "PHPSpec_Context_Zend_Tool_Provider_Phpspec"basicloader.classes.3 = "PHPSpec_Context_Zend_Tool_Provider_ModelSpec"basicloader.classes.4 = "PHPSpec_Context_Zend_Tool_Provider_ViewSpec"basicloader.classes.5 = "PHPSpec_Context_Zend_Tool_Provider_ControllerSpec"basicloader.classes.6 = "PHPSpec_Context_Zend_Tool_Provider_ActionSpec"basicloader.classes.7 = "PHPSpec_Context_Zend_Tool_Provider_Behat"

Saturday, 29 October 2011

$ zf create project callconfCreating project at /var/www/callconfNote: This command created a web project, for more information setting up your VHOST, please see docs/README

create a project

Saturday, 29 October 2011

$ cd callconf$ zf generate phpspec create spec create spec/SpecHelper.php create spec/.phpspec create spec/models create spec/views create spec/controllers

initialize PHPSpec

Saturday, 29 October 2011

$ zf generate behat+d features - place your *.feature files here+d features/bootstrap - place bootstrap scripts and static files here+f features/bootstrap/FeatureContext.php - place your feature related code here

initialize Behat

Saturday, 29 October 2011

Saturday, 29 October 2011

Views

Saturday, 29 October 2011

why specify the view?

Saturday, 29 October 2011

Controller and model to the point

Ensure we are focused on what matters

Sustainable pace

Saturday, 29 October 2011

create a view spec

Saturday, 29 October 2011

$ zf create view-spec add CallForPapers

create a view spec

Saturday, 29 October 2011

$ zf create view-spec add CallForPapers Creating a view script in location /var/www/callconf/application/views/scripts/call-for-papers/add.phtmlCreating a spec at /var/www/callconf/spec/views/call-for-papers/AddSpec.php

create a view spec

Saturday, 29 October 2011

Saturday, 29 October 2011

Spec created by default<?php

namespace CallForPapers;

require_once __DIR__ . '/../../SpecHelper.php';

use \PHPSpec\Context\Zend\View as ViewContext;

class DescribeAdd extends ViewContext{ function itRendersTheDefaultContent() { $this->render(); $this->rendered->should->contain('CallForPapers'); $this->rendered->should->contain('add'); }}

<?php

namespace CallForPapers;

require_once __DIR__ . '/../../SpecHelper.php';

use \PHPSpec\Context\Zend\View as ViewContext;

class DescribeAdd extends ViewContext{ function itRendersTheDefaultContent() { $this->render(); $this->rendered->should->contain('CallForPapers'); $this->rendered->should->contain('add'); }}

<?php

namespace CallForPapers;

require_once __DIR__ . '/../../SpecHelper.php';

use \PHPSpec\Context\Zend\View as ViewContext;

class DescribeAdd extends ViewContext{ function itRendersTheDefaultContent() { $this->render(); $this->rendered->should->contain('CallForPapers'); $this->rendered->should->contain('add'); }}

Saturday, 29 October 2011

what behaviors can we describe in the view spec?

Saturday, 29 October 2011

Variables we need assigned

What content was rendered(We can use selectors)

Saturday, 29 October 2011

Assigning Variables

function itRendersTheTalkAbstract(){ $marcello = $this->mock('Speaker', array('isVegetarian' => true)); $this->assign('speaker', $marcello); $this->render(); $this->rendered->should->contain('diet restrictions: vegetarian');}

Saturday, 29 October 2011

Controllers

Saturday, 29 October 2011

create a controller spec

Saturday, 29 October 2011

$ zf create controller-spec CallForPapers add,create

create a controller spec

Saturday, 29 October 2011

$ zf create controller-spec CallForPapers add,createCreating a controller at /private/var/www/callconf/application/controllers/CallForPapersController.phpCreating an add action method in controller CallForPapersCreating an create action method in controller CallForPapersCreating a spec at /private/var/www/callconf/spec/controllers/CallForPapersSpec.php

create a controller spec

Saturday, 29 October 2011

Saturday, 29 October 2011

Spec created by default

<?php

require_once __DIR__ . '/../SpecHelper.php';

class DescribeCallForPapers extends \PHPSpec\Context\Zend\Controller{ function itShouldBeSuccessfulToGetAdd() { $this->get('call-for-papers/add'); $this->response->should->beSuccess(); }

}

<?php

require_once __DIR__ . '/../SpecHelper.php';

class DescribeCallForPapers extends \PHPSpec\Context\Zend\Controller{ function itShouldBeSuccessfulToGetAdd() { $this->get('call-for-papers/add'); $this->response->should->beSuccess(); }

}

Saturday, 29 October 2011

what behaviors do we want todescribe in the controller spec?

Saturday, 29 October 2011

How do we want to route to its actions

What view variables we need assigned

What view we want rendered

Saturday, 29 October 2011

Routing and assigning

function itShouldRouteToTheAddAction() { $this->routeFor(array( 'controller' => 'call-for-papers', 'action' => 'add' ))->should->be('/call-for-papers/add'); } function itAssignsAddSubmissionFormVariable() { $this->get('/call-for-papers/add'); $this->assigns('addSubmissionForm')->should->beAnInstanceOf( '\Application_Form_AddSubmissionForm' ); }

function itShouldRouteToTheAddAction() { $this->routeFor(array( 'controller' => 'call-for-papers', 'action' => 'add' ))->should->be('/call-for-papers/add'); } function itAssignsAddSubmissionFormVariable() { $this->get('/call-for-papers/add'); $this->assigns('addSubmissionForm')->should->beAnInstanceOf( '\Application_Form_AddSubmissionForm' ); }

Saturday, 29 October 2011

Models

Saturday, 29 October 2011

create a model spec

Saturday, 29 October 2011

$ zf create model-spec Speaker name:string,email:string

create a model spec

Saturday, 29 October 2011

$ zf create model-spec Speaker name:string,email:stringCreating a model at /private/var/www/callconf/application/models/Speaker.phpCreating a db table at /private/var/www/callconf/application/models/DbTable/Speakers.phpCreating a mapper at /private/var/www/callconf/application/models/SpeakerMapper.phpCreating a spec at /private/var/www/callconf/spec/models/SpeakerSpec.phpCreating migration scripts at /private/var/www/callconf/db/migrate/001-CreateSpeakersTable.phpUpdating project profile '/private/var/www/callconf/.zfproject.xml'

create a model spec

Saturday, 29 October 2011

Saturday, 29 October 2011

Spec created by default<?php

require_once __DIR__ . '/../SpecHelper.php';

use Application_Model_Speaker as Speaker;

class DescribeSpeaker extends \PHPSpec\Context{ function before() { $this->validAttributes = array( 'name' => 'value for name', 'email' => 'value for email', ); } function itShouldCreateANewInstanceGivenValidAttributes() { $this->speaker = $this->spec(Speaker::create($this->validAttributes)); $this->speaker->should->beValid(); }}Saturday, 29 October 2011

what behaviors can wedescribe in the model spec?

Saturday, 29 October 2011

Business logic

Validation

Spying results from data source operations

Saturday, 29 October 2011

Business Logic

class DescribeSpeaker extends \PHPSpec\Context{ function before() { $this->validAttributes = array( 'name' => 'Marcello Duarte', 'email' => 'marcello@ibuildings.com', 'diet_restriction' => 'vegetarian', ); $this->speaker = $this->spec(Speaker::create($this->validAttributes)); } function itGetsExtraRatingPointsForTalkIfVegetarian() { $this->speaker->should->haveExtraPoints(); }}

class DescribeSpeaker extends \PHPSpec\Context{ function before() { $this->validAttributes = array( 'name' => 'Marcello Duarte', 'email' => 'marcello@ibuildings.com', 'diet_restriction' => 'vegetarian', ); $this->speaker = $this->spec(Speaker::create($this->validAttributes)); } function itGetsExtraRatingPointsForTalkIfVegetarian() { $this->speaker->should->haveExtraPoints(); }}

Saturday, 29 October 2011

Business Logic

class Speaker{ //... other methods

function hasExtraPoints() { return stripos($this->getDietRestrictions(), 'vegetarian') !== false; }}

class Speaker{ //... other methods

function hasExtraPoints() { return stripos($this->getDietRestrictions(), 'vegetarian') !== false; }}

Saturday, 29 October 2011

Real database hits?

Saturday, 29 October 2011

Avoid

Sometimes, for confidence

When testing data access objects

Saturday, 29 October 2011

Dependency chains

Saturday, 29 October 2011

Dependencies can be hard to manage

class DescribeEvent extends \PHPSpec\Context{ function itDoesSomethingWhenYouHaveSpeakerAllocated() { $event = new Event( new Organizer('John Smith', new Organization('Ibuildings') ) ); $event->addSpeaker(new Speaker('Rowan'), new Slot(’10:30’), new Room('A')); $event->addSpeaker(new Speaker('Ben'), new Slot(’10:30’), new Room('B')); // specify expected behavior }}

Saturday, 29 October 2011

Usually dependencies are replaced with doubles when writing specs

We can use a framework like Mockery

But if you really need the real thing

Saturday, 29 October 2011

Object Mother

Saturday, 29 October 2011

Dependencies can be hard to manage

class DescribeEvent extends \PHPSpec\Context{ function itDoesSomethingWhenYouHaveSpeakerAllocated() { $exampleEvent = ExampleEvent::newWithSimultaneousSpeakers();

// specify expected event behavior }}

Saturday, 29 October 2011

Code duplication

Too many methods

Saturday, 29 October 2011

Test Data Builder

Saturday, 29 October 2011

Is created with save “empty” objects

Has a fluent interface

Has a build method

Saturday, 29 October 2011

Dependencies can be hard to manage

class DescribeEvent extends \PHPSpec\Context{ function itDoesSomethingWhenYouHaveSpeakerAllocated() { $eventBuilder = new EventBuilder(); $organizerBuilder = new OrganizerBuilder();

$event = $eventBuilder->withOrganizer( $organizerBuilder->withOrganization()->build() )->withConflictingSpeakers() ->build();

// specify expected event behavior }}

Saturday, 29 October 2011

phactory.org

Saturday, 29 October 2011

installing

Saturday, 29 October 2011

$ sudo pear channel-discover pearhub.org

installing

Saturday, 29 October 2011

$ sudo pear channel-discover pearhub.org$ sudo pear install pearhub/Phactory

installing

Saturday, 29 October 2011

Needs a Pdo connection

Get from default adapter

Saturday, 29 October 2011

protected function _initPhactory() { Phactory::setConnection( Zend_Db_Table_Abstract::getDefaultAdapter()); return Phactory::getConnection(); }

Create a connection

Saturday, 29 October 2011

// spec/factories.php

Phactory::define('speaker', array( 'name' => 'John Smith', 'email' => 'john@smith.com'));

Define table blueprints

Saturday, 29 October 2011

// in one of my specs

$ben = Phactory::create('speaker', array('name' => 'Rowan'));$rowan = Phactory::create('speaker', array('name' => 'Ben'));

// Phactory_Row objectsecho $ben->name // prints Ben

Create objects

Saturday, 29 October 2011

Questions?

223

Saturday, 29 October 2011

224

Thank you!

http://joind.in/4318

http://slidesha.re/tcGM93

Marcello Duarte

@_md

is hiring. Come talk to me.

Saturday, 29 October 2011