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