際際滷

際際滷Share a Scribd company logo
Paying off technical debt with php 
spec 
Presented by 
Follow us on Twitter - @vivaitltd
Many thanks to Inviqua/SensioLabs 
Uk for the swag
Paying off technical debt with PHPSpec
How do we get to "good code" when 
the goal post is always evolving?
We manage our technical debt
But what is technical debt?
"As an evolving program is continually changed, 
its complexity, reflecting deteriorating 
structure, increases unless work is done to 
maintain or reduce it." 
 Meir Manny Lehman, 1980
Technical debt is essential for rapid 
and agile development
Otherwise we end up with something 
like this...
Paying off technical debt with PHPSpec
But debt gets worse the long you 
leave it 
Pay it off early, pay it off often
How do we pay it off?
Paying off technical debt with PHPSpec
What is refactoring? 
"Refactoring is the process of changing a software system in such a 
way that it does not alter the external behavior of the code yet 
improves its internal structure." 
 Martin Fowler
How do we ensure our code's 
behaviour remains consistent?
Paying off technical debt with PHPSpec
The purpose of our specs are to 
describe the behaviour of a class 
through examples.
PHPSpec is not about testing code 
you've already written
Lets write a spec for a user object
First we create a spec for our class 
Command: 
$ bin/phpspec desc Auth/User 
Response: 
> Specification for Auth 
created in spec/User.
# spec/Auth/UserSpec.php 
<?php 
namespace specAuth; 
use PhpSpecObjectBehavior; 
use ProphecyArgument; 
class UserSpec extends ObjectBehavior { 
function it_is_initializable() { 
$this->shouldHaveType('User'); 
} 
}
Not really specifying behaviour 
Lets replace it with a better one
# spec/Auth/UserSpec.php 
class UserSpec extends ObjectBehavior 
{ 
function it_generates_an_id() 
{ 
} 
}
# spec/Auth/UserSpec.php 
class UserSpec extends ObjectBehavior 
{ 
function it_generates_an_id() 
{ 
$this->getId() 
->shouldBeInteger(); 
} 
}
What's happening?
should* methods are called matchers 
They specify expected behaviour
There are lots of matchers 
shouldBe() .......... === 
shouldBeLike() ...... == 
shouldHaveType() .... instanceOf 
shouldHaveCount() ... count() 
shouldThrow() ....... try {} catch {} 
shouldBe*() ......... is*() === true 
shouldHave() ........ has*() === true 
shouldNot*() ........ !(*)
# spec/Auth/UserSpec.php 
function it_stores_a_username() 
{ 
$this->setUsername('LewisWright') 
->shouldBeAnInstance('AuthUser'); 
$this->getUsername() 
->shouldBe('LewisWright'); 
}
# spec/Auth/UserSpec.php 
function it_does_not_allow_spaces_in_usernames() 
{ 
$this 
->shouldThrow('InvalidArgumentException') 
->duringSetUsername('Lewis Wright'); 
}
# spec/Auth/UserSpec.php 
function it_lets_us_disable_a_user() 
{ 
$this->setActive(0) 
->shouldBeAnInstance('AuthUser'); 
$this->shouldNotBeActive(); 
}
Our tests start to build up a picture 
Command: 
$ bin/phpspec run
Our tests start to build up a picture 
Response: 
> specAuthUser 
 it generates an id 
 it stores a username 
 it does not allow spaces in usernames 
 it lets us disable a user 
5 examples (5 passed) 
247ms
Our spec should provide just enough examples 
to be confident we have clearly defined the 
intended behaviour. 
Only write specifications for edge cases if they 
are behaviour we want to specify.
Lets throw in collaborations 
A collaboration is the term for when our object 
interacts with another object
E.g. a User object having Roles 
objects
If our object is interacting with an 
external object (e.g. Roles), we need a 
way of specifying those interactions. 
So we create a fake object, called a double.
# spec/Auth/UserSpec.php 
function it_stores_many_roles(Role $role1, Role $role2) 
{ 
$this->addRole($role1); 
$this->addRole($role2); 
$this->getRoles() 
->shouldHaveCount(2); 
}
$role1 and $role2 may look fairly 
normal, but they're actually doubles. 
Meaning we can fake their behaviour.
# spec/Auth/UserSpec.php 
function it_provides_role_based_access(Role $role) 
{ 
$role->getPermittedAreas() 
->willReturn(['admin']); 
$this->addRole($role); 
$this->canAccess('admin') 
->shouldReturn(true); 
}
We've not actually specified that 
User should call getPermittedAreas 
Meaning we've not explicitly specified that 
collaboration.
# spec/Auth/UserSpec.php 
function it_provides_role_based_access(Role $role) 
{ 
$role->getPermittedAreas() 
->willReturn(['admin']) 
->shouldBeCalled(); 
$this->addRole($role); 
$this->canAccess('admin') 
->shouldReturn(true); 
}
PHPSpec will run the let and let_go 
before and after running each 
scenario 
Meaning we can share common 
stubbed behaviour between 
scenarios
# spec/Auth/UserSpec.php 
function let(Role $role) 
{ 
$role->checkPermittedAreas(Argument::any()) 
->will(function($args) { 
return $args; 
}); 
} 
function let_go() { 
// Do any deconstruction here 
}
PHPSpec is intended as a TDD tool 
to help you design your code through 
specifying it's behaviour. 
But that doesn't mean we can't use it to re-design 
existing code.
We've just invented TDDD! 
Technical Debt Driven Development
By specifying code behaviour, you 
create an environment where 
technical debt becomes managed. 
You, and more importantly other developers, 
can redesign and refactor with confidence.
Thanks for listening!
Symfony2 bootcamp workshop 
We're running a workshop on getting you 
started with Symfony2 on 7th June. 
Tickets are free but limited, so book your place 
ASAP. 
Link on our twitter - @vivaitld

More Related Content

Paying off technical debt with PHPSpec

  • 1. Paying off technical debt with php spec Presented by Follow us on Twitter - @vivaitltd
  • 2. Many thanks to Inviqua/SensioLabs Uk for the swag
  • 4. How do we get to "good code" when the goal post is always evolving?
  • 5. We manage our technical debt
  • 6. But what is technical debt?
  • 7. "As an evolving program is continually changed, its complexity, reflecting deteriorating structure, increases unless work is done to maintain or reduce it." Meir Manny Lehman, 1980
  • 8. Technical debt is essential for rapid and agile development
  • 9. Otherwise we end up with something like this...
  • 11. But debt gets worse the long you leave it Pay it off early, pay it off often
  • 12. How do we pay it off?
  • 14. What is refactoring? "Refactoring is the process of changing a software system in such a way that it does not alter the external behavior of the code yet improves its internal structure." Martin Fowler
  • 15. How do we ensure our code's behaviour remains consistent?
  • 17. The purpose of our specs are to describe the behaviour of a class through examples.
  • 18. PHPSpec is not about testing code you've already written
  • 19. Lets write a spec for a user object
  • 20. First we create a spec for our class Command: $ bin/phpspec desc Auth/User Response: > Specification for Auth created in spec/User.
  • 21. # spec/Auth/UserSpec.php <?php namespace specAuth; use PhpSpecObjectBehavior; use ProphecyArgument; class UserSpec extends ObjectBehavior { function it_is_initializable() { $this->shouldHaveType('User'); } }
  • 22. Not really specifying behaviour Lets replace it with a better one
  • 23. # spec/Auth/UserSpec.php class UserSpec extends ObjectBehavior { function it_generates_an_id() { } }
  • 24. # spec/Auth/UserSpec.php class UserSpec extends ObjectBehavior { function it_generates_an_id() { $this->getId() ->shouldBeInteger(); } }
  • 26. should* methods are called matchers They specify expected behaviour
  • 27. There are lots of matchers shouldBe() .......... === shouldBeLike() ...... == shouldHaveType() .... instanceOf shouldHaveCount() ... count() shouldThrow() ....... try {} catch {} shouldBe*() ......... is*() === true shouldHave() ........ has*() === true shouldNot*() ........ !(*)
  • 28. # spec/Auth/UserSpec.php function it_stores_a_username() { $this->setUsername('LewisWright') ->shouldBeAnInstance('AuthUser'); $this->getUsername() ->shouldBe('LewisWright'); }
  • 29. # spec/Auth/UserSpec.php function it_does_not_allow_spaces_in_usernames() { $this ->shouldThrow('InvalidArgumentException') ->duringSetUsername('Lewis Wright'); }
  • 30. # spec/Auth/UserSpec.php function it_lets_us_disable_a_user() { $this->setActive(0) ->shouldBeAnInstance('AuthUser'); $this->shouldNotBeActive(); }
  • 31. Our tests start to build up a picture Command: $ bin/phpspec run
  • 32. Our tests start to build up a picture Response: > specAuthUser it generates an id it stores a username it does not allow spaces in usernames it lets us disable a user 5 examples (5 passed) 247ms
  • 33. Our spec should provide just enough examples to be confident we have clearly defined the intended behaviour. Only write specifications for edge cases if they are behaviour we want to specify.
  • 34. Lets throw in collaborations A collaboration is the term for when our object interacts with another object
  • 35. E.g. a User object having Roles objects
  • 36. If our object is interacting with an external object (e.g. Roles), we need a way of specifying those interactions. So we create a fake object, called a double.
  • 37. # spec/Auth/UserSpec.php function it_stores_many_roles(Role $role1, Role $role2) { $this->addRole($role1); $this->addRole($role2); $this->getRoles() ->shouldHaveCount(2); }
  • 38. $role1 and $role2 may look fairly normal, but they're actually doubles. Meaning we can fake their behaviour.
  • 39. # spec/Auth/UserSpec.php function it_provides_role_based_access(Role $role) { $role->getPermittedAreas() ->willReturn(['admin']); $this->addRole($role); $this->canAccess('admin') ->shouldReturn(true); }
  • 40. We've not actually specified that User should call getPermittedAreas Meaning we've not explicitly specified that collaboration.
  • 41. # spec/Auth/UserSpec.php function it_provides_role_based_access(Role $role) { $role->getPermittedAreas() ->willReturn(['admin']) ->shouldBeCalled(); $this->addRole($role); $this->canAccess('admin') ->shouldReturn(true); }
  • 42. PHPSpec will run the let and let_go before and after running each scenario Meaning we can share common stubbed behaviour between scenarios
  • 43. # spec/Auth/UserSpec.php function let(Role $role) { $role->checkPermittedAreas(Argument::any()) ->will(function($args) { return $args; }); } function let_go() { // Do any deconstruction here }
  • 44. PHPSpec is intended as a TDD tool to help you design your code through specifying it's behaviour. But that doesn't mean we can't use it to re-design existing code.
  • 45. We've just invented TDDD! Technical Debt Driven Development
  • 46. By specifying code behaviour, you create an environment where technical debt becomes managed. You, and more importantly other developers, can redesign and refactor with confidence.
  • 48. Symfony2 bootcamp workshop We're running a workshop on getting you started with Symfony2 on 7th June. Tickets are free but limited, so book your place ASAP. Link on our twitter - @vivaitld