Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Upcoming SlideShare
Loading in...5
×
 

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

on

  • 9,784 views

Slides from my talk at Dutch PHP Conference in Amsterdam

Slides from my talk at Dutch PHP Conference in Amsterdam

Statistics

Views

Total Views
9,784
Views on SlideShare
9,767
Embed Views
17

Actions

Likes
33
Downloads
48
Comments
0

8 Embeds 17

http://librosweb.es 6
http://localhost 4
https://twitter.com 2
http://ds4.corp.gq1.yahoo.com 1
https://home.jolicloud.com 1
https://www.blogger.com 1
http://slideshare.neatcn.com 1
http://www.slideee.com 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

CC Attribution-ShareAlike LicenseCC Attribution-ShareAlike License

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

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

  • PHPSpecthe only Design Tool you need flickr.com/dad/5528226004
  • 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!
  • It’s hard to change!Rigidity
  • We're afraid to change it…
  • We're to change itFragility
  • We cannot
 reuse it!
  • We cannot reuse it!Immobility
  • What is Design about?
  • ‘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
  • Design is about Messaging
  • $orders  =  $orderRepository      -­‐>getEntityManager()        -­‐>createOrderQuery($customer)        -­‐>execute();
  • $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
  • 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  testFindCustomerById()          {                  $customerRepository  =  new  CustomerRepository;   !                $customer  =  $customerRepository-­‐>findById(5);                  $this-­‐>assertInstanceOf('Customer',  $customer);          }   }
  • class  CustomerRepositorySpec  extends  ObjectBehavior   {          function  it_is_initializable()          {                  $this-­‐>shouldHaveType('CustomerRepository');          }   }
  • 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_preferences()          {                  $customer  =  $this-­‐>findById(5);   !                $customer-­‐>shouldBeAnInstanceOf('Customer');          }   }
  • 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_holland_as_avialable_country()   {          $this-­‐>getCountryCodes()-­‐>shouldHaveValue('NL');   }   ! public  function  getMatchers()   {          return  [                  'haveValue'  =>  function  ($subject,  $value)  {                                  return  in_array($value,  $subject);                          }          ];   }
  • 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  it_returns_customer_by_id()   {          $customer  =  $this-­‐>findById(5);   !        $customer-­‐>shouldBeAnInstanceOf('Customer');          }   }
  • function  let(EntityManager  $entityManager)   {          $this-­‐>beConstructedWith($entityManager);   }   ! function  it_returns_customer_by_id()   {          $customer  =  $this-­‐>findById(5);   !        $customer-­‐>shouldBeAnInstanceOf('Customer');          }   }
  • Stub provides "indirect input" to the tested code
  • function  it_bolds_the_output(Stream  $stream)   {          $stream-­‐>getOutput()                        -­‐>willReturn('DPC');   !        $this-­‐>bold($stream)                    -­‐>shouldReturn('<b>DPC</b>’);   }
  • function  it_bolds_the_output(Stream  $stream)   {          $stream-­‐>getOutput()                        -­‐>willReturn('DPC');   !        $this-­‐>bold($stream)                    -­‐>shouldReturn('<b>DPC</b>’);   }
  • Mocks verifies "indirect output” of the tested code
  • function  let(Logger  $logger)   {          $this-­‐>beConstructedWith($logger);   }   ! function  it_returns_customer_by_id(Logger  $logger)   {          $logger-­‐>debug('DB  queried')                        -­‐>shouldBeCalled();   !        $this-­‐>findById(5);   }
  • function  let(Logger  $logger)   {          $this-­‐>beConstructedWith($logger);   }   ! function  it_returns_customer_by_id(Logger  $logger)   {          $logger-­‐>debug('DB  queried')                        -­‐>shouldBeCalled();   !        $this-­‐>findById(5);   }
  • Spy verifies "indirect output” by asserting the expectations afterwards
  • function  let(Logger  $logger)   {          $this-­‐>beConstructedWith($logger);   }   ! function  it_returns_customer_by_id(Logger  $logger)   {          $this-­‐>findById(5);   !        $logger-­‐>debug('DB  queried')                        -­‐>shouldHaveBeenCalled();   }
  • function  let(Logger  $logger)   {          $this-­‐>beConstructedWith($logger);   }   ! function  it_returns_customer_by_id(Logger  $logger)   {          $this-­‐>findById(5);   !        $logger-­‐>debug('DB  queried')                        -­‐>shouldHaveBeenCalled();   }
  • (a bit more) complex example
  • 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);     }
  • 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);     }
  • 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);     }
  • 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);     }
  • 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);     }
  • 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,            SecurityContext  $securityContext,            TokenInterface  $token,  User  $user)     {          $securityContext-­‐>getToken()-­‐>willReturn($token);          $token-­‐>getUser()-­‐>willReturn($user);   !        $user-­‐>setPreferences(Argument::type('Preferences'))                    -­‐>shouldBeCalled();   !        $this-­‐>handle($event);     }
  • function  it_returns_user_from_token(          SecurityContext  $securityContext,            TokenInterface  $token,  User  $user)   {          $securityContext-­‐>getToken()-­‐>willReturn($token);          $token-­‐>getUser()-­‐>willReturn($user);   !        $this-­‐>getUser()-­‐>shouldReturn($user);   }   !
  • public  function  __construct(SecurityContext  $securityContext){          $this-­‐>securityContext  =  $securityContext;   }   ! public  function  getUser()   {          $token  =  $this-­‐>securityContext-­‐>getToken();          if  ($token  instanceof  TokenInterface)  {                  return  $token-­‐>getUser();          }   !        return  null;   }
  • 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);     }
  • function  it_loads_user_preferences(          GetResponseEvent  $event,                                  DomainSecurityContext  $securityContext,  
        User  $user)   {          $securityContext-­‐>getUser()-­‐>willReturn($user);   !        $user-­‐>setPreferences(Argument::type(‘Preferences'))                    -­‐>shouldBeCalled();   !        $this-­‐>handle($event);   }  
  • Composition over Inheritance separation of concerns small, well focused objects composition is simpler to test
  • public  function  __construct(SecurityContext  $securityContext){          $this-­‐>securityContext  =  $securityContext;   }   ! public  function  getUser()   {          $token  =  $this-­‐>securityContext-­‐>getToken();          if  ($token  instanceof  TokenInterface)  {                  return  $token-­‐>getUser();          }   !        return  null;   }
  • We can still improve
  • function  it_loads_user_preferences(          GetResponseEvent  $event,                                  DomainSecurityContext  $securityContext,  
        User  $user)   {          $securityContext-­‐>getUser()-­‐>willReturn($user);   !        $user-­‐>setPreferences(Argument::type(‘Preferences'))                    -­‐>shouldBeCalled();   !        $this-­‐>handle($event);   }  
  • function  it_loads_user_preferences(          GetResponseEvent  $event,                                  DomainSecurityContextInterface  $securityContext,  
        User  $user)   {          $securityContext-­‐>getUser()-­‐>willReturn($user);   !        $user-­‐>setPreferences(Argument::type(‘Preferences'))                    -­‐>shouldBeCalled();   !        $this-­‐>handle($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
  • encourages injecting right Collaborators
  • and following Law of Demeter
  • 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!
  • joind.in/10864