Paying off technical debt with PHPSpec
-
Upload
lewis-wright -
Category
Software
-
view
49 -
download
0
description
Transcript of Paying off technical debt with PHPSpec
Paying off technical debt with php spec
Presented by
Follow us on Twitter - @vivaitltd
Many thanks to Inviqua/SensioLabs Uk for the swag
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...
But debt gets worse the long you leave it
Pay it off early, pay it off often
How do we pay it off?
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?
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 spec\Auth;
use PhpSpec\ObjectBehavior;use Prophecy\Argument;
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() .... instanceOfshouldHaveCount() ... count()shouldThrow() ....... try {} catch {}shouldBe*() ......... is*() === trueshouldHave() ........ has*() === trueshouldNot*() ........ !(*)
# spec/Auth/UserSpec.php
function it_stores_a_username() { $this->setUsername('LewisWright') ->shouldBeAnInstance('\Auth\User'); $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('\Auth\User'); $this->shouldNotBeActive(); }
Our tests start to build up a picture
Command:
$ bin/phpspec run
Our tests start to build up a picture
Response:
> spec\Auth\User
✔ 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