Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need

26,477 views

Published on

Slides from my talk at Dutch PHP Conference in Amsterdam

Published in: Engineering, Technology
  • Be the first to comment

Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need

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

×