The document discusses Test Driven Development (TDD) using PhpSpec. It begins with an overview of TDD vs Behavior Driven Development (BDD). It then covers key aspects of using PhpSpec including describing object behavior with examples, verifying behavior by running tests, matchers for assertions, describing collaborations and exceptions. The rest of the document demonstrates a TDD workflow using PhpSpec to develop a greeter class and related classes like Person in a step-by-step manner.
5. BDD is the art of
using examples in
conversation to
illustrate behaviour
1
Liz Keogh
6. 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
20. 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]
39. Object state
// isAdmin() should return true
$this->getUser()->shouldBeAdmin();
// hasLoggedInUser() should return true
$this->shouldHaveLoggedInUser();
41. 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...;
44. 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();
45. 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
47. 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.
48. 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”
49. 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
50. 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
52. 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
53. 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
54. 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
55. Kata - string calculator
Design an object that takes a string expression and
calculates an integer.
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)
80. # src/HelloWorld/Person.php
class Person implements Named
{
private $name;
public function __construct($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
81.
82. Another example for a Person:
When a person named
"Bob" changes their
name to "Alice", when
you ask their name
they return "Alice"
83. # 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');
}
}
84. # 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');
}
}
90. 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
91. 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
92. Final example for Greeter:
When it greets Bob,
the message "Hello
Bob" should be logged
93. # 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');
}
}
94. # 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');
}
}
95. # 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();
}
}
96.
97.
98.
99.
100. # 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;
}
}
101. # 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;
}
}
106. 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
107. 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)
108. 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)
109. Thank you!
4 @ciaranmcnulty
4 Lead Maintainer of PhpSpec
4 SeniorTrainer at:
Inviqa / Sensio Labs UK / Session Digital / iKOS
4 https://joind.in/talk/view/15424
Questions?