Spec BDD WITH 


PHPSPEC 2
November Camp

Stockholm 22/11/2013
flickr.com/arnolouise/3252847397/
Kacper Gunia @cakper
Software Engineer @SensioLabsUK
Symfony Certified Developer
Polish Symfony Community
Silesian PHP User Group
Softw
are
Quality
flickr.com/adforce1/2462794123/
INTERNAL
Quality
flickr.com/ensh/5084228263/
EXTERNAL
Quality
flickr.com/arselectronicacenter/8695704856/
INTERNAL
vs

EXTERNAL
It’s NOT VS
It’s AND
InnerNAL
And

EXTERNAL
flickr.com/pmiaki/6768810175/
How to ensure
quality?

Test
How to test?

Automa
te
So you WRITE
your code…
…and your tests
HOW DARE YOU?
are YOU sure
Tests ARE
correct?
Test
Driven
Development
Red

Refactor

Green
BUT…
test Something
tha doesn’t
t
exists?
flickr.com/ucumari/580865728/
Test In TDD
means


Specifica
tion
Specifica
tion
describes

Beha
vior
BeHa
vior
Driven
Development
IMPROVED

Naming
Conventions
Tools
TDD
v2.0

‘TDD DONE RIGHT’
Story BDD
&
Spec BDD
STORY BDD

description of
business-targeted
application behavior
SPEC BDD

specification for
low-level
implementation
Story BDD
Failing
Scenario

Passing
Scenario

Failing
Spec

RefacTOR

Passing
Spec

SPEC BDD
External quality
Failing
Scenario

Passing
Scenario

Failing
Spec

RefacTOR

Passing
Spec

INTERNAL QUALITY
BEHA 

T
(Story BDD)

&
PHPSpec 

(Spec BDD)
BEHA 

T
(Story BDD)

&
PHPSpec 

(Spec BDD)
PHPSPEC 2

FRAMEWORK SPEC BDD
CREATED BY
@_MD & @EVERZET
PHPSPEC 2

Bundled With Mocking
Framework - Prophecy
BUT…
WHY NOT
PHPUNIT?
PHPUNIT Is A

TESTING TOOL
PHPSPEC Is A

DESIGN TOOL
EVIDENCE?
PHPUNIT
PHPSPEC
Differences?
tEST Suite

Specifica
tion
tEST

EXAMPLE
Assertion

expect tion
a
class	
  MovieSpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  	
  	
  function	
  it_returns_movie_title()	
  
	
  	
  	
  	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  $this-­‐>getTitle()	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  -­‐>shouldReturn('Star	
  Wars');	
  
	
  	
  	
  	
  }	
  
}
Ma
tchers
IDENTITY MATCHER

class	
  MovieSpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  function	
  it_is_a_great_movie()	
  
	
  	
  {	
  
	
  	
  	
  	
  $this-­‐>getRating()-­‐>shouldBe(5);	
  
	
  	
  	
  	
  	
  	
  	
  
	
  	
  	
  	
  $this-­‐>getTitle()	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  -­‐>shouldBeEqualTo('Star	
  Wars');	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  
	
  	
  	
  	
  $this-­‐>getReleaseDate()	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  -­‐>shouldReturn(233366400);	
  
	
  	
  }	
  
}
COMPARISON MATCHER

class	
  MovieSpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  function	
  it_is_great_movie()	
  
	
  	
  {	
  
	
  	
  	
  	
  $this-­‐>getRating()-­‐>shouldBeLike('5');	
  
	
  	
  }	
  
}
THROW MATCHER

class	
  MovieSpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  function	
  it_does_not_allow_negative_ratings()	
  
	
  	
  {	
  
	
  	
  	
  	
  $this	
  
	
  	
  	
  	
  	
  	
  -­‐>shouldThrow('InvalidArgumentException')	
  
	
   	
  	
  	
  	
  -­‐>duringSetRating(-­‐3);	
  
	
  	
  }	
  
}
THROW MATCHER

class	
  MovieSpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  function	
  it_does_not_allow_negative_ratings()	
  
	
  	
  {	
  
	
  	
  	
  	
  $this-­‐>shouldThrow(	
  
	
  	
  	
  	
  	
  	
  	
  	
  new	
  InvalidArgumentException(	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  "Invalid	
  rating”	
  
	
  	
  	
  	
  	
  	
  	
  	
  )	
  
	
  	
  	
  	
  	
  	
  )-­‐>during('setRating',	
  array(-­‐3));	
  
	
  	
  	
  	
  }	
  
}
TYPE MATCHER

class	
  MovieSpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  function	
  it_is_a_movie()	
  
	
  	
  {	
  
	
  	
  	
  	
  $this-­‐>shouldHaveType('Movie');	
  
	
  	
  	
  	
  $this-­‐>shouldReturnAnInstanceOf('Movie');	
  
	
  	
  	
  	
  $this-­‐>shouldBeAnInstanceOf('Movie');	
  
	
  	
  	
  	
  $this-­‐>shouldImplement('Movie');	
  
	
  	
  }	
  
}
OBJECT-STATE MATCHER

class	
  MovieSpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  function	
  it_is_available_on_cinemas()	
  
	
  	
  {	
  
	
  	
  	
  	
  $this-­‐>shouldBeAvailableOnCinemas();	
  
	
  	
  }	
  
}	
  
!

class	
  Movie	
  
{	
  
	
  	
  public	
  function	
  isAvailableOnCinemas()	
  
	
  	
  {	
  return	
  true;	
  }<?php	
  
}
OBJECT-STATE MATCHER

class	
  MovieSpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  function	
  it_has_a_soundtrack()	
  
	
  	
  {	
  
	
  	
  	
  	
  $this-­‐>shouldHaveSoundtrack();	
  
	
  	
  }	
  
}	
  
!

class	
  Movie	
  
{	
  
	
  	
  public	
  function	
  hasSoundtrack()	
  
	
  	
  {	
  return	
  true;	
  }	
  
}
COUNT MATCHER

class	
  MovieSpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  function	
  it_has_one_director()	
  
	
  	
  {	
  
	
  	
  	
  	
  $this-­‐>getDirectors()-­‐>shouldHaveCount(1);	
  
	
  	
  }	
  
}
SCALAR MATCHER

class	
  MovieSpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  function	
  it_has_a_string_as_title()	
  
	
  	
  {	
  
	
  	
  	
  	
  $this-­‐>getTitle()-­‐>shouldBeString();	
  
	
  	
  }	
  
!

	
  	
  function	
  it_has_an_array_as_cast()	
  
	
  	
  {	
  
	
  	
  	
  	
  $this-­‐>getCast()-­‐>shouldBeArray();	
  
	
  	
  }	
  
}
INLINE MATCHER
class	
  MovieSpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  function	
  it_has_default_options()	
  
	
  	
  {	
  
	
  	
  	
  	
  $this-­‐>getOptions()-­‐>shouldHaveKey('username');	
  
	
  	
  }	
  
!

	
  	
  public	
  function	
  getMatchers()	
  
	
  	
  {	
  
	
  	
  	
  	
  return	
  [	
  
	
  	
  	
  	
  	
  	
  'haveKey'	
  =>	
  function($subject,	
  $key)	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  return	
  array_key_exists($key,	
  $subject);	
  
	
  	
  	
  	
  	
  	
  }	
  
	
  	
  	
  	
  ];	
  
	
  	
  }	
  
}
BUT…
Softw
are Design
is about

Messaging
TEST DOUBLES
DUMMIES

class	
  CinemaSpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  /**	
  
	
  	
  	
  *	
  @param	
  BoxOffice	
  $boxOffice	
  
	
  	
  	
  */	
  
	
  	
  function	
  it_is_a_cinema($boxOffice)	
  
	
  	
  {	
  
	
  	
  	
  	
  $this-­‐>beConstructedWith($boxOffice);	
  
	
  	
  	
  	
  $this-­‐>shouldHaveType('Cinema');	
  
	
  	
  }	
  
}
STUBS

class	
  CinemaSpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  /**	
  
	
  	
  	
  *	
  @param	
  Movie	
  $movie	
  
	
  	
  	
  */	
  
	
  	
  function	
  it_displays_big_movie_title($movie)	
  
	
  	
  {	
  
	
  	
  	
  	
  $movie-­‐>getTitle()	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  -­‐>willReturn('Star	
  Wars’);	
  
!

	
  	
  	
  	
  $this-­‐>displayTitle($movie)	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  -­‐>shouldReturn('<h1>Star	
  Wars</h1>');	
  
	
  	
  }	
  
}
MOCKS
class	
  CinemaSpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  /**	
  
	
  	
  	
  *	
  @param	
  DvdPlayer	
  $dvdPlayer	
  
	
  	
  	
  *	
  @param	
  MovieDisc	
  $movieDisc	
  
	
  	
  	
  */	
  
	
  	
  function	
  it_plays_movie($dvdPlayer,	
  $movieDisc)	
  
	
  	
  {	
  
	
  	
  	
  	
  $dvdPlayer-­‐>playDisc($movieDisc)	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  -­‐>shouldBeCalled();	
  
!

	
  	
  	
  	
  $this-­‐>setPlayer($dvdPlayer);	
  
	
  	
  	
  	
  $this-­‐>playMovie($movieDisc);	
  
	
  	
  }	
  
}
SPIES
class	
  CinemaSpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  /**	
  
	
  	
  	
  *	
  @param	
  DvdPlayer	
  $dvdPlayer	
  
	
  	
  	
  *	
  @param	
  MovieDisc	
  $movieDisc	
  
	
  	
  	
  */	
  
	
  	
  function	
  it_plays_movie($dvdPlayer,	
  $movieDisc)	
  
	
  	
  {	
  
	
  	
  	
  	
  $this-­‐>setPlayer($dvdPlayer);	
  
	
  	
  	
  	
  $this-­‐>playMovie($movieDisc);	
  
!

	
  	
  	
  	
  $dvdPlayer-­‐>playDisc($movieDisc)	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  -­‐>shouldHaveBeenCalled();	
  
	
  	
  }	
  
}
SET UP & TEAR DOWN
class	
  CinemaSpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  	
  	
  /**	
  
	
  	
  	
  	
  	
  *	
  @param	
  BoxOffice	
  $boxOffice	
  
	
  	
  	
  	
  	
  */	
  
	
  	
  	
  	
  function	
  let($boxOffice)	
  
	
  	
  	
  	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  $this-­‐>beConstructedWith($boxOffice);	
  
	
  	
  	
  	
  }	
  
!

	
  	
  	
  	
  function	
  letGo()	
  
	
  	
  	
  	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  $this-­‐>tellPeopleToGoHome();	
  
	
  	
  	
  	
  }	
  
}
BUT…
HOW TO
‘DESIGN’?
THREE RULES OF TDD

1.
2.
3.

WRITE NO PRODUCTION CODE EXCEPT 

TO PASS A FAILING TEST
WRITE ONLY ENOUGH OF A TEST 

TO DEMONSTRATE A FAILURE
WRITE ONLY ENOUGH PRODUCTION 

CODE TO PASS A TEST
4 RULES OF SIMPLE DESIGN

1.
2.
3.
4.

Passes all the tests
Express every idea we need to
express
Contains no duplication
Minimized the number of classes,
methods and other moving parts
SMELLS

1.
2.
3.
4.

CODE SMELLS
TEST SMELLS
DRY SMELLS
…and others
And?
QUICK START

{	
  
	
  	
  	
  	
  "require-­‐dev":	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  "phpspec/phpspec":	
  "2.0.*@dev"	
  
	
  	
  	
  	
  },	
  
	
  	
  	
  	
  "config":	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  "bin-­‐dir":	
  "bin"	
  
	
  	
  	
  	
  },	
  
	
  	
  	
  	
  "autoload":	
  {"psr-­‐0":	
  {"":	
  "src"}}	
  
}

http://phpspec.net/
THANK YOU!

?
JOIND.IN/10130

November Camp - Spec BDD with PHPSpec 2