PHPSpec - the only Design Tool you need - 4Developers

  • 12,690 views
Uploaded on

Slides from my talk at 4Developers conference in Warsaw

Slides from my talk at 4Developers conference in Warsaw

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
12,690
On Slideshare
0
From Embeds
0
Number of Embeds
9

Actions

Shares
Downloads
79
Comments
0
Likes
49

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 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!