Paying off technical debt with PHPSpec

48
Paying off technical debt with php spec Presented by Follow us on Twitter - @vivaitltd

description

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

Transcript of Paying off technical debt with PHPSpec

Page 1: Paying off technical debt with PHPSpec

Paying off technical debt with php spec

Presented by

Follow us on Twitter - @vivaitltd

Page 2: Paying off technical debt with PHPSpec

Many thanks to Inviqua/SensioLabs Uk for the swag

Page 3: Paying off technical debt with PHPSpec
Page 4: Paying off technical debt with PHPSpec

How do we get to "good code" when the goal post is always evolving?

Page 5: Paying off technical debt with PHPSpec

We manage our technical debt

Page 6: Paying off technical debt with PHPSpec

But what is technical debt?

Page 7: Paying off technical debt with PHPSpec

"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

Page 8: Paying off technical debt with PHPSpec

Technical debt is essential for rapid and agile development

Page 9: Paying off technical debt with PHPSpec

Otherwise we end up with something like this...

Page 10: Paying off technical debt with PHPSpec
Page 11: Paying off technical debt with PHPSpec

But debt gets worse the long you leave it

Pay it off early, pay it off often

Page 12: Paying off technical debt with PHPSpec

How do we pay it off?

Page 13: Paying off technical debt with PHPSpec
Page 14: 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

Page 15: Paying off technical debt with PHPSpec

How do we ensure our code's behaviour remains consistent?

Page 16: Paying off technical debt with PHPSpec
Page 17: Paying off technical debt with PHPSpec

The purpose of our specs are to describe the behaviour of a class

through examples.

Page 18: Paying off technical debt with PHPSpec

PHPSpec is not about testing code you've already written

Page 19: Paying off technical debt with PHPSpec

Lets write a spec for a user object

Page 20: Paying off technical debt with PHPSpec

First we create a spec for our class

Command:

$ bin/phpspec desc Auth/User

Response:

> Specification for Auth created in spec/User.

Page 21: Paying off technical debt with PHPSpec

# 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'); }

}

Page 22: Paying off technical debt with PHPSpec

Not really specifying behaviour

Lets replace it with a better one

Page 23: Paying off technical debt with PHPSpec

# spec/Auth/UserSpec.php

class UserSpec extends ObjectBehavior{ function it_generates_an_id() { }}

Page 24: Paying off technical debt with PHPSpec

# spec/Auth/UserSpec.php

class UserSpec extends ObjectBehavior{ function it_generates_an_id() { $this->getId() ->shouldBeInteger(); }}

Page 25: Paying off technical debt with PHPSpec

What's happening?

Page 26: Paying off technical debt with PHPSpec

should* methods are called matchers

They specify expected behaviour

Page 27: Paying off technical debt with PHPSpec

There are lots of matchers

shouldBe() .......... ===shouldBeLike() ...... ==shouldHaveType() .... instanceOfshouldHaveCount() ... count()shouldThrow() ....... try {} catch {}shouldBe*() ......... is*() === trueshouldHave() ........ has*() === trueshouldNot*() ........ !(*)

Page 28: Paying off technical debt with PHPSpec

# spec/Auth/UserSpec.php

function it_stores_a_username() { $this->setUsername('LewisWright') ->shouldBeAnInstance('\Auth\User'); $this->getUsername() ->shouldBe('LewisWright'); }

Page 29: Paying off technical debt with PHPSpec

# spec/Auth/UserSpec.php

function it_does_not_allow_spaces_in_usernames() { $this ->shouldThrow('\InvalidArgumentException') ->duringSetUsername('Lewis Wright'); }

Page 30: Paying off technical debt with PHPSpec

# spec/Auth/UserSpec.php

function it_lets_us_disable_a_user() { $this->setActive(0) ->shouldBeAnInstance('\Auth\User'); $this->shouldNotBeActive(); }

Page 31: Paying off technical debt with PHPSpec

Our tests start to build up a picture

Command:

$ bin/phpspec run

Page 32: Paying off technical debt with PHPSpec

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

Page 33: Paying off technical debt with PHPSpec

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.

Page 34: Paying off technical debt with PHPSpec

Lets throw in collaborations

A collaboration is the term for when our object interacts with another object

Page 35: Paying off technical debt with PHPSpec

E.g. a User object having Roles objects

Page 36: Paying off technical debt with PHPSpec

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.

Page 37: Paying off technical debt with PHPSpec

# spec/Auth/UserSpec.php

function it_stores_many_roles(Role $role1, Role $role2) { $this->addRole($role1); $this->addRole($role2);

$this->getRoles() ->shouldHaveCount(2); }

Page 38: Paying off technical debt with PHPSpec

$role1 and $role2 may look fairly normal, but they're actually doubles.

Meaning we can fake their behaviour.

Page 39: Paying off technical debt with PHPSpec

# spec/Auth/UserSpec.php

function it_provides_role_based_access(Role $role) { $role->getPermittedAreas() ->willReturn(['admin']);

$this->addRole($role); $this->canAccess('admin') ->shouldReturn(true); }

Page 40: Paying off technical debt with PHPSpec

We've not actually specified that User should call getPermittedAreas

Meaning we've not explicitly specified that collaboration.

Page 41: Paying off technical debt with PHPSpec

# 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); }

Page 42: Paying off technical debt with PHPSpec

PHPSpec will run the let and let_go before and after running each

scenario

Meaning we can share common stubbed behaviour between

scenarios

Page 43: Paying off technical debt with PHPSpec

# spec/Auth/UserSpec.php

function let(Role $role) { $role->checkPermittedAreas(Argument::any()) ->will(function($args) { return $args; }); }

function let_go() { // Do any deconstruction here }

Page 44: Paying off technical debt with PHPSpec

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.

Page 45: Paying off technical debt with PHPSpec

We've just invented TDDD!

Technical Debt Driven Development

Page 46: Paying off technical debt with PHPSpec

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.

Page 47: Paying off technical debt with PHPSpec

Thanks for listening!

Page 48: Paying off technical debt with PHPSpec

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