practical intro to phpspec
TRANSCRIPT
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
Contents1. TDD in 72 seconds
2. Get up and running with PhpSpec
@dave1010 - #phpsc16 - joind.in/talk/f4771 - 4
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
Make code simpler and clearer,without changing behaviour
@dave1010 - #phpsc16 - joind.in/talk/f4771 - 10
Red Green Refactor
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
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 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
...
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
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
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 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 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
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
@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 - 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
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
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