PhpSpec is a SpecBDD tool that enables you to use a TDD workflow that can transform the way you write PHP. In this session we will look at the TDD workflow and see how PhpSpec can be used to speed up your development; add regression safety, and improve your object-oriented design.
7. BDD is the art of using
examples in conversation to
illustrate behaviour
1
Liz Keogh
8. Test Driven
Development
4 Before you write your code,
write a test that validates how
it should behave
4 After you have written the
code, see if it passes the test
9. Behaviour Driven
Development
4 Before you write your code,
describe how it should behave
using examples
4 Then, Implement the behaviour
you have described
23. Describing object behaviour
4 We describe an object using a Specification
4 A specification is made up of Examples illustrating
different scenarios
Usage:
phpspec describe [Class]
42. Object state
// isAdmin() should return true
$this->getUser()->shouldBeAdmin();
// hasLoggedInUser() should return true
$this->shouldHaveLoggedInUser();
44. Wildcarding
4 In most cases you should know what arguments a
method will be invoked with
4 If not, you can use wildcards
$obj->doSomething(Argument::any())->will...;
$obj->save(Argument::type(User::class))->will...;
47. Construction
// new User(‘Ciaran’)
$this->beConstructedWith('Ciaran');
// User::named(‘Ciaran’)
$this->beConstructedThrough('named', ['Ciaran']);
$this->beConstructedNamed('Ciaran');
// Testing constructor exceptions
$this->shouldThrow(InvalidArgumentException::class)
->duringInstantiation();
48. Exercise
4 Install PhpSpec using composer
4 Describe a Calculator that takes two numbers and
adds them together, by writing a few examples
(using phpspec describe)
4 Test the specificaiton and see it fail (using phpspec
run)
4 Implement the code so that the tests pass
50. Coder's design process:
4 How should it behave?
4 How can I make it do that?
4 How can I make it do that well?
51. Coder's design process:
4 How should it behave?
Write a test
4 How can I make it do that?
Write some code
4 How can I make it do that well?
Improve what you wrote
52. The Rules of TDD
by Robert C Martin
1. Don’t write any code unless it is
to make a failing test pass.
2. Don’t write any more of a test
than is sufficient to fail.
3. Don’t write any more code than
is sufficient to pass the one
failing test.
53. Test - Describe the
next behaviour
4 Think about a behaviour the
object has that it doesn’t yet
4 Describe that behaviour in the
form of a test
4 Find the simple or degenerate
cases first
4 Don’t “Go for Gold”
54. Code - Make it pass
4 Code the most obvious or
simplest working solution
4 Don’t overthink design - do
that later
4 The test is failing! Get back to
green ASAP
55. Refactor - Improve
the design
4 Is there duplication?
4 What can be taken out?
4 Is the code clear and
expressive?
4 The tests are passing so we can
stop and think
57. Pairing
4 Driver + Navigator roles
4 Driver controls the keyboard
4 Driver solves the immediate problems
4 Navigator checks the TDD rules are being enforced
4 Navigator thinks about what to test next, what
future problems might come up
58. Kata
4 Short exercises to practise TDD
4 Solve an achievable problem in a fixed time
4 Throw away the code and do it again differently
4 Focus on the process not the problem
You will probably not solve the problem on first attempt
59. Kata
4 String Calculator
4 Roman Numbers
4 Bowling
4 Tic-Tac-Toe
4 The Command Line Argument Parser
4 Prime Factors
4 Factorial
4 String Tokeniser
60. Kata - string calculator
Design an object that takes a string expression and calculates an integer.
4 No arguments evaluate to zero
4 Empty string should evaluate to zero
4 Zero as a string should evaluate to zero
4 Numeric string should evaluate to that number
4 Space separated numbers should be added together
4 Whitespace separated numbers should be added together
4 Custom separator can be specified (e.g. ’[+]1+2+3’ -> 6)
85. # src/HelloWorld/Person.php
class Person implements Named
{
private $name;
public function __construct($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
86.
87. Another example for a Person:
When a person named
"Bob" changes their name
to "Alice", when you ask
their name they return
"Alice"
88. # spec/HelloWorld/PersonSpec.php
class PersonSpec extends ObjectBehavior
{
function it_returns_the_name_it_is_created_with()
{
$this->beConstructedWith('Bob');
$this->getName()->shouldReturn('Bob');
}
function it_returns_its_new_name_when_it_has_been_renamed()
{
$this->beConstructedWith('Bob');
$this->changeNameTo('Alice');
$this->getName()->shouldReturn('Alice');
}
}
89. # spec/HelloWorld/PersonSpec.php
class PersonSpec extends ObjectBehavior
{
function let()
{
$this->beConstructedWith('Bob');
}
function it_returns_the_name_it_is_created_with()
{
$this->getName()->shouldReturn('Bob');
}
function it_returns_its_new_name_when_it_has_been_renamed()
{
$this->changeNameTo('Alice');
$this->getName()->shouldReturn('Alice');
}
}
95. Describing collaboration - Stubs
Stubs are when we describe how we interact with
objects we query
4 willReturn()
4 Doesn't care when or how many times the method is
called
96. Describing collaboration - Mocking and
Spying
Mocks or Spies are when we describe how we interact
with objects we command
4 shouldBeCalled() or shouldHaveBeenCalled()
4 Verifies that the method is called
97. Final example for Greeter:
When it greets Bob, the
message "Hello Bob" should
be logged
98. # spec/HelloWorld/GreeterSpec.php
class GreeterSpec extends ObjectBehavior
{
// ...
function it_greets_named_things_by_name(Named $named)
{
$named->getName()->willReturn('Bob');
$this->greet($named)->shouldReturn('Hello, Bob');
}
}
99. # spec/HelloWorld/GreeterSpec.php
class GreeterSpec extends ObjectBehavior
{
function let(Named $named)
{
$named->getName()->willReturn('Bob');
}
// ...
function it_greets_named_things_by_name(Named $named)
{
$this->greet($named)->shouldReturn('Hello, Bob');
}
}
100. # spec/HelloWorld/GreeterSpec.php
class GreeterSpec extends ObjectBehavior
{
function let(Named $named, Logger $logger)
{
$this->beConstructedWith($logger);
$named->getName()->willReturn('Bob');
}
// ...
function it_logs_the_greetings(Named $named, Logger $logger)
{
$this->greet($named);
$logger->log('Hello, Bob')->shouldHaveBeenCalled();
}
}
101.
102.
103.
104.
105. # src/HelloWorld/Greeter.php
class Greeter
{
public function __construct($argument1)
{
// TODO: write logic here
}
public function greet(Named $named = null)
{
$greeting = 'Hello';
if ($named) { $greeting .= ', ' . $named->getName(); }
return $greeting;
}
}
106. # src/HelloWorld/Greeter.php
class Greeter
{
private $logger;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
public function greet(Named $named = null)
{
$greeting = 'Hello';
if ($named) { $greeting .= ', ' . $named->getName(); }
$this->logger->log($greeting);
return $greeting;
}
}
111. Kata - String Calculator
4 The String Calculator has more than one
responsibility:
1. Splitting the string into components
2. Combining them together again by summing
4 Do the exercise again, but this time use more than
one object to achieve the task
112. An high level test
echo $result = (new Calculator(new Splitter(), new Parser()))->evaluate('[x]1x2x3');
4 When your application becomes composed of small
self-contained objects, you need some higher level of
testing (e.g. PHPUnit or Behat)
113. Kata - string calculator
4 Empty string should evaluate to zero
4 Zero as a string should evaluate to zero
4 Numeric string should evaluate to that number
4 Space separated numbers should be added together
4 Whitespace separated numbers should be added
together
4 Custom separator can be specified (e.g. ’[+]1+2+3’ -> 6)
114. Thank you!
4 @ciaranmcnulty
4 Lead Maintainer of PhpSpec
4 SeniorTrainer at Inviqa
4 https://joind.in/talk/6a1ed
Questions?