Testing - Deployment, Testing, and Tuning - Modern PHP (2015)

Modern PHP (2015)

Part III. Deployment, Testing, and Tuning

Chapter 10. Testing

Testing is an important part of PHP application development, but it is often neglected. I think many PHP developers don’t test because they consider testing an unnecessary burden that requires too much time for too few benefits. Other developers may not know how to test, because there are a large number of testing tools and an overwhelming learning curve.

In this chapter I hope to dispel these misunderstandings. I want you to feel comfortable and excited about testing your PHP code. I want you to consider testing an integral part of your workflow that happens at the beginning, middle, and end of the application development process.

Why Do We Test?

We write tests to ensure that our PHP applications work, and continue to work, according to our expectations. It’s as simple as that. How often have you been afraid to deploy an application into production? Before I started testing my code, I was terrified to push a release into production. Would my code work? Would it break? All I could do was cross my fingers and hope for the best. This is no way to code. It’s scary and stressful, and it usually ends in frustration. Tests, however, mitigate uncertainty, and they let us write and deploy code with confidence.

Your pointy-haired boss may argue that there isn’t enough time to write tests. After all, time is money. This is shortsighted. Installing a testing infrastructure and writing tests takes time, but this is a wise investment that pays dividends into the future. Tests help us write code that works well the first time. Tests let us continuously iterate without breaking old code. We may move forward at a slower pace than if we didn’t use tests, but we won’t waste countless development hours in the future troubleshooting and refactoring bugs that were overlooked. In the long term, tests save money, prevent downtime, and inspire confidence.

When Do We Test?

I see many PHP developers write tests as an afterthought. These developers know testing is important, but they consider tests as something they must do instead of something they want to do. These developers often push testing to the very end of the application development process. They bang out a few passing tests to satisfy their management team and call it a day. This is wrong. Tests should be a foreground concern before development, during development, and after development.

Before

Install and configure your testing tools before you develop your application. It doesn’t matter which testing tools you choose. Install them as if they are a vital application dependency. This makes it physically and mentally easier to test your application during development. This is also a good time to meet with your project manager to define higher-level application behavior.

During

Write and run tests as you build each piece of your application. Did you just add a new PHP class? Test it now, because you probably won’t test it later. Testing while you develop helps you build confident and stable code, and it also helps you quickly find and refactor new code that breaks existing functionality.

After

You probably won’t anticipate and test all of your application’s behaviors during development. If you find a bug after your launch your application, write a new test to ensure that your bug fix works correctly. Tests are not a once-and-done thing. Tests are continuously modified and improved, just like the application itself. If you update your application’s code, be sure you also update the affected tests.

What Do We Test?

We test the smallest pieces of our application. A PHP application, on a microcosmic scale, has PHP classes, methods, and functions. We should test each public class, method, and function to ensure it behaves as we expect in isolation. If we know each piece works well on its own, we can be confident it also works well when integrated into the whole application. These tests are called unit tests.

Unfortunately, testing each individual piece does not guarantee it works correctly with the whole application. This is why we also test our application at a macrocosmic scale with automated testing tools that verify our application’s higher-level behaviors. These tests are called functionaltests.

How Do We Test?

We know why, when, and what to test. More important, let’s chat about how we test code. There are several popular ways PHP developers approach testing. Some developers prefer unit tests. Some developers prefer test-driven development (TDD). And other developers prefer behavior-driven development (BDD). These are not mutually exclusive.

Unit Tests

The most popular approach to PHP application testing is unit testing. As I described previously, unit tests certify individual classes, methods, and functions in isolation from the larger application. The de facto standard PHP unit testing framework is PHPUnit, written by Sebastian Bergmann. Sebastian’s PHPUnit framework adheres to the xUnit test architecture.

There are alternative PHP unit testing frameworks, like PHPSpec, available for you to use, too. However, most popular PHP frameworks provide PHPUnit tests. It’s vital that you know how to read, write, and run PHPUnit tests if you intend to contribute to or release PHP components. I’ll show you how to install, write, and run PHP unit tests at the end of this chapter.

Test-Driven Development (TDD)

Test-driven development means you write tests before you write application code. These tests purposefully fail and describe how your application should behave. As you build application functionality, your tests will eventually run successfully. TDD helps you build with a purpose; you know ahead of time what you will build and how it should work.

This does not meant that you must write all of your application tests before you write any code. Instead, write a few tests and then build the related functionality. Write tests and build. Write tests and build. TDD is iterative. Move forward in small sprints until your application is complete.

Behavior-Driven Development (BDD)

Behavior-driven development means that you write stories that describe how your application behaves. There are two types of BDD: SpecBDD and StoryBDD.

SpecBDD is a type of unit test that uses a fluid and human-friendly language to describe your application’s implementation. SpecBDD accomplishes the same goal as alternative unit testing tools like PHPUnit. Unlike PHPUnit’s xUnit architecture, SpecBDD tests use human-readable storiesto describe behavior. For example, a PHPUnit test might be named testRenderTemplate(). An equivalent SpecBDD test might be named itRendersTheTemplate(). The same SpecBDD test might use helper methods named $this->shouldReturn(), $this->shouldBe(), and$this->shouldThrow(). SpecBDD tests use a language that is much easier to read and understand than alternative xUnit tools. The most popular SpecBDD testing tool is PHPSpec.

StoryBDD tools use the same human-friendly stories as SpecBDD tests. StoryBDD tools, however, are more concerned with higher-level behavior than with lower-level implementation. For example, a StoryBDD test confirms that your code creates and emails a PDF report. A SpecBDD test, on the other hand, confirms that a specific PDF generator class method correctly renders a PDF file for a given set of input parameters. The difference is scope. StoryBDD resembles something a project manager would write (e.g., “this should generate and email me a report”). A SpecBDD test resembles something a developer would write (e.g., “this class method should receive an array of data and write it to this PDF file”). StoryBDD and SpecBDD testing tools are not mutually exclusive. They are often used together to build a more comprehensive set of tests. You’ll often sit with your project manager to write generic StoryBDD tests that define your application’s generic behavior, and then you’ll write SpecBDD tests when you design and build your application’s implementation. The most popular StoryBDD testing tool is Behat.

TIP

Write StoryBDD tests that describe your business logic and not a specific implementation. A good StoryBDD test confirms “a shopping cart total increases when I add a product to the cart.” A bad StoryBDD test confirms “a shopping cart total increases when I send an HTTP PUT request to the /cart URL with the body product_id=1&quantity=2.” The first test is generic and describes only the high-level business logic. The second test is too specific and describes a particular implementation.

PHPUnit

Let’s talk about how to install, write, and run PHPUnit tests. It takes a bit of work to get the infrastructure in place, but it’s dead simple to write and run your PHPUnit tests afterward. Before we dig too deep into PHPUnit, let’s quickly review some vocabulary. Your PHPUnit tests are grouped into test cases, and your test cases are grouped into test suites. PHPUnit runs your test suites with a test runner.

A test case is a single PHP class that extends the PHPUnit_Framework_TestCase class. Each test case contains public methods whose names begin with test; these methods are individual tests that assert specific scenarios to be true. Each assertion can pass or fail. You want all assertions to pass.

TIP

A test case class name must end with Test, and its filename must end with Test.php. A hypothetical test case class name is FooTest, and that class lives in a file named FooTest.php.

A test suite is a collection of related test cases. If you are working on a single PHP component, oftentimes you’ll only ever have a single test suite. If you are testing a larger PHP application with many different subsystems or components, you may find it best to organize tests into multiple test suites.

A test runner is exactly what it sounds like. It is a way for PHPUnit to run your test suites and output the result. The default PHPUnit test runner is the command-line runner that is invoked with the phpunit command in your terminal application.

Directory Structure

Here’s how I prefer to organize my PHP projects. The topmost project directory has a src/ directory where I keep my source code. It also has a tests/ directory where I keep my tests. Here’s an example directory structure:

src/

tests/

bootstrap.php

composer.json

phpunit.xml

.travis.yml

src/

This directory contains my PHP project’s source code (i.e., PHP classes).

tests/

This directory contains my PHP project’s PHPUnit tests. This directory contains a bootstrap.php file that is included by PHPUnit before the unit tests are run.

composer.json

This file lists my PHP project’s dependencies managed by Composer, including the PHPUnit test framework.

phpunit.xml

This file provides configuration details for the PHPUnit test runner.

.travis.yml

This file provides configuration details for the Travis CI continuous testing web service.

NOTE

Look at your favorite PHP component or framework’s source code on GitHub and you’ll see it uses a similar organization.

Install PHPUnit

First we need to install PHPUnit and the Xdebug profiler. PHPUnit runs our tests. The Xdebug profiler generates helpful code coverage information. Composer is the easiest way to install the PHPUnit test framework. Open your terminal application, navigate to your project’s topmost directory, and run this command:

composer require --dev phpunit/phpunit

This command downloads the PHPUnit test framework into your project’s vendor/ directory, and it updates your project’s composer.json file so that the phpunit/phpunit package is listed as a project dependency. The phpunit binary is installed in your project’s vendor/bin/ directory. You can add this directory to your environment path, or you can reference vendor/bin/phpunit whenever you invoke the PHPUnit command line test runner. The PHPUnit framework classes are autoloaded into your PHP application with your project’s other Composer-managed dependencies.

Install Xdebug

The Xdebug PHP extension is a bit trickier to install. If you installed PHP with your package manager, you can install Xdebug the same way (Example 10-1).

Example 10-1. How to install Xdebug

# Ubuntu

sudo apt-get install php5-xdebug

# CentOS

sudo yum -y --enablerepo=epel,remi,remi-php56 install php-xdebug

If you installed PHP from source, you’ll need to install the Xdebug extension with the pecl command:

pecl install xdebug

Next, update your php.ini configuration file with the path to the compiled Xdebug extension.

TIP

You can find your PHP extensions directory with the php-config --extension-dir or php -i | grep extension_dir commands.

Append this line to your php.ini file using your own PHP extension path:

zend_extension="/PATH/TO/xdebug.so"

Restart PHP and you’re good to go. We’ll discuss the Xdebug profiler in Chapter 11.

Configure PHPUnit

Now let’s configure PHPUnit in our project’s phpunit.xml file.

<?xml version="1.0" encoding="UTF-8"?>

<phpunit bootstrap="tests/bootstrap.php">

<testsuites>

<testsuite name="whovian">

<directory suffix="Test.php">tests</directory>

</testsuite>

</testsuites>

<filter>

<whitelist>

<directory>src</directory>

</whitelist>

</filter>

</phpunit>

PHPUnit test runner settings are attributes on the <phpunit> XML root element. The most important setting, in my opinion, is the bootstrap setting; it specifies the path (relative to the phpunit.xml file) to a PHP file that is included before the PHPUnit test runner executes our tests. We’ll autoload our application’s Composer dependencies in the bootstrap.php file so they are available to our PHPUnit tests. The bootstrap.php file also specifies the path to our test suite (i.e., a directory that contains related test cases); PHPUnit runs all PHP files in this directory whose file names end with Test.php. Finally, this configuration file lists the directories included in our code coverage analysis with the <filter> element. In the previous example XML, the <whitelist> element tells PHPUnit to generate code coverage only for code in the src/ directory.

The gist of this configuration file is to specify our PHPUnit settings in one location. This makes our lives easier locally because we don’t have to specify these settings each time we use the phpunit command-line runner. This configuration file also lets us apply the same PHPUnit settings on remote continuous testing servers like Travis CI. After you update the phpunit.xml configuration file, update the tests/bootstrap.php file with this code:

<?php

// Enable Composer autoloader

require dirname(__DIR__) . '/vendor/autoload.php';

TIP

Make sure you install your Composer dependencies before running PHPUnit tests.

The Whovian Class

Before we write unit tests, we need something to test. Here’s a hypothetical PHP class named Whovian that has a pretty strong opinion about a particular BBC television show. Place this class definition into the src/Whovian.php file:

<?php

class Whovian

{

/**

* @var string

*/

protected $favoriteDoctor;

/**

* Constructor

* @param string $favoriteDoctor

*/

public function __construct($favoriteDoctor)

{

$this->favoriteDoctor = (string)$favoriteDoctor;

}

/**

* Say

* @return string

*/

public function say()

{

return 'The best doctor is ' . $this->favoriteDoctor;

}

/**

* Respond to

* @param string $input

* @return string

* @throws \Exception

*/

public function respondTo($input)

{

$input = strtolower($input);

$myDoctor = strtolower($this->favoriteDoctor);

if (strpos($input, $myDoctor) === false) {

throw new Exception(

sprintf(

'No way! %s is the best doctor ever!',

$this->favoriteDoctor

)

);

}

return 'I agree!';

}

}

The Whovian class constructor sets the instance’s favorite doctor. The say() method returns a string with the instance’s favorite doctor. And its respondTo() method receives a statement from another Whovian instance and responds accordingly.

The WhovianTest Test Case

The unit tests for our Whovian class live in the test/WhovianTest.php file. We call a group of related tests a test suite. In our example, all tests beneath the test/ directory belong to the same test suite. Each class file beneath the test/ directory is called a test case, and its class methods that begin with test (e.g., testThis or testThat) are individual tests. Each individual test uses assertions to verify a given condition. An assertion can pass or fail.

NOTE

Find a list of PHPUnit assertions on the PHPUnit website. Some assertions are undocumented; you can find all available assertions in the source code on GitHub.

Each PHPUnit test case is a class that extends the PHPUnit_Framework_TestCase class. Let’s declare a test case named WhovianTest in the test/WhovianTest.php file:

<?php

require dirname(__DIR__) . '/src/Whovian.php';

class WhovianTest extends PHPUnit_Framework_TestCase

{

// Individual tests go here

}

Remember, unit tests verify a public interface’s expected behavior. We’ll test the three public methods in the Whovian class. We’ll write a unit test to ensure that the __construct() method argument becomes the instance’s preferred doctor. Next, we’ll write a unit test to ensure that thesay() method’s return value mentions the instance’s preferred doctor. Finally, we’ll write two tests for the respondTo() method. One test ensures that the method’s return value is the string "I agree!" if the input matches its preferred doctor. The second test that ensures the method throws an exception if the input does not match its preferred doctor.

Test 1: __construct()

Our first test confirms that the constructor sets the Whovian instance’s favorite doctor:

public function testSetsDoctorWithConstructor()

{

$whovian = new Whovian('Peter Capaldi');

$this->assertAttributeEquals('Peter Capaldi', 'favoriteDoctor', $whovian);

}

This test instantiates a new Whovian instance with one string argument: "Peter Capaldi". We use the PHPUnit assertion method assertAttributeEquals() to assert the favoriteDoctor property on the $whovian instance equals the string "Peter Capaldi".

NOTE

The PHPUnit assertion assertAttributeEquals() receives three arguments. The first argument is the expected value; the second argument is the property name; and the final argument is the object to inspect. What’s neat is that the assertAttributeEquals() method can inspect and verify protected properties using PHP’s reflection capabilities.

Why do we inspect the favorite doctor value with the assertAttributeEquals() assertion instead of a getter method (e.g., getFavoriteDoctor())? When we write a test, we test only one specific method in isolation. Ideally, our test does not rely on other methods. In this particular example, we test the __construct() method and verify that it assigns its argument value to the object’s $favoriteDoctor property. The assertAttributeEquals() assertion lets us inspect the object’s internal state without relying on a separate, untested getter method.

Test 2: say()

Our next test confirms that the Whovian instance’s say() method returns a string value that contains its favorite doctor’s name:

public function testSaysDoctorName()

{

$whovian = new Whovian('David Tennant');

$this->assertEquals('The best doctor is David Tennant', $whovian->say());

}

We use the PHPUnit assertion assertEquals() to compare two values. The assertion’s first argument is the expected value. Its second argument is the value to inspect.

Test 3: respondTo() in agreement

Now let’s test how a Whovian instance responds in agreement with another Whovian:

public function testRespondToInAgreement()

{

$whovian = new Whovian('David Tennant');

$opinion = 'David Tennant is the best doctor, period';

$this->assertEquals('I agree!', $whovian->respondTo($opinion));

}

This test is successful because the Whovian instance’s respondTo() method receives a string argument that includes the name of its favorite doctor.

Test 4: respondTo() in disagreement

But what if a Whovian disagrees? Get out of the area as quickly as possible, because s#!t is going to hit the fan. Well, actually, it’ll just throw an exception. Let’s test that:

/**

* @expectedException Exception

*/

public function testRespondToInDisagreement()

{

$whovian = new Whovian('David Tennant');

$opinion = 'No way. Matt Smith was awesome!';

$whovian->respondTo($opinion);

}

If this test throws an exception, the test passes. Otherwise, the test fails. We can test this condition with the @expectedException annotation.

NOTE

PHPUnit provides several annotations that can control a given test. Read more about PHPUnit annotations in the PHPUnit documentation.

Run Tests

After you write each test, you should run your test suite to ensure that it passes. This is really simple to do. Open your terminal application and navigate to your project’s topmost directory (the same directory as your phpunit.xml configuration file). We’ll use the PHPUnit binary installed with Composer. Use this command to start the PHPUnit test runner:

vendor/bin/phpunit -c phpunit.xml

The -c option specifies the path to the PHPUnit configuration file. The terminal shows the results from the PHPUnit command-line test runner, and they look like Figure 10-1.

PHPUnit test runner results

Figure 10-1. PHPUnit test results

These results tell us:

1. PHPUnit read our configuration file.

2. PHPUnit took 24 ms to complete.

3. PHPUnit used 3.5 MB of memory.

4. PHPUnit successfully ran five tests and five assertions.

Code Coverage

We know our PHPUnit tests pass. However, are we sure we tested as much of our code as possible? Perhaps we forgot to test something. We can see exactly which code is tested (and untested) with PHPUnit’s code coverage report (Figure 10-2). We already specify the path(s) to our source code files in the PHPUnit configuration file. All PHP files in the whitelisted directories are included in PHPUnit’s code coverage report. We can generate code coverage each time we run the PHPUnit test runner:

vendor/bin/phpunit -c phpunit.xml --coverage-html coverage

This is the same command we used earlier, except we append the new --coverage-html option whose value is the path to a the code coverage report directory. After you run this command, open the newly generated coverage/index.html file in a web browser to see the code coverage results. Ideally, you want to see 100% coverage across the board. However, 100% coverage is not realistic and definitely should not be a requirement. How much coverage is good is subjective and varies from project to project.

PHPUnit code coverage report

Figure 10-2. PHPUnit code coverage report

TIP

Use PHPUnit’s code coverage report as a guideline to improve your code. Don’t use code coverage percentages as requirements.

Continuous Testing with Travis CI

Sometimes even the best PHP developers forget to write tests. This is why it is important to automate your tests. The best tests are like a good backup strategy—out of sight and out of mind. Tests should run automatically. My favorite continuous testing service is Travis CI because it has native hooks into GitHub repositories. I can run my application tests within Travis CI every time I push code to GitHub. Travis CI runs my tests against multiple PHP versions, too.

Setup

If you have not used Travis CI before, go to https://travis-ci.org (for public repositories) or https://travis-ci.com (for private repositories). Log in with your GitHub account. Follow the on-screen instructions to choose which repository to test with Travis CI.

Next, create the .travis.yml Travis CI configuration file in your application’s topmost directory. Don’t forget the leading . character! Save, commit, and push the Travis CI configuration file to your GitHub repository. Here’s an example Travis CI configuration:

language: php

php:

- 5.4

- 5.5

- 5.6

- hhvm

install:

- composer install --no-dev --quiet

script: phpunit -c phpunit.xml --coverage-text

The Travis CI configuration is written in YAML format and includes these settings:

language

This is the language used for our application. We set this to php. This value is case-sensitive!

php

Travis CI runs our application tests against these PHP versions. It is important that you test against all PHP versions supported by your application.

install

This is a bash command executed by Travis CI before it runs application tests. This is where you instruct Travis CI to install your project’s Composer dependencies. It is important that you use the --no-dev option to avoid installing unnecessary development dependencies.

script

This is the bash command executed by Travis CI to run application tests. By default, this is phpunit. You can override Travis CI’s default command with this setting. In this example, we tell Travis CI to use our custom PHPUnit configuration file and generate plain text coverage results.

Run

Travis CI automatically runs your application tests every time you push new commits to your GitHub repository and emails you the test results. How cool is that? There are, of course, many more Travis CI settings to further customize the Travis CI testing environment (e.g., install custom PHP extensions, use custom ini settings, and so on). Read more about Travis CI configuration for PHP at Travis CI.

Further Reading

Here are a few links to help you learn more about PHP application testing:

§ https://phpunit.de/

§ http://www.phpspec.net/docs/introduction.html

§ http://behat.org/

§ https://leanpub.com/grumpy-phpunit

§ https://leanpub.com/grumpy-testing

§ http://www.littlehart.net/atthekeyboard/

What’s Next

In this chapter we learned why, when, and how to write tests. Testing our applications builds confidence and creates more predictable code. However, tests do not let us analyze application performance. This is why we must also profile our applications. That’s what I want to talk about next.