PHPSpec - the only Design Tool you need - 4Developers
Upcoming SlideShare
Loading in...5
×
 

PHPSpec - the only Design Tool you need - 4Developers

on

  • 11,703 views

Slides from my talk at 4Developers conference in Warsaw

Slides from my talk at 4Developers conference in Warsaw

Statistics

Views

Total Views
11,703
Views on SlideShare
11,401
Embed Views
302

Actions

Likes
45
Downloads
69
Comments
0

8 Embeds 302

http://stack-dev.ossone.jp 130
https://twitter.com 108
http://stack.osslife.co 43
http://symfony2developer.com 15
http://www.fortantispam.com 3
http://www.google.com 1
https://www.linkedin.com 1
http://www.linkedin.com 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

CC Attribution 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

PHPSpec - the only Design Tool you need - 4Developers PHPSpec - the only Design Tool you need - 4Developers Presentation Transcript

  • 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 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
  • composer  create-­‐project                    
                  cakper/phpspec-­‐standard  
                  project-­‐name
  • 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  testClassExists()          {                  $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_poland_as_avialable_country()   {          $this-­‐>getCountryCodes()-­‐>shouldHaveValue('PL');   }   ! 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('4  Developers');   !        $this-­‐>bold($stream)                    -­‐>shouldReturn('<b>4  Developers</b>’);   }
  • function  it_bolds_the_output(Stream  $stream)   {          $stream-­‐>getOutput()                        -­‐>willReturn('4  Developers');   !        $this-­‐>bold($stream)                    -­‐>shouldReturn('<b>4  Developers</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()-­‐>shouldRetun($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
  • 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!