Paying off technical debt with PHPSpec

Post on 06-Jul-2015

49 views 0 download

Tags:

description

An introduction to PHPSpec with a focus on introducing it to legacy projects.

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