Tutorial: Introduction to Unit Testing in PHP with PHPUnit

It’s a familiar situation: you’ve been developing on an application for hours and you feel like you’ve been going round in circles. You fix one bug and another pops up. Sometimes, it’s the same one you found 30 minutes ago, and sometimes it’s new, but you just know it’s related. For most developers, debugging means either clicking around on the site, or putting in a whole load of debugging statements to hunt the problem down.

You’ve been there, right? You’ve had these same frustrations with all your applications, and have sat back and thought that there has to be a better way. Well, you’re in luck! There is, and it’s not as difficult as you might think it. Unit testing your application will not only save you a lot of headaches during development, but it can result in code that’s easier to maintain, allowing you to make more fearless changes (like major refactoring) without hesitation.

The key to understanding unit testing is to define what we mean by “unit.” A unit is simply a chunk of functionality that performs a specific action where you can test the outcome. A unit test, then, is a sanity check to make sure that the chunk of functionality does what it’s supposed to.

Once you’ve written up your set of tests, whenever you make a change to your code, all you have to do is run the set of tests and watch everything pass. That way, you can be reassured that you haven’t inadvertently broken another part of your application.

Debunking Unit-testing Myths

I’m sure you’re sitting there thinking, “if this unit testing stuff is so awesome, why doesn’t everyone do it for all their applications?” There are a few answers to that question, but none of them really are very good excuses. Let’s run through the common objections, and explain why they’re far from compelling reasons to avoid writing tests.

It Takes Too Long

One of the largest concerns about writing up tests is that they just take too much time to generate. Sure, some of the IDEs out there will autogenerate a set of basic tests for you; but sitting down and writing good complete tests for your code takes some time. Like many best practices in development, a little investment of time to do tasks the right way can save you a lot of time over the life of your project. Writing a solid test suite is definitely one of those cases. Moreover, you’re probably already testing your code by visiting your site and clicking around every time you add new features. Running an established test suite can be much faster than manually testing all your functionality.

There’s No Need to Test: My Code Already Works!

Another common statement you hear from developers about writing up a set of tests is that the application works, so there’s no real need to test it. They know the application, and they know right where to drop in and fix a bug, sometimes in seconds. But drop a shiny new developer into the cubicle next to them, and you might start to see why those tests would be a good idea. Without them, the newbie could go changing code without a care in the world, and break who knows what. With a repeatable test run, some of those bugs could be avoided.

It’s No Fun

The final reason why developers don’t like writing tests is that it’s just not much fun to write them. Developers, by their nature, want to solve problems. Writing code is like forming something out of nothing, creating order out of chaos to make something useful. As a result, they see writing tests as boring—a task they might get to one of these days if they have time after the real work is done. So they miss out on the value testing has even during development (especially during development!) to keep the process consistent and clean. Look at it this way: no one thinks that wasting hours chasing a pernicious bug is fun, and testing enables you to put in a little effort up front to avoid a lot of frustration down the track.

An Example

Now we get to the good part—a practical example that you can sink your teeth into. For the sake of my examples, I’m only going to use one of the more popular unit-testing tools out there, PHPUnit. This amazing application was developed by Sebastian Bergmann, and it provides an excellent set of features to help make testing your code a snap.

If you’re yet to install PHPUnit, the simplest way to grab it is from its PEAR channel.

pear channel-discover pear.phpunit.de
pear channel-discover components.ez.no
pear channel-discover pear.symfony-project.com
pear install phpunit/PHPUnit

If all goes well, you’ll have all the tools you need installed. The PEAR installer will grab any dependencies you might need for it to run.

If you want to install it manually, there are instructions in the PHPUnit manual. Note, though, that you’ll need a working PEAR installation to use PHPUnit. It relies on several other PEAR libraries to work and, if you don’t have the things it needs in place, it’ll throw some errors when you try to run the phpunit binary. The manual installation process is a bit more work, but, trust me, your fully tested consistent code will thank you for it.

Writing a First-test Case

With PHPUnit, the most basic thing you’ll write is a test case. A test case is just a term for a class with several different tests all related to the same functionality. There are a few rules you’ll need to worry about when writing your cases so that they’ll work with PHPUnit:

  • Most often, you’ll want to have your test class extend the PHPUnit_Framework_TestCase class. This gives you access to built-in functionality like the setUp() and tearDown() methods for your tests.
  • The name of the test class needs to mimic the name of the class you’re testing. For example, to test RemoteConnect, you’d use RemoteConnectTest.
  • When you create the test methods, you need to always start them with “test” (as in testDoesLikeWaffles(). The methods need to be public. You can have private methods in your tests, but they won’t be run as tests by PHPUnit.
  • The test methods will never receive any parameters. When you write your tests, you need to make them as self-contained as possible, pulling in what they need themselves. This can be very frustrating sometimes, but it leads to cleaner, more effective tests.

We need to start with some functionality to test, so here’s the class we’ll be working with in the following examples. It’s fairly basic just to keep it simple. Here’s what goes in the RemoteConnect.php library:

<?php
class RemoteConnect
{
  public function connectToServer($serverName=null)
  {
    if($serverName==null){
      throw new Exception(“That's not a server name!”);
    }
    $fp = fsockopen($serverName,80);
    return ($fp) ? true : false;
  }

  public function returnSampleObject()
  {
    return $this;
  }
}
?>

So, for example, if we were going to test this functionality to make a request to a remote server, our test might look like this:

<?php

require_once('RemoteConnect.php');

class RemoteConnectTest extends PHPUnit_Framework_TestCase
{
  public function setUp(){ }
  public function tearDown(){ }

  public function testConnectionIsValid()
  {
    // test to ensure that the object from an fsockopen is valid
    $connObj = new RemoteConnect();
    $serverName = 'www.google.com';
    $this->assertTrue($connObj->connectToServer($serverName) !== false);
  }
}
?>

You’ll notice that the class extends the base PHPUnit test case, so a lot of excellent features come along with it. Those first two methods—setUp and tearDown—are examples of this kind of built-in functionality. They are helper functions that are executed as part of the normal test run. They’re run before all the tests, and after they’re all done, respectively. Despite being handy, we won’t worry about them yet. The real focus is our testConnectionIsValid method. Our method sets up our environment by creating a new instance of our RemoteConnect class, and calls the connectToServer on it.

Now, on to the real business of our test. See that assertTrue in there? That’s one of PHPUnit’s helper functions, of which there are quite a few. assertTrue is the simplest assertion: all it does is check to see if a Boolean expression is true. Other helper functions can test for object properties, file existence, the presence of a given key in an array, or a match of a value to a regular expression, to name just a few. In this case, we want to be sure that the connectToServer result isn’t false—that would mean that our connection had failed for some reason.

Running Tests

Running your tests is as simple as calling the phpunit executable and pointing it at your tests. Here’s an example of calling our test from above:

phpunit /path/to/tests/RemoteConnectTest.php

Simple, right? The output is just as basic: for each of the tests in your test case, PHPUnit runs through them and gathers some statistics like pass, fail, and number of tests and assertions made. Here’s an example of the output from our example run:

PHPUnit 3.4 by Sebastian Bergmann
.
Time: 1 second
Tests: 1, Assertions: 1, Failures 0

For each test that’s run, you’ll either see a period (.) if it’s successful (as above), an “F” if there’s a failure, an “I” if the test is marked as incomplete, or an “S” if it’s been marked as skipped.

By default, PHPUnit is configured to run through a whole set of tests at once and report back the total statistics in one easy report.

Our example test shows a passing test—so we know that, provided with the correct parameters, our method functions as expected. But, you also need to be sure to test for when things go wrong. What happens if the host name you provide to the method doesn’t exist? Does the method throw an exception as we’d like it to? Be sure that when you’re writing tests, you have checks for the positive as well as the negatives.

In the case of the connectToServer method from our example class, providing an invalid host name for the connection will throw an exception. Handling exceptions with PHPUnit is outside the scope of this article, but I’d recommend reading the relevant section of the PHPUnit documentation if you want to dig into it.

There are several different assertions that can help you test the results of all sorts of calls in your applications. Sometimes you have to be a bit more creative to test a more complex piece of functionality, but the assertions provided by PHPUnit cover the majority of cases you’d want to test. Here’s a list of some of the more common ones you’ll find yourself using in your tests:

AssertTrue/AssertFalse Check the input to verify it equals true/false
AssertEquals Check the result against another input for a match
AssertGreaterThan Check the result to see if it’s larger than a value (there’s also LessThan, GreaterThanOrEqual, and LessThanOrEqual)
AssertContains Check that the input contains a certain value
AssertType Check that a variable is of a certain type
AssertNull Check that a variable is null
AssertFileExists Verify that a file exists
AssertRegExp Check the input against a regular expression

For example, let’s say we get back an object from a method (like the one our returnSampleObject method provides) and we want to see if it’s an instance of a particular class:

<?php

function testIsRightObject() {
  $connObj = new RemoteConnect();
  $returnedObject = $connObj->returnSampleObject();
  $this->assertType('remoteConnect', $returnedObject);
}

?>

Our method was written to return the class itself, so this test should pass and we can go on our merry way.

One Assertion per Test

As with any area of development, there are a few best practices worth adhering to when writing tests. An important one is the idea of “one test, one assertion.” This school of thought says that for each of your tests, there can be only one check or assertion. Our test examples so far have followed this principle: each test only called one assertion method. Some developers, however, think that this can be a waste of test space: “Hey, while we’re in there—let’s test for this as well.” Here’s an example:

<?php

public function testIsMyString(){
  $string = “Mostly Harmless”;
  $this->assertGreaterThan(0,strlen($string));
  $this->assertContains(“42”,$string);
}

?>

Our fun little testIsMyString example is testing for two different things. First, it checks that the string is nonempty (length greater than 0), and then it checks that the string contains the number “42.” Right away, you can see how this might become tricky: the test would fail if the string was, for example, “fortytwo.” But you’d see exactly the same failure if the string was empty, which might be caused by an entirely different bug. The “fail” result can be deceptive and might cause some confusion as to what it’s really reporting.

Framework Support for Unit Tests

Several of the popular PHP-based frameworks out there (such as the Zend Framework and Symfony) have included the ability to write tests against their functionality. Because MVC frameworks involve quite a bit more than what you’d find in a simple PHP script or library, they’ve provided hooks into the framework to assist in writing tests.

It might make more sense if you see an example. Let’s look at a test using the Zend Framework, verifying the routing of a URL to a controller:

<?php

class CommentControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
  public function setUp()
  {
    parent::setUp();
  }

  public function tearDown()
  {
    parent::tearDown();
  }

  public function appBootstrap()
  {
    $this->frontController->registerPlugin(new Initializer('test'));
  }

  public function testGoHome()
  {
    $this->dispatch('/home');
    $this->assertController('home');
  }

}

?>

Of course, this example is a little silly, since we’re testing built-in framework functionality rather than our own code, but you get the idea. Your test extends a different test case class, Zend_Test_PHPUnit_ControllerTestCase, so that it knows how to test a Zend Framework controller. You’ll notice, though, that we’re still essentially using PHPUnit here. Most of the test will feel familiar, but you have access to a few special assertions, like assertController used above. You can find the complete documentation about the Zend_Test component in the Zend Framework manual.

Test-driven Development

I’d be remiss if I talked about testing without mentioning a growing technique used by many developers: test driven development. Test-driven development (TDD) is a technique used during development. The basic idea behind TDD is that you write the tests first, before a single line of application code is even written. But wait, how do you know what to put in the tests without the working code to look at? Well, that’s the point. In TDD you write the test to check for the anticipated functionality, and then write the code to match it. When you start out and have your first set of tests, they’ll all (obviously) fail. As you write your code, you work until the full test case is “green” and everything passes. This method lets you focus more on the requirements first, rather than getting lost in the minutia of your code.

This can be a difficult method for a newbie to unit testing to pick up. If that describes you, I’d recommend writing against current code first, so that you can gain a feel for tests and using PHPUnit. Then, if you want to jump to the other side, you can start out your next project with TDD. Be aware that it will be slow the first time. Thankfully, you can carry over the knowledge you gathered in testing, and write better-informed tests that cover the right functionality.

Summary

I hope I’ve given you a good introduction to the world of unit testing. Even though there are a multitude of topics I’ve not touched on, I’ve tried to give you a good jumping off point where you can go and start writing your own tests right now (you are going to write some tests, right?). Even if it’s just a few tests here and there, you can ease your development fears just a bit. Soon enough, you’ll reach a point where you won’t be able to imagine refactoring your code without them.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Tom

    You’re example looks more like a integration test. You validate your internet connection and the webserver (www.google.com), too.
    Tests methods can have parameters. The key to that are annotations.

    /**
    * @covers myclass::method
    * @dataProvider provideParameters
    */
    public function testMethod($parameter) {
    $object = new myclass();
    $this->assertTrue($object->method($parameter));
    }
    public static function provideParameters() {
    return array(
    array(1),
    array(-10),
    array(42),
    )
    }

    I don’t agree that you should start with tests for existing code. Often it is really difficult and complex to write unit tests for existing code. To learn TDD it is easier to write some little component/library without dependencies.

    • Seth Thornberry

      +1 to the @dataProvider notation. it works even better if you put descriptive keys on your parameters array, because the test runs will show your descriptions, ie:
      (the static requirement is only required in phpunit version pre 3.3, if I remember correctly)

      public static function provideParamters()
      {
      return array(
      ‘Singular’ => array(1),
      ‘Negative’ => array(-10),
      ‘The Answer?’ => array(42)
      );
      }

      I also agree that if you are just starting with unit testing, writing test for existing code will probably bore you to tears. If you do TDD with new development, you’ll just WISH your existing code had unit tests, because you’ll be so much more confident in making changes to the code that does have unit tests.

  • Chris Cornutt

    @tom I hadn’t seen the data provider stuff before – that’s pretty slick stuff. I know of a few spots I could drop that in. Learn something new every day :) If SitePoint’s nice enough and let me do a few more of these, I’ll be sure that makes it in.

    @tom and @seth – While I agree that TDD is a very nice method for development, it also requires a large shift in thinking (and how people work!) to accomplish. My suggestion to work on existing code wasn’t so much to drive yourself nuts going for the 100% code coverage but so that you’d have code already there to work with. Plus it has the added benefit of forcing you to learn how to make your code more testable – something someone new to testing needs to wade through on their own (at least when it comes to their own code).

  • http://www.cemerson.co.uk Stormrider

    “Our example test shows a passing test”

    Er, does it? The ‘Failures 1′ seems to contradict this!

  • Chris Cornutt

    @Stormrider hah, good catch…that was actually from a different run. You’re correct – that should be all passing tests. Sorry for the confusion!

  • http://www.dmgx.com Michael Morris

    I haven’t had good experience with PEAR under Windows. While I’m fairly certain PHP Unit is more powerful than my homebrew I did homebrew a unit testing structure that is working very well using closures to hold the tests and namespaces to separate the test classes from the test targets. The tests inherit in parallel to the tested classes, so the test of any class can involve testing functionality of parents to make sure its intact.

    The weakness of my approach is an inability to test the protected methods, but I don’t see it as major since that’s what the assert() statement exists to handle.

    Eventually I’ll post my homebrew. Since it is public property/method oriented it can complement PHPUnit in the long run rather than try to compete with it.

  • Chris

    You left one big important thing out why developers don;t unit test.. and that is that it is extremely hard to test code that has dependencies and external data sources…

    And the more complex the test will be the chance that there will be mistakes in the tests will grow too :)

    • http://www.reich-consulting.net/ coffee_ninja

      Agreed! I’ve got tons of code for my employer’s intranet that only succeeds if the authenticated user is a member of certain groups in Active Directory. So in order to test my code I either have to a) put the username and password of real users in my configuration (not cool) or b) clutter my AD domain with test accounts and group memberships, then remember to delete them all when I’m done running tests.

      I know with database connections PHPUnit has some nice features to create test data before tests and delete it after, but no such thing exists for LDAP directories as far as I know.

    • http://www.facebook.com/stm555 Seth Thornberry

      External data sources are exactly what mocks are built for.

      testFunctionRunswithProperPermissions()
      {
      [..] (mock set up stuff)
      $classUnderTest->expects($this->once())
      ->method(‘getPermissions’)
      ->will($this->returnValue(array(‘perm1′,’perm2′,’perm3′));
      $classUnderTest->functionToTest();
      [..] etc
      }

      If you can’t encapsulate your external data sources for easy mocking, then a refactor is probably in order.

  • ManDan

    are serious php developers are also a good graphic designer also?….not seen yet in my time.

    • http://www.dmgx.com Michael Morris

      What does this have to do with the topic?

      • http://www.facebook.com/people/Jatin-Dhoot/1012312666 Jatin Dhoot

        Diverting the flow of topic

  • Josh Hayden

    Hi Chris,

    Thanks for the article. I think your description of test-driven development (TDD) needs clarified a bit (http://en.wikipedia.org/wiki/Test-driven_development).

    “The basic idea behind TDD is that you write the tests first, before a single line of application code is even written … When you start out and have your first set of tests, they’ll all (obviously) fail.”
    This is not quite correct. The idea is that you write one test (not tests), run it, and it should fail. Then you write code, execute the test again, and it should pass. Then you write the next test, execute all tests including the new one, and only the latest test should fail. Write the code and run all tests again, and they should all pass.

    Writing one test at a time between code changes allows the design to better evolve. Other guidelines include adding the least amount of test code to make it fail and then adding the least amount of production code to make it pass. Then refactor when all tests are passing.

  • Deepesh Khanal

    I am using SimpleTest for Unit Testing in PHP. Is it good enough?