Behaviour Driven Development with PHPSpec
Zvonimir Spajic
Behaviour Driven Development
focus on behaviour not implementation
technique derived from test-first development
It's using examples to talk through how an application behaves... And having conversations about those examples.
Dan North
a tool which can help you write clean and working PHP code using Behaviour Driven Development
at the spec level
{
"require-dev": {
"phpspec/phpspec": "^4.0"
},
"config": {
"bin-dir": "bin"
},
"autoload": {"psr-0": {"": "src"}}
}
Dog
requirement:
<?php
namespace spec\App;
use App\Dog;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class DogSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(Dog::class);
}
}
<?php
namespace App;
class Dog
{
}
Dog can make a sound
behaviour:
<?php
# spec/App
class DogSpec extends ObjectBehavior
{
// ...
function it_should_make_a_sound()
{
$this->makeSound()->shouldBe('Vuf Vuf');
}
}
<?php
# src/App
class Dog
{
public function makeSound()
{
return 'Vuf Vuf';
}
}
Matchers
describe how an object should behave
// Identity matchers
$this->getRating()->shouldBe(5);
$this->getTitle()->shouldBeEqualTo("Star Wars");
$this->getReleaseDate()->shouldReturn(233366400);
$this->getDescription()->shouldEqual("Inexplicably popular children's film");
// Comparison Matcher
$this->getRating()->shouldBeLike('5');
// Type Matcher
$this->shouldHaveType('Movie');
$this->shouldReturnAnInstanceOf('Movie');
$this->shouldBeAnInstanceOf('Movie');
$this->shouldImplement('Movie');
// Count Matcher
$this->getDirectors()->shouldHaveCount(1);
// IterableContain Matcher¶
$this->getCast()->shouldContain('Jane Smith');
// Throw Matcher
$this->shouldThrow('\InvalidArgumentException')->duringSetRating(-3);
$this->shouldThrow('\InvalidArgumentException')->during('setRating', array(-3));
$this->shouldThrow('\InvalidArgumentException')->duringInstantiation();
// ...
Dog can greet a Person
behaviour:
Stubs
describe how we interact with object we query
<?php
# spec/App
class DogSpec extends ObjectBehavior
{
// ...
function it_can_greet_a_person(Person $person)
{
$person->name()->willReturn('Mike');
$this->greet($person)->shouldBe('Hello Mike, Vuf Vuf');
}
}
<?php
# spec/App
class DogSpec extends ObjectBehavior
{
// ...
function it_can_greet_a_person(Named $named)
{
$named->name()->willReturn('Mike');
$this->greet($person)->shouldBe('Hello Mike, Vuf Vuf');
}
}
<?php
# src/App
interface Named
{
public function name();
}
<?php
# src/App
class Dog
{
public function makeSound()
{
return 'Vuf Vuf';
}
public function greet($argument1)
{
// TODO: write logic here
}
}
<?php
# src/App
class Dog
{
public function makeSound()
{
return 'Vuf Vuf';
}
public function greet(Named $named)
{
return 'Hello ' . $named->name() . 'Vuf Vuf';
}
}
Person has a name
spec:
<?php
# src/App
class PersonSpec extends ObjectBehavior
{
// ..
function it_returns_the_name_it_was_constructed_with()
{
$this->beConstructedWith('Mike');
$this->name()->shouldBe('Mike');
}
}
<?php
# src/App
class Person implements Named
{
public function __construct($argument1)
{
// TODO: write logic here
}
public function name()
{
// TODO: Implement name() method.
}
}
<?php
# src/App
class Person implements Named
{
private $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function name()
{
return $this->name;
}
}
Person can change it's name
spec:
<?php
# spec/App
class PersonSpec extends ObjectBehavior
{
// ...
function it_returns_new_name_if_renamed()
{
$this->beConstructedWith('Mike');
$this->renameTo ('Mary');
$this->name()->shouldBe('Mary');
}
}
<?php
# src/App
class Person implements Named
{
private $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function name()
{
return $this->name;
}
public function renameTo($argument1)
{
// TODO: write logic here
}
}
<?php
# src/App;
class Person implements Named
{
private $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function name()
{
return $this->name;
}
public function renameTo(string $name)
{
$this->name = $name;
}
}
<?php
# spec/App
class PersonSpec extends ObjectBehavior
{
function it_returns_the_name_it_was_constructed_with()
{
$this->beConstructedWith('Mike');
$this->name()->shouldBe('Mike');
}
function it_returns_new_name_if_renamed()
{
$this->beConstructedWith('Mike');
$this->renameTo ('Mary');
$this->name()->shouldBe('Mary');
}
}
<?php
# spec/App
class PersonSpec extends ObjectBehavior
{
function let()
{
$this->beConstructedWith('Mike');
}
function it_returns_the_name_it_was_constructed_with()
{
$this->name()->shouldBe('Mike');
}
function it_returns_new_name_if_renamed()
{
$this->renameTo ('Mary');
$this->name()->shouldBe('Mary');
}
}
When a Dog greets a Person, it should be logged
behaviour:
Mocks and Spies
describe how we interact with object we command
<?php
# spec/App
class DogSpec extends ObjectBehavior
{
function let(Logger $logger)
{
$this->beConstructedWith($logger);
}
// ...
function it_logs_the_greeting(Named $named, Logger $logger)
{
$named->name()->willReturn('Mike');
$logger->log('Hello Mike, Vuf Vuf')->shouldBeCalled();
$this->greet($named);
}
}
<?php
# src/App
class Dog
{
public function __construct($argument1)
{
// TODO: write logic here
}
public function makeSound()
{
return 'Vuf Vuf';
}
public function greet(Named $named)
{
$greeting = 'Hello ' . $named->name() . ', Vuf Vuf';
return $greeting;
}
}
<?php
# src/App
class Dog
{
private $logger;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
public function makeSound()
{
return 'Vuf Vuf';
}
public function greet(Named $named)
{
$greeting = 'Hello ' . $named->name() . ', Vuf Vuf';
$this->logger->log($greeting);
return $greeting;
}
}
Resources
Thank You!
BDD with PHPSpec
By konrad 126
BDD with PHPSpec
Presentation @ Madewithlove Retreat 2019
- 727