An Introduction to Testing and TDD
In this chapter I’m going to introduce some testing tools which we will be using, as well as give a brief introduction to TDD. This is an optional chapter so if you’re already familiar with these tools and TDD then feel free to skip it. However, if you are new to TDD, or have not been doing it for long, then this chapter is definitely worth reading.
Like most of the things I’ve covered so far, this chapter is just here to serve as a brief introduction. It aims to give you what you will need to work through the rest of this book. As always, I recommend you research further in to everything covered here - there are many wonderful resources available.
Types of Test
The 2 main types of test which we will be using during the process of building our application are: acceptance tests and unit tests.
Acceptance Tests
Acceptance tests are high level tests which confirm to the developer and the stakeholder, that the required features have been implemented and are working properly. Since these tests are about producing the features that the stakeholder requests, they are ideally produced with the stakeholder’s presence and input and in a form which they can understand.
A typical example might be to write something down like this:
Listing customers when there are none will return an empty list
Given there are no customers
When I ask to list customers
Then I should get an empty list
These simple rules could then be converted easily into automated tests using any assert based test framework, like so:
xUnit style acceptance test example:
1 function test_that_listing_customers_when_there_are_none_will_return_an_empty_li\
2 st()
3 {
4 // Given there are no customers
5 $customerRepository = new CustomerRepository();
6 $customerRepository->clear();
7
8 // When I ask to list customers
9 $lister = new CustomerLister($customerRepository);
10 $customers = $lister->listCustomers();
11
12 // Then I should get an empty list
13 $this->assertEquals([], $customers);
14 }
This works. The comments are there to explain the test in terms the stakeholder can understand, and the code checks the conditions are met. That said, it does mix up the stakeholder’s language with the programmer’s, and it relies on the comments being present and correct. Therefore, if you are working with the stakeholder to produce acceptance tests, then there is a better tool for the job. It’s called Cucumber, and in PHP it’s implemented by a tool called Behat.
Cucumber scripts are tests written in a language that the stakeholder can read and write in also. Tests are grouped as features, which are a collection of scenarios. These scenarios look very much like the original test rules we started with:
Behat acceptance test example
1 Feature: List customers
2 In order to view a list of customers on the system
3 As an administrator
4 I need to be able to see lists of the customers information
5
6 Scenario: Listing customers when there are none will return an empty list
7 Given there are no customers
8 When I ask to list customers
9 Then I should get an empty list
The language used in the Behat tests can include any phrases that you want. The meanings of these phases (called snippets) are then implemented in PHP in classes know as contexts. Here’s an example:
Behat context example
1 <?php
2
3 use Behat\Behat\Tester\Exception\PendingException;
4 use Behat\Behat\Context\SnippetAcceptingContext;
5 use Behat\Gherkin\Node\PyStringNode;
6 use Behat\Gherkin\Node\TableNode;
7
8 /**
9 * Behat context class.
10 */
11 class FeatureContext implements SnippetAcceptingContext
12 {
13 /**
14 * @var CustomerRepository
15 */
16 private $customerRepository;
17
18 /**
19 * @var mixed
20 */
21 private $result;
22
23 /**
24 * Initializes context.
25 *
26 * Every scenario gets its own context object.
27 * You can also pass arbitrary arguments to the context constructor through
28 * behat.yml.
29 */
30 public function __construct()
31 {
32 $this->customerRepository = new CustomerRepository();
33 }
34
35 /**
36 * @Given there are no customers
37 */
38 public function thereAreNoCustomers()
39 {
40 $this->customerRepository->clear();
41 }
42
43 /**
44 * @When I ask to list customers
45 */
46 public function iAskToListCustomers()
47 {
48 $lister = new CustomerLister($this->customerRepository);
49 $this->result = $lister->listCustomers();
50 }
51
52 /**
53 * @Then I should get an empty list
54 */
55 public function iShouldGetAnEmptyList()
56 {
57 if ([] !== $this->result) {
58 throw new Exception('Result was not an empty array.');
59 }
60 }
61 }
The different snippets can then be reused in other scenarios and features.
In this book, I will be providing the features for each new bit of functionality that we add. We’ll then use Behat to run the tests.
Unit Tests
Unit tests are for the development team’s benefit. They are not written with the stakeholder’s help; in fact they may event not know they exist. Instead, they are created by the developer as the code is written, and they test little bits of code as isolated units. A unit is a small piece of code which performs a specific task. Often, we treat each class as a unit, but there are situations where multiple classes, or a subsection of a single class, could be considered a unit. Saying that, PHPSpec (the main tool we will be using for unit testing) does enforce that each class is a unit. As a result, this will be the way that we shall mostly be working.
When doing TDD, the tests are actually written before each new bit of code is written. This process, when used properly, actually influences what code is written. Sometimes producing surprisingly elegant solutions.
There are 2 main types of unit testing tools, these are: xUnit style tools and BDD style tools.
xUnit Frameworks
xUnit test tools are call so because each language has at least one of these tools available and they are generally named SomethingUnit (e.g. JUnit for Java, CppUnit for C++). The origin of these names comes from Smalltalk’s SUnit, which was created by Kent Beck and was the first testing framework of this kind. Of course, PHP has such a framework, and as expected it is called PHPUnit. PHPUnit is probably PHP’s most well used unit testing framework and was created by Sebastian Bergmann.
xUnit frameworks are based on assertions. Each framework provides a set of available assertion functions. These are used to check that the results our code produces are what we expect. Some examples of the assertions functions available in PHPUnit are:
PHPUnit assertions example
1 $this->assertEquals(7, $subject->getValue());
2 $this->assertSame($expectedOwner, $subject->getOwner());
3 $this->assertFalse($subject->isBroken());
4 $this->assertTrue($subject->isWonderful());
5 $this->assertNull($subject->getNothing());
6 // ...
|
A full list of PHPUnit’s assertions can be found in Appendix A of the manual. |
While we will not be using PHPUnit as the main unit testing framework in this book, we will be making use of it at some point for certain tests. We will also make use if its assertion library in our Behat Contexts for simplicity.
BDD Frameworks
The second type of unit testing frameworks are BDD spec frameworks. These are used in a very similar way to xUnit frameworks, but the language used in a bit different. Instead of asserting a test’s expectations, they are described using the word should. The idea is that the tests describe the functionality of a unit, rather than just check it’s functionality. I will be using PHPSpec which is a great, but highly opinionated, BDD unit testing tool for PHP.
Using PHPSpec, the assertions we looked at for PHPUnit would written like this instead:
PHPSpec tests example
1 $this->getValue()->shouldReturn(7);
2 $this->getOwner()->shouldReturn($expectedOwner);
3 $this->shouldNotBeBroken();
4 $this->shouldBeWonderful();
5 $this->getNothing()->shouldReturn(null);
The first difference you should notice is that they simply read as better sentences than the PHPUnit assertions.
There is a second difference though: assertions allow you to assert the values of anything. Whereas, PHPSpec’s should* methods only work on return values from the unit being tested or on test double methods. This means that you are much more limited in how you can test with PHPSpec, however, this is not necessarily a bad thing: it forces you to write good code and I like this (it’s not for everyone though).
Red, Green, Refactor: The TDD Unit Testing Process
When doing TDD, there is a process that defines the order in which things should be done. This order is known as Red, Green, Refactor:
- Red
- Red means that when we run our test suite we see a failing test. This is the first step in writing code: write a failing test. The test we write should be next smallest step we can take in our implementation which will cause the test suite to fail. Once the test is written, you must run the test suite and you must see it fail.
- Green
- The green step is making the failing test pass. In order to do this you only write the smallest amount of code needed to make the failing test pass. Then we run the test suite again and see that all tests pass.
- Refactor
- The refactor step is the point at which the code can, and should, be refactored. No functionality should be altered here, you simply tidy up the code. After any refactorings have been made, you must run the test suite to make sure you’ve not broken anything.
After the Refactor step you go back to the Red step and write the next failing test. This process is repeated until the solution is complete.

The Red, Green, Refactor Cycle
I will run though a demonstration of this process shortly in the Unit Testing With PHPSpec section.
The Double Feedback Loop
Acceptance tests and unit tests support the development process in a double feedback loop. Acceptance tests come from the stakeholder in order of priority. The developers then implement the features using unit tests with the red, green, refactor cycle, until the acceptance tests pass. At this point, the feature can be delivered to the stakeholder and the next set of acceptance tests will be produced.

The Double Feedback Loop
Given, When, Then
We’ve already seen the use of these 3 words earlier in the Acceptance Tests section. However, these 3 steps apply to all types of tests. The given part sets up the preconditions, the when part performs the action being tested, and the then part checks the result. Whenever you are writing a test, it is a good idea to maintain clear grouping into these 3 stages.
We’ve seen this in Behat already. It’s highlighted and enforce by the Cucumber language. Now lets take a look an example in a PHPUnit and a PHPSpec test:
Given, When, Then example using PHPUnit
1 // Inside AgeCalcuatorTest
2 function test_calculate_the_age_of_a_customer()
3 {
4 // Given there is a customer whose date of birth is 1982-03-15
5 // And today is 2014-09-03
6 $customer = new Customer('customer name', Date::fromDate('1982-03-15'));
7 $today = Date::fromDate('2014-09-03');
8
9 // When I calcuate the age of the customer
10 $calculator = new AgeCalculator($today);
11 $age = $calculator->calculateAge($customer);
12
13 // Then the result should be 32
14 $this->assertEquals(32, $age);
15 }
Given, When, Then example using PHPSpec
1 // Inside AgeCalcuatorSpec
2 function it_can_calculate_the_age_of_a_customer()
3 {
4 // Given there is a customer whose date of birth is 1982-03-15
5 // And today is 2014-09-03
6 $customer = new Customer('customer name', Date::fromDate('1982-03-15'));
7 $this->beConstructedWith(Date::fromDate('2014-09-03'));
8
9 // When I calcuate the age of the customer
10 $age = $calculator->calculateAge($customer);
11
12 // Then the result should be 32
13 $age->shouldBe(32);
14 }
The comments are not actually necessary, and it’s also fine to compound the various expressions (particularly the when and then) but the point is not to mix up the order of these stages. I like to use vertical whitespace to separate the setting up of the preconditions, from the tests. A more condensed version of the PHPSpec example would be:
Condensed Given, When, Then example using PHPSpec
1 // Inside AgeCalcuatorSpec
2 function it_can_calculate_the_age_of_a_customer()
3 {
4 $customer = new Customer('customer name', Date::fromDate('1982-03-15'));
5 $this->beConstructedWith(Date::fromDate('2014-09-03'));
6
7 $calculator->calculateAge($customer)->shouldReturn(32);
8 }
I think that looks pretty neat and is very easy to read and understand what is supposed to happen.
Acceptance Testing with Behat
We’ve already seen Behat and what Cucumber tests look like. Now let’s have
a go at setting up a project using them. To start off create a new folder for
the project and cd into it:
1 $ mkdir behat-example
2 $ cd behat-example
Next up, let’s add Behat to the project. We’ve already seen how to set up the
composer.json file but there is a simpler way: instead, we can create the
composer.json, add Behat to it, and run composer update, all in one command
like so:
1 $ composer require behat/behat:3.* --dev
Once it has finished, Behat will be installed in the project and the behat
command will be available to us. Next, we need to tell Behat to initialise the
project - to do this simply run:
1 $ behat --init
This command creates a directory called features. This is where we’ll put the
tests. It also creates directory inside features called bootstrap, which
contains a file called FeatureContext.php. This is the default context, and
is where we can add our snippets.
Now let’s add a feature by creating a file called
features/list-books.feature:
features/list-books.feature
1 Feature: List books
2 In order to list books
3 As a reader
4 I must be able to view a list of all books stored on the system
5
6 Scenario: Display an empty list
7 Given there are no books
8 When I list all books
9 Then I should see an empty list
Next, run Behat:
1 $ behat
This should produce the following output:
1 Feature: List books
2 In order to list books
3 As a reader
4 I must be able to view a list of all books stored on the system
5
6 Scenario: Display an empty list # features/list-books.feature:6
7 Given there are no books
8 When I list all books
9 Then I should see an empty list
10
11 1 scenario (1 undefined)
12 3 steps (3 undefined)
13 0m0.03s (9.00Mb)
14
15 --- FeatureContext has missing steps. Define them with these snippets:
16
17 /**
18 * @Given there are no books
19 */
20 public function thereAreNoBooks()
21 {
22 throw new PendingException();
23 }
24
25 /**
26 * @When I list all books
27 */
28 public function iListAllBooks()
29 {
30 throw new PendingException();
31 }
32
33 /**
34 * @Then I should see an empty list
35 */
36 public function iShouldSeeAnEmptyList()
37 {
38 throw new PendingException();
39 }
This is Behat saying that it doesn’t understand the snippets which we have used in the test. It also gives some template code which can be copied and pasted into our feature context to implement these snippets. We could do that, but we can actually get Behat to do this for us by running:
1 $ behat --append-snippets
If you look in features/bootstrap/FeatureContext.php after this command has
run, you will see the template code has been added for the 3 new snippets. At
this point we can run Behat again:
1 $ behat
This time it informs us that the first snippet has no content and needs to be implemented (the others have been skipped). Let’s implement the first snippet.
Update features/bootstrap/FeatureContext.php so that it looks like this:
features/bootstrap/FeatureContext.php
1 <?php
2
3 use Behat\Behat\Tester\Exception\PendingException;
4 use Behat\Behat\Context\SnippetAcceptingContext;
5 use Behat\Gherkin\Node\PyStringNode;
6 use Behat\Gherkin\Node\TableNode;
7
8 use BehatExample\BookList;
9
10 /**
11 * Behat context class.
12 */
13 class FeatureContext implements SnippetAcceptingContext
14 {
15 /**
16 * @var BookList
17 */
18 private $bookList;
19
20 /**
21 * Initializes context.
22 *
23 * Every scenario gets its own context object.
24 * You can also pass arbitrary arguments to the context constructor through
25 * behat.yml.
26 */
27 public function __construct()
28 {
29 $this->bookList = new BookList();
30 }
31
32 /**
33 * @Given there are no books
34 */
35 public function thereAreNoBooks()
36 {
37 $this->bookList->clear();
38 }
39
40 /**
41 * @When I list all books
42 */
43 public function iListAllBooks()
44 {
45 throw new PendingException();
46 }
47
48 /**
49 * @Then I should see an empty list
50 */
51 public function iShouldSeeAnEmptyList()
52 {
53 throw new PendingException();
54 }
55 }
You can try running Behat again, but it will fail since there is no
BookList class. To create this quickly set up the autoloader by updating the
composer.json to look like this:
composer.json
1 {
2 "require-dev": {
3 "behat/behat": "3.*"
4 },
5 "autoload": {
6 "psr-0": {
7 "BehatExample\\": "src/"
8 }
9 }
10 }
Next, create the directory for the source code and update Composer:
1 $ mkdir -p src/BehatExample
2 $ composer update
Then, create the BookList class:
src/BehatExample/BookList.php
1 <?php
2
3 namespace BehatExample;
4
5 class BookList
6 {
7 public function clear()
8 {
9 }
10 }
Running Behat at this point, will show the first snippet passing and inform
that the second snippet needs to be implemented. Let’s implement the last 2
snippets at once: update the features/bootstrap/FeatureContext.php to
contain the following:
features/bootstrap/FeatureContext.php
1 <?php
2
3 use Behat\Behat\Tester\Exception\PendingException;
4 use Behat\Behat\Context\SnippetAcceptingContext;
5 use Behat\Gherkin\Node\PyStringNode;
6 use Behat\Gherkin\Node\TableNode;
7
8 use BehatExample\BookList;
9
10 /**
11 * Behat context class.
12 */
13 class FeatureContext implements SnippetAcceptingContext
14 {
15 /**
16 * @var BookList
17 */
18 private $bookList;
19
20 /**
21 * @var mixed
22 */
23 private $result;
24
25 /**
26 * Initializes context.
27 *
28 * Every scenario gets its own context object.
29 * You can also pass arbitrary arguments to the context constructor through
30 * behat.yml.
31 */
32 public function __construct()
33 {
34 $this->bookList = new BookList();
35 }
36
37 /**
38 * @Given there are no books
39 */
40 public function thereAreNoBooks()
41 {
42 $this->bookList->clear();
43 }
44
45 /**
46 * @When I list all books
47 */
48 public function iListAllBooks()
49 {
50 $this->result = $this->bookList->getBooks();
51 }
52
53 /**
54 * @Then I should see an empty list
55 */
56 public function iShouldSeeAnEmptyList()
57 {
58 if ([] !== $this->result) {
59 throw new Exception('Result was incorrect.');
60 }
61 }
62 }
Again, we’ve not implemented BookList::getBooks() so let’s do that too:
src/BehatExample/BookList.php
1 <?php
2
3 namespace BehatExample;
4
5 class BookList
6 {
7 public function clear()
8 {
9 }
10
11 /** @return array */
12 public function getBooks()
13 {
14 return [];
15 }
16 }
Run Behat and see the test pass:
1 $ behat
2 Feature: List books
3 In order to list books
4 As a reader
5 I must be able to view a list of all books stored on the system
6
7 Scenario: Display an empty list
8 Given there are no books
9 When I list all books
10 Then I should see an empty list
11
12 1 scenario (1 passed)
13 3 steps (3 passed)
14 0m0.01s (9.10Mb)
Before we move on, let’s quickly add another test to the feature by appending
the following to features/list-books.feature:
features/list-books.feature
1 Scenario: Books are listed in alphabetical order
2 Given there is a book called "Domain Driven Design" by "Eric Evans"
3 And there is a book called "Refactoring" by "Martin Fowler"
4 And there is a book called "Design Patterns" by "The Gang of Four"
5 When I list all books
6 Then I should see:
7 | title | author |
8 | Design Patterns | The Gang of Four |
9 | Domain Driven Design | Eric Evans |
10 | Refactoring | Martin Fowler |
Again, if you run Behat it will say that there are new, unknown snippets. As before, add these by running:
1 $ behat --append-snippets
Open up features/bootstrap/FeatureContext.php again, and update the 2 new
methods which have been added to the class, so that they look like this:
features/bootstrap/FeatureContext.php
1 /**
2 * @Given there is a book called :title by :author
3 */
4 public function thereIsABookCalledBy($title, $author)
5 {
6 $this->bookList->add($title, $author);
7 }
8
9 /**
10 * @Then I should see:
11 */
12 public function iShouldSee(TableNode $table)
13 {
14 if ($table->getHash() !== $this->result) {
15 throw new Exception('Result was incorrect.');
16 }
17 }
Finally, update BehatExample\BookList to contain a working implementation:
src/BehatExample/BookList.php
1 <?php
2
3 namespace BehatExample;
4
5 class BookList
6 {
7 /** @var array */
8 private $books = [];
9
10 public function clear()
11 {
12 $this->books = [];
13 }
14
15 /**
16 * @param string $title
17 * @param string $author
18 */
19 public function add($title, $author)
20 {
21 $this->books[] = [
22 'title' => $title,
23 'author' => $author
24 ];
25 }
26
27 /** @return array */
28 public function getBooks()
29 {
30 $list = $this->books;
31
32 usort($list, function ($a, $b) {
33 return $a['title'] > $b['title'];
34 });
35
36 return $list;
37 }
38 }
Then run Behat to see the tests pass:
1 $ behat
2 Feature: List books
3 In order to list books
4 As a reader
5 I must be able to view a list of all books stored on the system
6
7 Scenario: Display an empty list
8 Given there are no books
9 When I list all books
10 Then I should see an empty list
11
12 Scenario: Books are listed in alphabetical order
13 Given there is a book called "Domain Driven Design" by "Eric Evans"
14 And there is a book called "Refactoring" by "Martin Fowler"
15 And there is a book called "Design Patterns" by "The Gang of Four"
16 When I list all books
17 Then I should see:
18 | title | author |
19 | Design Patterns | The Gang of Four |
20 | Domain Driven Design | Eric Evans |
21 | Refactoring | Martin Fowler |
22
23 2 scenarios (2 passed)
24 8 steps (8 passed)
25 0m0.02s (9.15Mb)
Mink
AUTHOR NOTE: Coming soon…
The Mink Context
AUTHOR NOTE: Coming soon…
Which Language to use in Interface Tests
AUTHOR NOTE: Coming soon…
Unit Testing with PHPSpec
Now let’s take a look at unit testing. We’ll use PHPSpec for this. As we work through building the application in the next section of this book, I’ll be showing the process of acceptance testing. However, I will not be showing the process of unit testing in the examples (except in certain circumstances where there is something to learn from it). The reason for this is, that it would be a long winded, pointless and excessive process to document, and it would detract from the topic of this book. That said, all the example code will have been written using the full TDD process, and you should do the same. This also means, that all the example code for this book will contain complete unit tests which you can study for yourself.
Since we’re not going to be highlighting the unit testing process when building the application, we’ll go through an example now to get familiar with the process.
Example
For our TDD example we’ll create an algorithm which finds the greatest common divisor of 2 numbers.
Setup
Let’s start by creating a new project:
1 $ mkdir phpspec-example
2 $ cd phpspec-example
And configure Composer for the project:
composer.json
1 {
2 "require-dev": {
3 "phpspec/phpspec": "2.*@dev"
4 },
5 "autoload": {
6 "psr-0": {
7 "PhpspecExample\\": "src/"
8 }
9 }
10 }
1 $ composer install
Now we can start the TDD Cycle:
First of all, we’ll tell PHPSpec to create a new Spec file. To do this run:
1 $ phpspec desc PhpspecExample\\GreatestCommonDivisorFinder
This will create a test file called
spec/PhpspecExample/GreatestCommonDivisorFinderSpec.php. To run it use:
1 $ phpspec run --format=pretty
PHPSpec will say it can’t find the PhpspecExample\GreatestCommonDivisorFinder
class and offer to create it, say Yes and run will create it and run the
tests again. This time the test should pass.
|
Output FormattingThe There are other formats available, these are: progress (the default), html, pretty, junit and dot. |
At this point 2 files have been created for us. The test:
spec/PhpspecExample/GreatestCommonDivisorFinderSpec.php
1 <?php
2
3 namespace spec\PhpspecExample;
4
5 use PhpSpec\ObjectBehavior;
6 use Prophecy\Argument;
7
8 class GreatestCommonDivisorFinderSpec extends ObjectBehavior
9 {
10 function it_is_initializable()
11 {
12 $this->shouldHaveType('PhpspecExample\GreatestCommonDivisorFinder');
13 }
14 }
And the class we are going to implement:
src/PhpspecExample/GreatestCommonDivisorFinder.php
1 <?php
2
3 namespace PhpspecExample;
4
5 class GreatestCommonDivisorFinder
6 {
7 }
The it_is_initializable test has served it’s purpose now so you can remove
that from the test.
Red
Now we need to think what the simplest condition we can test is, which will make progress towards the final goal.
Let’s start with the condition that when both numbers are the same, then that number is the greatest common divisor.
Add the following test to the spec file:
spec/PhpspecExample/GreatestCommonDivisorFinderSpec.php
1 function it_returns_the_number_if_both_values_are_that_number()
2 {
3 $this->findGreatestDivisor(5, 5)->shouldReturn(5);
4 }
Now we have to see this fail, so run:
1 $ phpspec run --format=pretty
Again PHPSpec says that the findGreatestDivisor method does not exist and
offers to create it. Say yes, this will add the empty method to
PhpspecExample\GreatestCommonDivisorFinder and run it again. At this point
you should see some red:
1 $ phpspec run --format=pretty
2
3 PhpspecExample\GreatestCommonDivisorFinder
4
5 10 x returns the number if both values are that number
6 expected [integer:5], but got null.
7
8 ---- failed examples
9
10 PhpspecExample/GreatestCommonDivisorFinder
11 10 x returns the number if both values are that number
12 expected [integer:5], but got null.
13
14
15 1 specs
16 1 examples (1 failed)
17 11ms
This tells us that the test was expecting the value 5 to be returned, but
null was returned instead. Time to make make this test go green…
Green
Remember that in the green stage, we must only add the smallest amount of code
to make the test pass. In this case all we need to do is return the value 5.
Update the PhpspecExample\GreatestCommonDivisorFinder::findGreatestDivisor()
method and re-run the tests:
src/PhpspecExample/GreatestCommonDivisorFinder.php
1 public function findGreatestDivisor($a, $b)
2 {
3 return 5;
4 }
This time we see the green result:
1 $ phpspec run --format=pretty
2
3 PhpspecExample\GreatestCommonDivisorFinder
4
5 10 ✓ returns the number if both values are that number
6
7
8 1 specs
9 1 examples (1 passed)
10 9ms
Refactor
This is the point when we do our refactoring. However, right now there’s not much to refactor, so let’s move straight on to the next failing test.
Red
For this next test, let’s check that when the first number is a divisor of the second number, then the first number should be returned. To do this add the following test:
spec/PhpspecExample/GreatestCommonDivisorFinderSpec.php
1 function it_returns_the_first_number_if_it_is_a_divisor_of_the_second()
2 {
3 $this->findGreatestDivisor(3, 9)->shouldReturn(3);
4 }
Now run PHPSpec to see red:
1 $ phpspec run --format=pretty
2
3 PhpspecExample\GreatestCommonDivisorFinder
4
5 10 ✓ returns the number if both values are that number
6 15 x returns the first number if it is a divisor of the second
7 expected [integer:3], but got [integer:5].
8
9 ---- failed examples
10
11 PhpspecExample/GreatestCommonDivisorFinder
12 15 x returns the first number if it is a divisor of the second
13 expected [integer:3], but got [integer:5].
14
15
16 1 specs
17 2 examples (1 passed, 1 failed)
18 12ms
The first test continues to pass, but the second one does not since our
implementation currently only returns the integer 5.
Green
Again, using the smallest change possible, we can easily change this to green by just returning the first number:
src/PhpspecExample/GreatestCommonDivisorFinder.php
1 public function findGreatestDivisor($a, $b)
2 {
3 return $a;
4 }
Now when running PHPSpec we see that both tests are successfully passing:
1 $ phpspec run --format=pretty
2
3 PhpspecExample\GreatestCommonDivisorFinder
4
5 10 ✓ returns the number if both values are that number
6 15 ✓ returns the first number if it is a divisor of the second
7
8
9 1 specs
10 2 examples (2 passed)
11 11ms
Refactor
There’s still nothing to refactor yet. So on to the next failing test…
Red
Let’s now try the previous test the other way around: when the second number is a divisor of the first, the second number should be returned.
spec/PhpspecExample/GreatestCommonDivisorFinderSpec.php
1 function it_returns_the_second_number_if_it_is_a_divisor_of_the_first()
2 {
3 $this->findGreatestDivisor(9, 3)->shouldReturn(3);
4 }
Run PHPSpec again (you have to see it fail!):
1 $ phpspec run --format=pretty
2
3 PhpspecExample\GreatestCommonDivisorFinder
4
5 10 ✓ returns the number if both values are that number
6 15 ✓ returns the first number if it is a divisor of the second
7 20 x returns the second number if it is a divisor of the first
8 expected [integer:3], but got [integer:9].
9
10 ---- failed examples
11
12 PhpspecExample/GreatestCommonDivisorFinder
13 20 x returns the second number if it is a divisor of the first
14 expected [integer:3], but got [integer:9].
15
16
17 1 specs
18 3 examples (2 passed, 1 failed)
19 13ms
Green
To make this one pass we can simply return the smaller of the two numbers:
src/PhpspecExample/GreatestCommonDivisorFinder.php
1 public function findGreatestDivisor($a, $b)
2 {
3 return min($a, $b);
4 }
Yet again, we run the tests and see the successful results:
1 $ phpspec run --format=pretty
2
3 PhpspecExample\GreatestCommonDivisorFinder
4
5 10 ✓ returns the number if both values are that number
6 15 ✓ returns the first number if it is a divisor of the second
7 20 ✓ returns the second number if it is a divisor of the first
8
9
10 1 specs
11 3 examples (3 passed)
12 10ms
Refactor
Still nothing to refactor so let’s move on…
Red
How about if there’s no common divisor other than then number 1? Let’s write a test for that:
spec/PhpspecExample/GreatestCommonDivisorFinderSpec.php
1 function it_returns_1_if_there_is_no_greater_divisor()
2 {
3 $this->findGreatestDivisor(3, 5)->shouldReturn(1);
4 }
As always, run the tests and see it fail:
1 $ phpspec run --format=pretty
2
3 PhpspecExample\GreatestCommonDivisorFinder
4
5 10 ✓ returns the number if both values are that number
6 15 ✓ returns the first number if it is a divisor of the second
7 20 ✓ returns the second number if it is a divisor of the first
8 25 x returns 1 if there is no greater divisor
9 expected [integer:1], but got [integer:3].
10
11 ---- failed examples
12
13 PhpspecExample/GreatestCommonDivisorFinder
14 25 x returns 1 if there is no greater divisor
15 expected [integer:1], but got [integer:3].
16
17
18 1 specs
19 4 examples (3 passed, 1 failed)
20 13ms
Green
This one is a tiny bit more complicated. To solve it let’s say that if the lower
of the 2 numbers is not a factor of one of the numbers, then return 1:
src/PhpspecExample/GreatestCommonDivisorFinder.php
1 public function findGreatestDivisor($a, $b)
2 {
3 $divisor = min($a, $b);
4
5 if ($a % $divisor !== 0 || $b % $divisor !== 0) {
6 $divisor = 1;
7 }
8
9 return $divisor;
10 }
Run the tests:
1 $ phpspec run --format=pretty
2
3 PhpspecExample\GreatestCommonDivisorFinder
4
5 10 ✓ returns the number if both values are that number
6 15 ✓ returns the first number if it is a divisor of the second
7 20 ✓ returns the second number if it is a divisor of the first
8 25 ✓ returns 1 if there is no greater divisor
9
10
11 1 specs
12 4 examples (4 passed)
13 11ms
So far, so good!
Refactor
Finally, something to refactor! There is a duplication of the logic to check if
the $divisor variable is a factor of a variable. Let’s extract it out with the
extract method refactoring
like so:
src/PhpspecExample/GreatestCommonDivisorFinder.php
1 <?php
2
3 namespace PhpspecExample;
4
5 class GreatestCommonDivisorFinder
6 {
7 private $divisor;
8
9 public function findGreatestDivisor($a, $b)
10 {
11 $this->divisor = min($a, $b);
12
13 if (!$this->divisorIsFactorOf($a) || !$this->divisorIsFactorOf($b)) {
14 $this->divisor = 1;
15 }
16
17 return $this->divisor;
18 }
19
20 private function divisorIsFactorOf($target)
21 {
22 return $target % $this->divisor === 0;
23 }
24 }
Now run the tests to make sure they still pass:
1 $ phpspec run --format=pretty
2
3 PhpspecExample\GreatestCommonDivisorFinder
4
5 10 ✓ returns the number if both values are that number
6 15 ✓ returns the first number if it is a divisor of the second
7 20 ✓ returns the second number if it is a divisor of the first
8 25 ✓ returns 1 if there is no greater divisor
9
10
11 1 specs
12 4 examples (4 passed)
13 11ms
Red
OK. Next let’s try a divisor that is not 1, or either of the numbers
themselves:
spec/PhpspecExample/GreatestCommonDivisorFinderSpec.php
1 function it_returns_a_divisor_of_both_numbers()
2 {
3 $this->findGreatestDivisor(6, 9)->shouldReturn(3);
4 }
Watch the test fail:
1 $ phpspec run --format=pretty
2
3 PhpspecExample\GreatestCommonDivisorFinder
4
5 10 ✓ returns the number if both values are that number
6 15 ✓ returns the first number if it is a divisor of the second
7 20 ✓ returns the second number if it is a divisor of the first
8 25 ✓ returns 1 if there is no greater divisor
9 30 x returns a divisor of both numbers
10 expected [integer:3], but got [integer:1].
11
12 ---- failed examples
13
14 PhpspecExample/GreatestCommonDivisorFinder
15 30 x returns a divisor of both numbers
16 expected [integer:3], but got [integer:1].
17
18
19 1 specs
20 5 examples (4 passed, 1 failed)
21 14ms
Green
We actually only have to make a very small change here to make this pass: we
simply change the if to a while, and decrement the divisor in the loop:
spec/PhpspecExample/GreatestCommonDivisorFinderSpec.php
1 public function findGreatestDivisor($a, $b)
2 {
3 $this->divisor = min($a, $b);
4
5 // leanpub-insert-start
6 while (!$this->divisorIsFactorOf($a) || !$this->divisorIsFactorOf($b)) {
7 $this->divisor--;
8 }
9 // leanpub-insert-end
10
11 return $this->divisor;
12 }
Run the tests:
1 $ phpspec run --format=pretty
2
3 PhpspecExample\GreatestCommonDivisorFinder
4
5 10 ✓ returns the number if both values are that number
6 15 ✓ returns the first number if it is a divisor of the second
7 20 ✓ returns the second number if it is a divisor of the first
8 25 ✓ returns 1 if there is no greater divisor
9 30 ✓ returns a divisor of both numbers
10
11
12 1 specs
13 5 examples (5 passed)
14 11ms
Everything has passed! And, if you study the code you should see we’ve reached a final solution to find the greatest common divisor or 2 numbers.
If you really want to double check it, you could add this extra test, but it will pass straight away:
spec/PhpspecExample/GreatestCommonDivisorFinderSpec.php
1 function it_returns_the_greatest_divisor_of_both_numbers()
2 {
3 $this->findGreatestDivisor(12, 18)->shouldReturn(6);
4 }
Refactor
Looking at the code that we’ve produced, I’d say it’s pretty neat already, and doesn’t require any additional refactoring. Maybe we could just add some docblocks for type information.
The Result
Here is this final result. A complete solution to our original problem, achieved by TDD:
src/PhpspecExample/GreatestCommonDivisorFinder.php
1 <?php
2
3 namespace PhpspecExample;
4
5 class GreatestCommonDivisorFinder
6 {
7 /** @var int */
8 private $divisor;
9
10 /**
11 * @param int $a
12 * @param int $b
13 *
14 * @return int
15 */
16 public function findGreatestDivisor($a, $b)
17 {
18 $this->divisor = min($a, $b);
19
20 while (!$this->divisorIsFactorOf($a) || !$this->divisorIsFactorOf($b)) {
21 $this->divisor--;
22 }
23
24 return $this->divisor;
25 }
26
27 /**
28 * @param int $target
29 *
30 * @return bool
31 */
32 private function divisorIsFactorOf($target)
33 {
34 return $target % $this->divisor === 0;
35 }
36 }
Test Doubles
I’ve already mentioned test doubles, and how they can be used to help test units of code in isolation by replacing the unit’s dependencies. Now let’s take a look at how this is actually done.
Types of Test Doubles
Test doubles take on different forms depending on how the are used. Each of these different types are used to solve a different problem. Let’s take a look at the different types.
Dummy
A dummy is the simplest test double. It simply serves as a place holder for a dependency. It’s essentially an empty class which implements the dependency’s interface. Dummies are used when the unit being tested has a required dependency, but the dependency is not needed for the functionality being tested.
Dummy test double example
1 /**
2 * Method to be tested.
3 *
4 * @param number $a
5 * @param number $b
6 *
7 * @return number
8 */
9 public function addValues(Writer $writer, $a, $b)
10 {
11 $writer->write("Adding $a and $b");
12
13 return $a + $b;
14 }
15
16 /**
17 * The test.
18 *
19 * Here we only want to test the addition takes place and don't care
20 * about the writer. However, the writer is required, so we provide a dummy in
21 * its place.
22 */
23 function test_dummy()
24 {
25 $writer = new DummyWriter();
26 $examples = new Examples();
27
28 $this->assertEquals(5, $examples->addValues($writer, 2, 3));
29 }
30
31 /**
32 * The dummy writer.
33 *
34 * No functionality is requried, it just needs to implement the Writer
35 * interface.
36 */
37 final class DummyWriter implements \Writer
38 {
39 public function write($string)
40 {
41 }
42 }
Stub
A stub is the next simplest test double. It simply returns constant values from the methods in the public interface. Stubs are used to isolate the unit under test, from the complexity of its dependency, or for testing an interface or abstract class which has no implementation available.
Stub test double example
1 /**
2 * The method being tested.
3 *
4 * @return int
5 */
6 public function doubleInput(Reader $reader)
7 {
8 return $reader->readInt() * 2;
9 }
10
11 /**
12 * The test.
13 *
14 * We don't want to use a real Reader to check the logic, so we use a stub
15 * which returns a constant value, which we can reliably check.
16 */
17 function test_stub()
18 {
19 $reader = new StubReader();
20 $examples = new Examples();
21
22 $this->assertEquals(14, $examples->doubleInput($reader));
23 }
24
25 /**
26 * The stub reader.
27 *
28 * There is no logic in a stub, it simply returns constant values.
29 */
30 final class StubReader implements Reader
31 {
32 public function readInt($src = self::STDIN)
33 {
34 return 7;
35 }
36 }
Fake
A fake contains logic which emulates a simplified and reliable version of the dependency. Fakes are used when the dependency is suitably complex, that a stub is not powerful enough.
Fake test double example
1 /**
2 * The method being tested.
3 *
4 * Here Reader::readInt() is called multiple times with different parameters.
5 */
6 public function addFromInputAndFile(Reader $reader, $filename)
7 {
8 return $reader->readInt(Reader::STDIN) + $reader->readInt($filename);
9 }
10
11 /**
12 * The test.
13 *
14 * We're using a fake as we want different values to be returned depending
15 * on the parameters provided.
16 */
17 function test_fake()
18 {
19 $reader = new FakeReader();
20 $examples = new Examples();
21
22 $this->assertEquals(
23 7,
24 $examples->addFromInputAndFile($reader, 'file.txt')
25 );
26 }
27
28 /**
29 * The fake reader.
30 *
31 * Here we return a 2 if the reader is instructed to read from STDIN, otherwise
32 * it returns 5.
33 */
34 final class FakeReader implements Reader
35 {
36 public function readInt($src = self::STDIN)
37 {
38 return $src == self::STDIN ? 2 : 5;
39 }
40 }
Mock
A mock is like a fake, but unlike a fake mocks are aware that they are part of a test suite. Rather than being just a simplified implementation of the dependency’s interface, they contain testing related logic and code.
When crafting test doubles by hand the situation for creating a mock comes up very rarely. However, as we’ll see in a moment, test doubles can be created automatically using mocking frameworks. In this case all test doubles are technically mocks, regardless of the actual way they are being used. For this reason, the term mock is often used interchangeably with test double.
Spy
A spy is a type of mock which records the actions which have been performed on it. After executing the code under test, a spy can be asked if it was interacted with as expected. Spies are used to verify a dependency has be used in the correct way.
Spy test double example
1 /**
2 * Method being tested.
3 *
4 * @param number $a
5 * @param number $b
6 *
7 * @return number
8 */
9 public function addValues(Writer $writer, $a, $b)
10 {
11 $writer->write("Adding $a and $b");
12
13 return $a + $b;
14 }
15
16 /**
17 * The test.
18 *
19 * This time, we want to make sure that the writer was instructed to write a
20 * message.
21 */
22 function test_spy()
23 {
24 $writer = new SpyWriter();
25 $examples = new Examples();
26
27 $examples->addValues($writer, 2, 3);
28
29 $this->assertTrue(
30 $writer->hasWriteBeenCalledWith('Adding 2 and 3'),
31 '$writer->write("Adding 2 and 3") should have been called'
32 );
33 }
34
35 /**
36 * The spy write test double.
37 *
38 * The spy records the message that the write function was called with.
39 */
40 final class SpyWriter implements \Writer
41 {
42 private $message;
43
44 public function write($message)
45 {
46 $this->message = $message;
47 }
48
49 /**
50 * @param string $message
51 *
52 * @return bool
53 */
54 public function hasWriteBeenCalledWith($message)
55 {
56 return $this->message == $message;
57 }
58 }
Expectations
Expectations serve a similar purpose to spies, except rather than recording what methods have been called so they can be checked later, the methods required to be called are defined first. Not all mocking framework’s provide both spies and expectations, but generally they will support at least one of these features.
The one thing which using expectations does, is mix up the given, when, then order. For this reason, I prefer to use spies when possible.
Mocking Frameworks
There are many mocking frameworks available for PHP. PHPUnit has one built in, PhpSpec uses one called Prophecy, and there are others available too. The main reasons for using a different framework are, either because it provides some extra features, or because they produce tests which you think are easier to understand.
Some examples of other mocking frameworks are Mockery, Phake and Facebook’s FBMock.
Now, let’s quickly take a look at the previous test double examples, this time using PHPUnit’s and PHPSpec’s Prophecy framework’s mocking facilities.
PHPUnit
PHPUnit’s built in mocking facilities are quite verbose, and often considered to not be the easiest to read. It also doesn’t support spies, so you have to use expectations. For these reasons, alternative mocking frameworks are often chosen when working with PHPUnit.
Here’s the examples with PHPUnit:
tests/MockExampleTest.php
1 <?php
2
3 namespace tests;
4
5 use Examples;
6 use Reader;
7
8 class MockExampleTest extends \PHPUnit_Framework_TestCase
9 {
10 function test_dummy()
11 {
12 $examples = new Examples();
13
14 $writer = $this->getMock('Writer');
15
16 $this->assertEquals(5, $examples->addValues($writer, 2, 3));
17 }
18
19 function test_stub()
20 {
21 $examples = new Examples();
22
23 $reader = $this->getMock('Reader');
24
25 $reader->expects($this->any())
26 ->method('readInt')
27 ->will($this->returnValue(7));
28
29 $this->assertEquals(14, $examples->doubleInput($reader));
30 }
31
32 function test_fake()
33 {
34 $examples = new Examples();
35
36 $reader = $this->getMock('Reader');
37
38 $reader->expects($this->at(0))
39 ->method('readInt')
40 ->with($this->equalTo(Reader::STDIN))
41 ->will($this->returnValue(2));
42
43 $reader->expects($this->at(1))
44 ->method('readInt')
45 ->with($this->equalTo('file.txt'))
46 ->will($this->returnValue(5));
47
48 $this->assertEquals(
49 7,
50 $examples->addFromInputAndFile($reader, 'file.txt')
51 );
52 }
53
54 function test_expectation()
55 {
56 $examples = new Examples();
57
58 $writer = $this->getMock('Writer');
59
60 $writer->expects($this->once())
61 ->method('write')
62 ->with($this->equalTo('Adding 2 and 3'));
63
64 $examples->addValues($writer, 2, 3);
65 }
66 }
PHPSpec & Prophecy
Prophecy is very much a part of PHPSpec. By using PHPSpec, you are choosing to use Prophecy also. That said, Prophecy doesn’t have to be used with PHPSpec and can be used with other test frameworks also.
PHPSpec makes mocking exceptionally simple. You don’t need to call any special methods to create a mock, rather you just provide a typehinted parameter to the test method, and PHPSpec will create and inject the mock automatically.
Here’s the same examples with PHPSpec:
spec/ExamplesSpec.php
1 <?php
2
3 namespace spec;
4
5 use PhpSpec\ObjectBehavior;
6 use Reader;
7 use Writer;
8
9 class ExamplesSpec extends ObjectBehavior
10 {
11 function it_uses_mock_as_dummy(Writer $writer)
12 {
13 $this->addValues($writer, 2, 3)->shouldReturn(5);
14 }
15
16 function it_uses_mock_as_stub(Reader $reader)
17 {
18 $reader->readInt()->willReturn(7);
19
20 $this->doubleInput($reader)->shouldReturn(14);
21 }
22
23 function it_uses_mock_as_fake(Reader $reader)
24 {
25 $reader->readInt(Reader::STDIN)->willReturn(2);
26 $reader->readInt('file.txt')->willReturn(5);
27
28 $this->addFromInputAndFile($reader, 'file.txt')->shouldReturn(7);
29 }
30
31 function it_uses_mock_as_spy(Writer $writer)
32 {
33 $this->addValues($writer, 2, 3);
34
35 $writer->write('Adding 2 and 3')->shouldHaveBeenCalled();
36 }
37
38 function it_can_set_expectations_on_mocks(Writer $writer)
39 {
40 $writer->write('Adding 2 and 3')->shouldBeCalled();
41
42 $this->addValues($writer, 2, 3);
43 }
44 }
Katas
In order to develop and hone your programming skills, the concept of katas has become a popular way to practice them. A kata (like a martial arts kata) is a sequence of steps which are performed repeatedly in order to drill and perfect the skills and techniques which they contain.
Some popular katas are Prime Factors, The Bowling Game, String Calculator and Roman Numerals. You can easily find the details and demonstrations of these katas in many languages with a quick web search. That said Ciaran McNulty has put up excellent videos of the Prime Factors and Roman Numerals katas using PHPSpec on Vimeo. I think these are well worth a watch.