practical intro to phpspec

110
Practical intro to Dave Hulbert @dave1010

Upload: david-hulbert

Post on 28-Jan-2018

632 views

Category:

Software


0 download

TRANSCRIPT

Practical intro to

Dave Hulbert@dave1010

I like PhpSpec best because● Talk to it like a human

● Encourages better code

● Can still use PHPUnit

○ Unit testing existing code

○ Functional / UI / integration tests

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 2

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 3

Contents1. TDD in 72 seconds

2. Get up and running with PhpSpec

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 4

TDDin 72 seconds

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 5

We write code that isn't neededWriting wasteful code leads to:

● Bugs

● Lower productivity

● Difficulty in adding new features

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 6

TDD solves this● TDD stands for "Tspecification Driven

Design"

● Work out what code would be useful,

before writing it

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 7

How we do TDD

Example of how your code will work

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 8

Red

How we do TDD

Minimum code to make it work

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 9

Red Green

How we do TDD

Make code simpler and clearer,without changing behaviour

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 10

Red Green Refactor

How we do TDD

Start again

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 11

Red Green Refactor

PhpSpecsetup

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 12

Setup (1 of 2)$ mkdir demo

$ cd demo

$ mkdir src

$ composer require --dev phpspec/phpspec

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 13

Setup (2 of 2)$ vim composer.json

...

"autoload": {

"psr-0": {"": "src"}

}

...

$ composer update --lock

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 14

UsingPhpSpec

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 15

How we do TDD

Example of how your code will work

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 16

Red

Describe an object$ vendor/bin/phpspec

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 17

Describe an object$ vendor/bin/phpspec describe VendingMachine

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 18

Describe an object$ vendor/bin/phpspec describe VendingMachine

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 19

Describe an object$ vendor/bin/phpspec describe VendingMachine

Specification for VendingMachine created in

/home/dave/demo/spec/VendingMachineSpec.php.

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 20

Spec is generated automatically$ tree

├── composer.json

├── composer.lock

├── spec

│ └── VendingMachineSpec.php

├── src

└── vendor

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 21

Spec is generated automatically$ cat spec/VendingMachineSpec.php

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 22

Spec is generated automatically$ cat spec/VendingMachineSpec.php

<?php

class VendingMachineSpec extends ObjectBehavior

{

function it_is_initializable()

{

$this->shouldHaveType('VendingMachine');

}

}@dave1010 - #phpsc16 - joind.in/talk/f4771 - 23

Run the specification$ vendor/bin/phpspec run

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 24

Run the specification$ vendor/bin/phpspec run

...

class VendingMachine does not exist.

1 specs

1 example (1 broken)

Do you want me to create `VendingMachine` for you?

[Y/n]@dave1010 - #phpsc16 - joind.in/talk/f4771 - 25

Run the specification$ vendor/bin/phpspec run

...

class VendingMachine does not exist.

1 specs

1 example (1 broken)

Do you want me to create `VendingMachine` for you?

[Y/n]@dave1010 - #phpsc16 - joind.in/talk/f4771 - 26

Get PhpSpec to fix stuff for you...

Do you want me to create `VendingMachine` for you?

[Y/n]

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 27

Get PhpSpec to fix stuff for you...

Do you want me to create `VendingMachine` for you?

[Y/n]

y@dave1010 - #phpsc16 - joind.in/talk/f4771 - 28

Get PhpSpec to fix stuff for youDo you want me to create `VendingMachine` for you?

[Y/n]

y

Class VendingMachine created in

/home/dave/demo/src/VendingMachine.php.

1 specs

1 example (1 passed)

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 29

Get PhpSpec to fix stuff for youDo you want me to create `VendingMachine` for you?

[Y/n]

y

Class VendingMachine created in

/home/dave/demo/src/VendingMachine.php.

1 specs

1 example (1 passed)

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 30

PhpSpec generates code$ tree

├── composer.json

├── composer.lock

├── spec

│ └── VendingMachineSpec.php

├── src

│ └── VendingMachine.php

└── vendor

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 31

PhpSpec generates code$ cat src/VendingMachine.php

<?php

class VendingMachine

{

}

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 32

How we do TDD

Minimum code to make it work

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 33

Red Green

How we do TDD

Make code simpler and clearer,without changing behaviour

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 34

Red Green Refactor

How we do TDD

Start again

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 35

Red Green Refactor

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 36

How we do TDD

Example of how your code will work

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 37

Red

Example of how your code will workExample: it makes funds available

When I call

$this->insertCoin(20);

then

$this->availableFunds() should be 20

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 38

Example of how your code will workExample: it makes funds available

When I call

$this->insertCoin(20);

then

$this->availableFunds() should be 20

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 39

Example of how your code will workExample: it makes funds available

When I call

$this->insertCoin(20);

then

$this->availableFunds() should be 20

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 40

Example of how your code will workExample: it makes funds available

When I call

$this->insertCoin(20);

then

$this->availableFunds() should be 20

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 41

Example of how your code will workfunction it_makes_funds_available()

When I call

$this->insertCoin(20);

then

$this->availableFunds() should be 20

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 42

Example of how your code will workfunction it_makes_funds_available()

When I call

$this->insertCoin(20);

then

$this->availableFunds() should be 20

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 43

Example of how your code will workfunction it_makes_funds_available()

{

$this->insertCoin(20);

$this->availableFunds() should be 20

}

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 44

Example of how your code will workfunction it_makes_funds_available()

{

$this->insertCoin(20);

$this->availableFunds() should be 20

}

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 45

Example of how your code will workfunction it_makes_funds_available()

{

$this->insertCoin(20);

$this->availableFunds()->shouldBe(20);

}

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 46

Example of how your code will work

function it_makes_funds_available()

{

$this->insertCoin(20);

$this->availableFunds()->shouldBe(20);

}

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 47

Example of how your code will workclass VendingmachineSpec extends ObjectBehavior

{

...

function it_makes_funds_available()

{

$this->insertCoin(20);

$this->availableFunds()->shouldBe(20);

}

}@dave1010 - #phpsc16 - joind.in/talk/f4771 - 48

PhpSpec generates code$ tree

├── composer.json

├── composer.lock

├── spec

│ └── VendingmachineSpec.php

├── src

│ └── VendingMachine.php

└── vendor

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 49

Run the specification$ vendor/bin/phpspec run

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 50

Run the specification$ vendor/bin/phpspec run

Method VendingMachine::insertCoin not found.

Do you want me to create `VendingMachine::insertCoin()

` for you?

[Y/n]

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 51

Run the specification$ vendor/bin/phpspec run

Method VendingMachine::insertCoin not found.

Do you want me to create `VendingMachine::insertCoin()

` for you?

[Y/n]

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 52

Get PhpSpec to fix stuff for you

n

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 53

Get PhpSpec to fix stuff for you

y

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 54

Get PhpSpec to fix stuff for youy

Method VendingMachine::insertCoin() has been created.

Method VendingMachine::availableFunds not found.

Do you want me to create `VendingMachine::

availableFunds()` for you?

[Y/n]

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 55

Get PhpSpec to fix stuff for youy

Method VendingMachine::insertCoin() has been created.

Method VendingMachine::availableFunds not found.

Do you want me to create `VendingMachine::

availableFunds()` for you?

[Y/n]

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 56

Get PhpSpec to fix stuff for youy

Method VendingMachine::insertCoin() has been created.

Method VendingMachine::availableFunds not found.

Do you want me to create `VendingMachine::

availableFunds()` for you?

[Y/n]

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 57

Get PhpSpec to fix stuff for you

y

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 58

Get PhpSpec to fix stuff for youy

Method VendingMachine::availableFunds() has been

created.

expected [integer:20], but got null.

1 specs

2 examples (1 passed, 1 failed)

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 59

Get PhpSpec to fix stuff for youy

Method VendingMachine::availableFunds() has been

created.

expected [integer:20], but got null.

1 specs

2 examples (1 passed, 1 failed)

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 60

Get PhpSpec to fix stuff for youy

Method VendingMachine::availableFunds() has been

created.

expected [integer:20], but got null.

1 specs

2 examples (1 passed, 1 failed)

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 61

How we do TDD

Minimum code to make it work

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 62

Red Green

Automatically fake return values$ vendor/bin/phpspec run --fake

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 63

Automatically fake return values$ vendor/bin/phpspec run --fake

Do you want me to make `VendingMachine::

availableFunds()` always return 20 for you?

[Y/n]

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 64

Automatically fake return valuesy

Method VendingMachine::availableFunds() has been

modified.

1 specs

2 examples (2 passed)

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 65

PhpSpec generates code$ cat src/VendingMachine.php

<?php

class VendingMachine

{

public function insertCoin($argument1)

{

// TODO: write logic here

}

public function availableFunds()

{

return 20;

}

}

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 66

public function insertCoin($argument1)

{

// TODO: write logic here

}

public function availableFunds()

{

return 20;

}

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 67

How we do TDD

Make code simpler and clearer,without changing behaviour

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 68

Red Green Refactor

public function insertCoin($argument1)

{

// TODO: write logic here

}

public function availableFunds()

{

return 20;

}

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 69

public function insertCoins(int $coin)

{

// TODO: write logic here

}

public function availableFunds() : int

{

return 20;

}

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 70

How we do TDD

Start again

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 71

Red Green Refactor

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 72

How we do TDD

Example of how your code will work

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 73

Red

Use examples to describe behaviourfunction it_accumulates_funds()

{

$this->insertCoin(20);

$this->insertCoin(50);

$this->availableFunds()->shouldBe(70);

}

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 74

Run the specification$ vendor/bin/phpspec run

...

expected [integer:70], but got 20.

1 specs

3 examples (2 passed, 1 failed)

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 75

public function insertCoin(int $coin)

{

// TODO: write logic here

}

public function availableFunds() : int

{

return 20;

}

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 76

private $funds = 0;

public function insertCoin(int $coin)

{

$this->funds = $this->funds + $coin;

}

public function availableFunds() : int

{

return $this->funds;

}

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 77

How we do TDD

Make code simpler and clearer,without changing behaviour

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 78

Red Green Refactor

How we do TDD

Start again

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 79

Red Green Refactor

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 80

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 81

function it_uses_funds_when_buying(Catalog $catalog)

{

$catalog->howMuchIsA('kitten')->willReturn(30);

$this->insertCoin(50);

$this->buy('kitten');

$this->availableFunds()->shouldBe(20);

}

Use examples to describe behaviour

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 82

function it_uses_funds_when_buying(Catalog $catalog)

{

$catalog->howMuchIsA('kitten')->willReturn(30);

$this->insertCoin(50);

$this->buy('kitten');

$this->availableFunds()->shouldBe(20);

}

Use examples to describe behaviour

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 83

function it_uses_funds_when_buying(Catalog $catalog)

{

$catalog->howMuchIsA('kitten')->willReturn(30);

$this->insertCoin(50);

$this->buy('kitten');

$this->availableFunds()->shouldBe(20);

}

Use examples to describe behaviour

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 84

function let(Catalog $catalog)

{

$this->beConstructedWith($catalog);

}

Let $this be constructed with

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 85

$ vendor/bin/phpspec run

Would you like me to generate an interface `Catalog`

for you? [Y/n]

interface Catalog

{

}

PhpSpec generates interfaces

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 86

$ vendor/bin/phpspec run

Would you like me to generate a method signature

`Catalog::howMuchIsA()` for you? [Y/n]

interface Catalog

{

public function howMuchIsA($argument1);

}

PhpSpec generates interfaces

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 87

$ vendor/bin/phpspec run

Do you want me to create `VendingMachine::

__construct()` for you? [Y/n]

public function __construct($argument1)

{

// TODO: write logic here

}

PhpSpec generates code

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 88

private $catalog;

public function __construct(Catalog $catalog)

{

$this->catalog = $catalog;

}

Minimum code to make it work

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 89

public function buy($argument1)

{

// TODO: write logic here

}

Minimum code to make it work

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 90

public function buy(string $item)

{

$this->funds = $this->funds -

$this->catalog->howMuchIsA($item); }

Minimum code to make it work

public function buy(string $item)

{

$price = $this->catalog->howMuchIsA($item);

$this->funds -= $price;}

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 91

Make code simpler and clearer

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 92

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 93

function it_returns_coins(CoinReturn $coinReturn)

{

$this->insertCoin(5);

$this->returnCoins();

$coinReturn->returnPence(5)

->shouldHaveBeenCalled();

}

Use examples to describe behaviour

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 94

function it_returns_coins(CoinReturn $coinReturn)

{

$this->insertCoin(5);

$this->returnCoins();

$coinReturn->returnPence(5)

->shouldHaveBeenCalled();

}

Use examples to describe behaviour

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 95

function let(Catalog $catalog, CoinReturn $coinReturn)

{

$this->beConstructedWith($catalog, $coinReturn);

}

Let $this be constructed with

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 96

private $catalog;

public function __construct(

Catalog $catalog

) {

$this->catalog = $catalog;

}

Minimum code to make it work

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 97

private $catalog;

private $coinReturn;

public function __construct(

Catalog $catalog, CoinReturn $coinReturn

) {

$this->catalog = $catalog;

$this->coinReturn = $coinReturn;

}

Minimum code to make it work

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 98

public function returnCoins()

{

// TODO: write logic here

}

Minimum code to make it work

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 99

public function returnCoins()

{

$this->coinReturn->returnPence($this->funds);

}

Minimum code to make it work

Make code simpler and clearerpublic function returnCoins()

{

$this->coinReturn->returnPence($this->funds);

}

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 100

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 101

PhpSpec is a design tool● Behaviour of objects● Object collaboration

(sending and receiving messages through interfaces)

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 102

PhpSpec does lots more● More matchers: ->shouldHaveType() ● Matching exceptions: ->shouldThrow()● Matching wildcard arguments: Argument::any()

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 103

PhpSpec does even more● Custom matchers● Code templates● Extensions

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 104

4 Pro tips1.

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 105

RefactorNo refactoring (slow to a standstill)R G|R_ G_|R__ G__|R___ G___|R______ G______

Refactoring (keep up pace)R G R|R G R|R G R|R G R|R G R

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 106

4 Pro tips1. Refactor2. Use interfaces when calling methods on

other objects

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 107

Refactor

4 Pro tips1. Refactor2. Use interfaces when calling methods on

other objects3. Painful to spec means you have a bad design

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 108

Refactor

4 Pro tips1. Refactor2. Use interfaces when calling methods on

other objects3. Painful to spec means you have a bad design4. Refactor !

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 109

Refactor

Refactor

Thanks!● Questions?● joind.in/talk/f4771

Dave Hulbert, @dave1010Engineering director, @wearebaseOrganiser, @phpdorset

@dave1010 - #phpsc16 - joind.in/talk/f4771 - 110