0
PHPSpec
the only Design Tool you need
flickr.com/mobilestreetlife/4179063482/
Kacper Gunia @cakper
Software Engineer @SensioLabsUK
Symfony Certified Developer
PHPers Silesia @PHPersPL
!
‘Is my code well 

designed?’
’That’s not the way
I would have done it…’
It’s hard to

change!
We're afraid
to change it…
We cannot

reuse it!
So what
Design
is about?
‘The key in making great and
growable systems is much more to
design how its
modules communicate
rather than what their in...
Design is about
Messaging
$orders	
  =	
  $orderRepository	
  
	
  	
  -­‐>getEntityManager()	
  
	
  	
  	
  -­‐>createOrderQuery($customer)	
  
	
...
$orders	
  =	
  $orderRepository	
  
	
  	
  -­‐>findBy($customer);	
  
!
We have
to refactor! :)
We need
two weeks
to refactor! :)
We need
two sprints
to refactor! :|
We need
two months
to refactor! :/
Refactoring
is the process of restructuring
existing code without
changing its external behavior
4 Rules of Simple Design
1. Passes its tests
2. Minimizes duplication
3. Maximizes clarity
4. Has fewer elements
…so we need
to write
Tests!
how
to write
Tests?
Tests
Driven
Development
Red
GreenRefactor
Red
GreenRefactor
But!
How to test
something that
doesn’t exist?
flickr.com/ucumari/580865728/
Test in TDD
means
specification
Specification
describes
behavior
Behavior
Driven
Development
BDD improves
Naming
Conventions
Tools
Story BDD
vs
Spec BDD
Story BDD
description of
business-targeted
application behavior
Spec BDD
specification for
low-level
implementation
http://phpspec.net/
Spec BDD
tool created by
@_md & @everzet
Bundled with
mocking library
Prophecy
composer	
  create-­‐project	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  

	
  	
  	
  	
  	
  	
  	
  	
  	
  cakper/phpspec-­...
But!
Isn’t it a tool just
like PHPUnit?
PHPUnit is a
Testing Tool
PHPSpec is the
Design Tool
class	
  CustomerRepositoryTest	
  extends	
  
PHPUnit_Framework_TestCase	
  
{	
  
	
  	
  	
  	
  function	
  testClassE...
class	
  CustomerRepositorySpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  	
  	
  function	
  it_is_initializable()	
...
Naming
TestCase
!
Specification
Test
!
Example
Assertion
!
Expectation
OK, so how to
specify a method?
What method can do?
return a value
modify state
delegate
throw an exception
Command-Query
Separation
Command
change the state of a system
but do not return a value
Query
return a result and do not
change the state of the
system (free of side effects)
Never both!
class	
  CustomerRepositorySpec	
  extends	
  ObjectBehavior	
  
{	
  
	
  	
  	
  	
  function	
  it_loads_user_preferenc...
Matchers
Type
shouldBeAnInstanceOf(*)
shouldReturnAnInstanceOf(*)
shouldHaveType(*)
$customer-­‐>shouldBeAnInstanceOf('Customer');
Identity ===
shouldReturn(*)
shouldBe(*)
shouldEqual(*)
shouldBeEqualTo(*)
$this-­‐>findById(-­‐1)-­‐>shouldReturn(null);
Comparison ==
shouldBeLike(*)
$this-­‐>getAmount()-­‐>shouldBeLike(5);
Throw
throw(*)->during*()
$this-­‐>shouldThrow(‘InvalidArgumentException’)

	
  	
  	
  	
  	
  -­‐>duringFindByCustomer(null);
Object State
shouldHave*()
$car-­‐>hasEngine();	
  
!
$this-­‐>shouldHaveEngine();
Scalar
shouldBeString()
shouldBeArray()
Count
shouldHaveCount(*)
Or write your own
Inline Matcher
function	
  it_should_have_poland_as_avialable_country()	
  
{	
  
	
  	
  	
  	
  $this-­‐>getCountryCodes()-­‐>shouldHav...
But!
Design is about
Messaging!
And (so far) there
is no messaging…
London School
Mockist TDD
only tested object is real
Test Doubles
Dummy
tested code requires
parameter but
doesn’t need to use it
function	
  let(EntityManager	
  $entityManager)	
  
{	
  
	
  	
  	
  	
  $this-­‐>beConstructedWith($entityManager);	
  ...
function	
  let(EntityManager	
  $entityManager)	
  
{	
  
	
  	
  	
  	
  $this-­‐>beConstructedWith($entityManager);	
  ...
Stub
provides "indirect input"
to the tested code
function	
  it_bolds_the_output(Stream	
  $stream)	
  
{	
  
	
  	
  	
  	
  $stream-­‐>getOutput()	
  
	
  	
  	
  	
  	
...
function	
  it_bolds_the_output(Stream	
  $stream)	
  
{	
  
	
  	
  	
  	
  $stream-­‐>getOutput()	
  
	
  	
  	
  	
  	
...
Mocks
verifies "indirect output”
of the tested code
function	
  let(Logger	
  $logger)	
  
{	
  
	
  	
  	
  	
  $this-­‐>beConstructedWith($logger);	
  
}	
  
!
function	
  ...
function	
  let(Logger	
  $logger)	
  
{	
  
	
  	
  	
  	
  $this-­‐>beConstructedWith($logger);	
  
}	
  
!
function	
  ...
Spy
verifies "indirect output”
by asserting the expectations
afterwards
function	
  let(Logger	
  $logger)	
  
{	
  
	
  	
  	
  	
  $this-­‐>beConstructedWith($logger);	
  
}	
  
!
function	
  ...
function	
  let(Logger	
  $logger)	
  
{	
  
	
  	
  	
  	
  $this-­‐>beConstructedWith($logger);	
  
}	
  
!
function	
  ...
(a bit more)
complex example
function	
  let(SecurityContext	
  $securityContext)	
  {	
  
	
  	
  	
  	
  $this-­‐>beConstructedWith($securityContext)...
function	
  let(SecurityContext	
  $securityContext)	
  {	
  
	
  	
  	
  	
  $this-­‐>beConstructedWith($securityContext)...
function	
  let(SecurityContext	
  $securityContext)	
  {	
  
	
  	
  	
  	
  $this-­‐>beConstructedWith($securityContext)...
function	
  let(SecurityContext	
  $securityContext)	
  {	
  
	
  	
  	
  	
  $this-­‐>beConstructedWith($securityContext)...
function	
  let(SecurityContext	
  $securityContext)	
  {	
  
	
  	
  	
  	
  $this-­‐>beConstructedWith($securityContext)...
But mocking
becomes painful…
And it smells…
Law of Demeter
unit should only
talk to its friends;
don't talk to strangers
It’s time to
refactor! :)
function	
  it_loads_user_preferences(	
  
	
  	
  	
  	
  GetResponseEvent	
  $event,	
  	
  
	
  	
  	
  	
  SecurityCon...
function	
  it_returns_user_from_token(	
  
	
  	
  	
  	
  SecurityContext	
  $securityContext,	
  	
  
	
  	
  	
  	
  T...
public	
  function	
  __construct(SecurityContext	
  $securityContext){	
  
	
  	
  	
  	
  $this-­‐>securityContext	
  =	...
function	
  it_loads_user_preferences(	
  
	
  	
  	
  	
  GetResponseEvent	
  $event,	
  	
  
	
  	
  	
  	
  SecurityCon...
function	
  it_loads_user_preferences(	
  
	
  	
  	
  	
  GetResponseEvent	
  $event,	
  	
  	
  	
  	
  	
  	
  	
  	
  ...
Composition over Inheritance
separation of concerns
small, well focused objects
composition is simpler to test
public	
  function	
  __construct(SecurityContext	
  $securityContext){	
  
	
  	
  	
  	
  $this-­‐>securityContext	
  =	...
We can still
improve
function	
  it_loads_user_preferences(	
  
	
  	
  	
  	
  GetResponseEvent	
  $event,	
  	
  	
  	
  	
  	
  	
  	
  	
  ...
function	
  it_loads_user_preferences(	
  
	
  	
  	
  	
  GetResponseEvent	
  $event,	
  	
  	
  	
  	
  	
  	
  	
  	
  ...
Dependency
Inversion Principle
high-level modules should
not depend on low-level
modules; both should depend
on abstractions
DIP states:
DIP states:
abstractions should not
depend upon details; details
should depend upon
abstractions
Isn’t it overhead?
What are benefits of
using PHPSpec?
TDD-cycle
oriented tool
ease Mocking
focused on
Messaging
encourage
injecting right
Collaborators
and following
Demeter Low
enables
Refactoring
and gives you
Regression Safety
and it’s trendy ;)
Is PHPSpec
the only design tool
we need?
s
So it helps ;)
Kacper Gunia
Software Engineer
Symfony Certified Developer
PHPers Silesia
Thanks!
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
Upcoming SlideShare
Loading in...5
×

PHPSpec - the only Design Tool you need - 4Developers

13,835

Published on

Slides from my talk at 4Developers conference in Warsaw

Published in: Technology

Transcript of "PHPSpec - the only Design Tool you need - 4Developers"

  1. 1. PHPSpec the only Design Tool you need flickr.com/mobilestreetlife/4179063482/
  2. 2. Kacper Gunia @cakper Software Engineer @SensioLabsUK Symfony Certified Developer PHPers Silesia @PHPersPL
  3. 3. ! ‘Is my code well 
 designed?’
  4. 4. ’That’s not the way I would have done it…’
  5. 5. It’s hard to
 change!
  6. 6. We're afraid to change it…
  7. 7. We cannot
 reuse it!
  8. 8. So what Design is about?
  9. 9. ‘The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.’ Alan Kay
  10. 10. Design is about Messaging
  11. 11. $orders  =  $orderRepository      -­‐>getEntityManager()        -­‐>createOrderQuery($customer)        -­‐>execute();
  12. 12. $orders  =  $orderRepository      -­‐>findBy($customer);   !
  13. 13. We have to refactor! :)
  14. 14. We need two weeks to refactor! :)
  15. 15. We need two sprints to refactor! :|
  16. 16. We need two months to refactor! :/
  17. 17. Refactoring is the process of restructuring existing code without changing its external behavior
  18. 18. 4 Rules of Simple Design 1. Passes its tests 2. Minimizes duplication 3. Maximizes clarity 4. Has fewer elements
  19. 19. …so we need to write Tests!
  20. 20. how to write Tests?
  21. 21. Tests Driven Development
  22. 22. Red GreenRefactor
  23. 23. Red GreenRefactor
  24. 24. But!
  25. 25. How to test something that doesn’t exist? flickr.com/ucumari/580865728/
  26. 26. Test in TDD means specification
  27. 27. Specification describes behavior
  28. 28. Behavior Driven Development
  29. 29. BDD improves Naming Conventions Tools
  30. 30. Story BDD vs Spec BDD
  31. 31. Story BDD description of business-targeted application behavior
  32. 32. Spec BDD specification for low-level implementation
  33. 33. http://phpspec.net/
  34. 34. Spec BDD tool created by @_md & @everzet
  35. 35. Bundled with mocking library Prophecy
  36. 36. composer  create-­‐project                    
                  cakper/phpspec-­‐standard  
                  project-­‐name
  37. 37. But!
  38. 38. Isn’t it a tool just like PHPUnit?
  39. 39. PHPUnit is a Testing Tool
  40. 40. PHPSpec is the Design Tool
  41. 41. class  CustomerRepositoryTest  extends   PHPUnit_Framework_TestCase   {          function  testClassExists()          {                  $customerRepository  =  new  CustomerRepository;   !                $customer  =  $customerRepository-­‐>findById(5);                  $this-­‐>assertInstanceOf('Customer',  $customer);          }   }
  42. 42. class  CustomerRepositorySpec  extends  ObjectBehavior   {          function  it_is_initializable()          {                  $this-­‐>shouldHaveType('CustomerRepository');          }   }
  43. 43. Naming
  44. 44. TestCase ! Specification
  45. 45. Test ! Example
  46. 46. Assertion ! Expectation
  47. 47. OK, so how to specify a method?
  48. 48. What method can do? return a value modify state delegate throw an exception
  49. 49. Command-Query Separation
  50. 50. Command change the state of a system but do not return a value
  51. 51. Query return a result and do not change the state of the system (free of side effects)
  52. 52. Never both!
  53. 53. class  CustomerRepositorySpec  extends  ObjectBehavior   {          function  it_loads_user_preferences()          {                  $customer  =  $this-­‐>findById(5);   !                $customer-­‐>shouldBeAnInstanceOf('Customer');          }   }
  54. 54. Matchers
  55. 55. Type shouldBeAnInstanceOf(*) shouldReturnAnInstanceOf(*) shouldHaveType(*)
  56. 56. $customer-­‐>shouldBeAnInstanceOf('Customer');
  57. 57. Identity === shouldReturn(*) shouldBe(*) shouldEqual(*) shouldBeEqualTo(*)
  58. 58. $this-­‐>findById(-­‐1)-­‐>shouldReturn(null);
  59. 59. Comparison == shouldBeLike(*)
  60. 60. $this-­‐>getAmount()-­‐>shouldBeLike(5);
  61. 61. Throw throw(*)->during*()
  62. 62. $this-­‐>shouldThrow(‘InvalidArgumentException’)
          -­‐>duringFindByCustomer(null);
  63. 63. Object State shouldHave*()
  64. 64. $car-­‐>hasEngine();   ! $this-­‐>shouldHaveEngine();
  65. 65. Scalar shouldBeString() shouldBeArray()
  66. 66. Count shouldHaveCount(*)
  67. 67. Or write your own Inline Matcher
  68. 68. function  it_should_have_poland_as_avialable_country()   {          $this-­‐>getCountryCodes()-­‐>shouldHaveValue('PL');   }   ! public  function  getMatchers()   {          return  [                  'haveValue'  =>  function  ($subject,  $value)  {                                  return  in_array($value,  $subject);                          }          ];   }
  69. 69. But!
  70. 70. Design is about Messaging!
  71. 71. And (so far) there is no messaging…
  72. 72. London School
  73. 73. Mockist TDD only tested object is real
  74. 74. Test Doubles
  75. 75. Dummy tested code requires parameter but doesn’t need to use it
  76. 76. function  let(EntityManager  $entityManager)   {          $this-­‐>beConstructedWith($entityManager);   }   ! function  it_returns_customer_by_id()   {          $customer  =  $this-­‐>findById(5);   !        $customer-­‐>shouldBeAnInstanceOf('Customer');          }   }
  77. 77. function  let(EntityManager  $entityManager)   {          $this-­‐>beConstructedWith($entityManager);   }   ! function  it_returns_customer_by_id()   {          $customer  =  $this-­‐>findById(5);   !        $customer-­‐>shouldBeAnInstanceOf('Customer');          }   }
  78. 78. Stub provides "indirect input" to the tested code
  79. 79. function  it_bolds_the_output(Stream  $stream)   {          $stream-­‐>getOutput()                        -­‐>willReturn('4  Developers');   !        $this-­‐>bold($stream)                    -­‐>shouldReturn('<b>4  Developers</b>’);   }
  80. 80. function  it_bolds_the_output(Stream  $stream)   {          $stream-­‐>getOutput()                        -­‐>willReturn('4  Developers');   !        $this-­‐>bold($stream)                    -­‐>shouldReturn('<b>4  Developers</b>’);   }
  81. 81. Mocks verifies "indirect output” of the tested code
  82. 82. function  let(Logger  $logger)   {          $this-­‐>beConstructedWith($logger);   }   ! function  it_returns_customer_by_id(Logger  $logger)   {          $logger-­‐>debug('DB  queried')                        -­‐>shouldBeCalled();   !        $this-­‐>findById(5);   }
  83. 83. function  let(Logger  $logger)   {          $this-­‐>beConstructedWith($logger);   }   ! function  it_returns_customer_by_id(Logger  $logger)   {          $logger-­‐>debug('DB  queried')                        -­‐>shouldBeCalled();   !        $this-­‐>findById(5);   }
  84. 84. Spy verifies "indirect output” by asserting the expectations afterwards
  85. 85. function  let(Logger  $logger)   {          $this-­‐>beConstructedWith($logger);   }   ! function  it_returns_customer_by_id(Logger  $logger)   {          $this-­‐>findById(5);   !        $logger-­‐>debug('DB  queried')                        -­‐>shouldHaveBeenCalled();   }
  86. 86. function  let(Logger  $logger)   {          $this-­‐>beConstructedWith($logger);   }   ! function  it_returns_customer_by_id(Logger  $logger)   {          $this-­‐>findById(5);   !        $logger-­‐>debug('DB  queried')                        -­‐>shouldHaveBeenCalled();   }
  87. 87. (a bit more) complex example
  88. 88. function  let(SecurityContext  $securityContext)  {          $this-­‐>beConstructedWith($securityContext);     }   ! function  it_loads_user_preferences(          GetResponseEvent  $event,  SecurityContext  $securityContext,            TokenInterface  $token,  User  $user)     {          $securityContext-­‐>getToken()-­‐>willReturn($token);          $token-­‐>getUser()-­‐>willReturn($user);   !        $user-­‐>setPreferences(Argument::type('Preferences'))                    -­‐>shouldBeCalled();   !        $this-­‐>handle($event);     }
  89. 89. function  let(SecurityContext  $securityContext)  {          $this-­‐>beConstructedWith($securityContext);     }   ! function  it_loads_user_preferences(          GetResponseEvent  $event,  SecurityContext  $securityContext,            TokenInterface  $token,  User  $user)     {          $securityContext-­‐>getToken()-­‐>willReturn($token);          $token-­‐>getUser()-­‐>willReturn($user);   !        $user-­‐>setPreferences(Argument::type('Preferences'))                    -­‐>shouldBeCalled();   !        $this-­‐>handle($event);     }
  90. 90. function  let(SecurityContext  $securityContext)  {          $this-­‐>beConstructedWith($securityContext);     }   ! function  it_loads_user_preferences(          GetResponseEvent  $event,  SecurityContext  $securityContext,            TokenInterface  $token,  User  $user)     {          $securityContext-­‐>getToken()-­‐>willReturn($token);          $token-­‐>getUser()-­‐>willReturn($user);   !        $user-­‐>setPreferences(Argument::type('Preferences'))                    -­‐>shouldBeCalled();   !        $this-­‐>handle($event);     }
  91. 91. function  let(SecurityContext  $securityContext)  {          $this-­‐>beConstructedWith($securityContext);     }   ! function  it_loads_user_preferences(          GetResponseEvent  $event,  SecurityContext  $securityContext,            TokenInterface  $token,  User  $user)     {          $securityContext-­‐>getToken()-­‐>willReturn($token);          $token-­‐>getUser()-­‐>willReturn($user);   !        $user-­‐>setPreferences(Argument::type('Preferences'))                    -­‐>shouldBeCalled();   !        $this-­‐>handle($event);     }
  92. 92. function  let(SecurityContext  $securityContext)  {          $this-­‐>beConstructedWith($securityContext);     }   ! function  it_loads_user_preferences(          GetResponseEvent  $event,  SecurityContext  $securityContext,            TokenInterface  $token,  User  $user)     {          $securityContext-­‐>getToken()-­‐>willReturn($token);          $token-­‐>getUser()-­‐>willReturn($user);   !        $user-­‐>setPreferences(Argument::type('Preferences'))                    -­‐>shouldBeCalled();   !        $this-­‐>handle($event);     }
  93. 93. But mocking becomes painful…
  94. 94. And it smells…
  95. 95. Law of Demeter unit should only talk to its friends; don't talk to strangers
  96. 96. It’s time to refactor! :)
  97. 97. function  it_loads_user_preferences(          GetResponseEvent  $event,            SecurityContext  $securityContext,            TokenInterface  $token,  User  $user)     {          $securityContext-­‐>getToken()-­‐>willReturn($token);          $token-­‐>getUser()-­‐>willReturn($user);   !        $user-­‐>setPreferences(Argument::type('Preferences'))                    -­‐>shouldBeCalled();   !        $this-­‐>handle($event);     }
  98. 98. function  it_returns_user_from_token(          SecurityContext  $securityContext,            TokenInterface  $token,  User  $user)   {          $securityContext-­‐>getToken()-­‐>willReturn($token);          $token-­‐>getUser()-­‐>willReturn($user);   !        $this-­‐>getUser()-­‐>shouldRetun($user);   }   !
  99. 99. public  function  __construct(SecurityContext  $securityContext){          $this-­‐>securityContext  =  $securityContext;   }   ! public  function  getUser()   {          $token  =  $this-­‐>securityContext-­‐>getToken();          if  ($token  instanceof  TokenInterface)  {                  return  $token-­‐>getUser();          }   !        return  null;   }
  100. 100. function  it_loads_user_preferences(          GetResponseEvent  $event,            SecurityContext  $securityContext,            TokenInterface  $token,  User  $user)     {          $securityContext-­‐>getToken()-­‐>willReturn($token);          $token-­‐>getUser()-­‐>willReturn($user);   !        $user-­‐>setPreferences(Argument::type('Preferences'))                    -­‐>shouldBeCalled();   !        $this-­‐>handle($event);     }
  101. 101. function  it_loads_user_preferences(          GetResponseEvent  $event,                                  DomainSecurityContext  $securityContext,  
        User  $user)   {          $securityContext-­‐>getUser()-­‐>willReturn($user);   !        $user-­‐>setPreferences(Argument::type(‘Preferences'))                    -­‐>shouldBeCalled();   !        $this-­‐>handle($event);   }  
  102. 102. Composition over Inheritance separation of concerns small, well focused objects composition is simpler to test
  103. 103. public  function  __construct(SecurityContext  $securityContext){          $this-­‐>securityContext  =  $securityContext;   }   ! public  function  getUser()   {          $token  =  $this-­‐>securityContext-­‐>getToken();          if  ($token  instanceof  TokenInterface)  {                  return  $token-­‐>getUser();          }   !        return  null;   }
  104. 104. We can still improve
  105. 105. function  it_loads_user_preferences(          GetResponseEvent  $event,                                  DomainSecurityContext  $securityContext,  
        User  $user)   {          $securityContext-­‐>getUser()-­‐>willReturn($user);   !        $user-­‐>setPreferences(Argument::type(‘Preferences'))                    -­‐>shouldBeCalled();   !        $this-­‐>handle($event);   }  
  106. 106. function  it_loads_user_preferences(          GetResponseEvent  $event,                                  DomainSecurityContextInterface  $securityContext,  
        User  $user)   {          $securityContext-­‐>getUser()-­‐>willReturn($user);   !        $user-­‐>setPreferences(Argument::type(‘Preferences'))                    -­‐>shouldBeCalled();   !        $this-­‐>handle($event);   }  
  107. 107. Dependency Inversion Principle
  108. 108. high-level modules should not depend on low-level modules; both should depend on abstractions DIP states:
  109. 109. DIP states: abstractions should not depend upon details; details should depend upon abstractions
  110. 110. Isn’t it overhead?
  111. 111. What are benefits of using PHPSpec?
  112. 112. TDD-cycle oriented tool
  113. 113. ease Mocking
  114. 114. focused on Messaging
  115. 115. encourage injecting right Collaborators
  116. 116. and following Demeter Low
  117. 117. enables Refactoring
  118. 118. and gives you Regression Safety
  119. 119. and it’s trendy ;)
  120. 120. Is PHPSpec the only design tool we need?
  121. 121. s
  122. 122. So it helps ;)
  123. 123. Kacper Gunia Software Engineer Symfony Certified Developer PHPers Silesia Thanks!
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×