Advertisement

PHPSpec - the only Design Tool you need - 4Developers

Independent Software Consultant & Trainer at Domain Centric
Apr. 7, 2014
Advertisement

More Related Content

Advertisement
Advertisement

PHPSpec - the only Design Tool you need - 4Developers

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