An Introduction to the Art of Unit Testing in PHP

December 3, 2007

Tutorials

Introduction

Testing is an essential aspect of developing in any programming language. If you don’t test your source code then how can you verify it works as expected? Manual testing can only be performed irregularly and usually only in limited ways. The answer to testing source code regularly, and in depth, is to write automated tests which can be frequently executed. In PHP such tests are usually written using a unit testing framework, a framework which allows the source code of any application or library to be tested as isolated units of functionality such as a single class or method. As unit testing has gained popularity, it has become a standard practice in PHP with libraries and frameworks such as Swiftmailer, the Zend Framework and Symfony all requiring unit test coverage of their source code.

Unit Testing is often seen as an arcane, time consuming task – which it sometimes can be! But the point of spending time writing tests is to improve the quality of your source code so it has fewer overall bugs, many of which are detected early, a continual testing process to prevent new changes from changing the behaviour of older code, and to provide confidence that your code can be depended on. There are other benefits too, and we’ll detail these later.

The Testing Fallacies

Unit Testing, and actually all other forms of testing, fall afoul of four common excuses which hinder adoption by developers.

  1. It’s time consuming and takes too long.
  2. Complex code cannot be tested.
  3. So long as it works, I don’t need to write tests.
  4. Testing is boring.

These are testing fallacies, excuses which appear quite reasonable but are actually misinformed in subtle ways. So let’s clear up a few things!

Testing does take time. The question is why should that time be considered worthwhile and the answer is that it reduces the future time you would consume in modifying code, maintainance, refactoring and fixing undetected bugs. And we both know there would be tons of undetected bugs if you’re not testing comprehensively!

Testing early is like catching the proverbial worm; as you write code, you can use Unit Tests to test isolated methods/classes or groups of functional classes immediately. By doing so you find and fix bugs quickly as they are created. A problem is that you find bugs so often, and fix them so quickly, that you barely notice the time it took. When you make changes later, and a test fails, you can fix the integration problem just as quickly – more saved time you barely notice. The benefits can be so well disguised you may miss them completely – and only see the test code it took you a few hours to write, not the bugs you solved in 10 seconds that would have taken minutes or hours six months down the line.

Secondly, there’s complex code – and there’s complex code made up of smaller practical parts. In OOP a simple objective is often “being testable”. It’s like a litmus test for quality decoupled code. If your code can’t be easily tested than it’s not that Unit Testing has failed, it’s that you failed to write practical code which was flexible and decoupled. If you were testing as you wrote code, you would have been forced to decouple classes almost on automatic. The fact code seems too complex to test is usually a symptom of having waited too long to start testing!

Thirdly, working code and working tested code are two different animals. Tests offer a safety net which makes changes, refactoring, and new features less of a pain to add since integration issues will be detected almost immediately. They also improve the efficiency of new programmers on your team who, unfamiliar with the code, see their mistakes detected immediately by the shorter feedback loop and so gain experience on the run.

In both cases, if new code changes the behaviour of older code the tests will throw a tantrum. Without tests, some behaviour could be subtly altered without anyone noticing (afterall if you’re not testing how are you ever going to detect it until some end-user figures it out in the future and complains!). Don’t pat yourself on the back because something works without tests – wait in line at your public bug tracker and wait for the predictable onslaught. If you are very lucky, the code will be simple enough that eventually the bug reports will uncover most of the problems and you can fix them before anyone gets too annoyed. That still does little for your reputation nor the future effect of even more untested changes.

Lastly, testing is not always boring. Oh, I know it can be boring at times but usually it’s boring because you’re writing tests at the end of the development process. If you’re constantly switching from tests to code, and back, you’ll get more done with less yawning. So the cure to boredom is simple task switching and timely test writing – don’t fall into the trap of having to spend days writing tests and only tests.

So no more excuses. Sit, relax, read on.

Unit Testing in PHP

Unit Testing in PHP can be performed using one of three methods. The two common options are Marcus Baker’s SimpleTest and Sebastian Bergmann’s PHPUnit. To add some confusion there’s also the old reliable: phpt. All three allow you to unit test code, and the two Framework libraries also offer numerous extensions.

We’ll take an example with PHPUnit. To install PHPUnit, follow the installation instructions available online at http://www.phpunit.de/pocket_guide/3.2/en/installation.html which form part of the PHPUnit Pocket Guide. You’ll need a working PEAR installation.

PHPUnit (quite like SimpleTest) organises tests into cases; basically a class whose public methods are singular independent tests. Here we’re about to create a class for representing a fleet of ships (a simple Model). Seems overly simple, but let’s crawl before we start peg legging it. Here’s a test case I’ve written to get this class up to some minimal level before we even consider a database input.

<?php
require_once 'PHPUnit/Framework.php';
require_once 'My/Fleet.php';
 
class MyFleetTest extends PHPUnit_Framework_TestCase
{
    protected $_fleet = null;

    public function setUp()
    {
	$this->_fleet = new My_Fleet;
    }

    public function tearDown()
    {
        unset($this->_fleet);
    }

    public function testShouldNotHaveAnyShipsYetInIntitialState()
    {
	/**
	 * $this->_fleet->count() is boring; let's implement SPL's Countable interface
	 */
	$this->assertEquals(0, count($this->_fleet));
    }

    public function testAddingAShipWillIncrementCountByOne()
    {
	$this->_fleet->addShip('USS Enterprise');
	$this->assertEquals(1, count($this->_fleet));
    }

    public function testAfterAddingAShipWeCanRetrieveItsNameByIndex()
    {
	$this->_fleet->addShip('USS Enterprise');
	$this->assertEquals('USS Enterprise', $this->_fleet->getShip( count($this->_fleet) - 1 ));
    }
}

The test case above is a good small start. We’ve defined a setUp() and tearDown() pair of methods. In both SimpleTest and PHPUnit, these are used to create Fixtures. A Fixture is some resource all tests in a test case have in common and which sets the context of the test (the situation in which we’re testing), as well as other repetitive objects or resources. Because each test must be isolated (cannot share information) these methods will tell the Framework to create our Fixture (a My_Fleet instance) before each test, and destroy it after. So each test gets a shiny new version to play with, free of anything a previous test may have done.

Each test above is a public method whose name starts with “test”. Anything not starting with “test” will not be reported on (unless annotated as a test under PHPUnit). You can use this to create Helper methods for setting up a test without fear it will be interpreted as a separate test by the framework. Helper methods are quite handy for repetitive tasks – yes, even tests may be refactored. The class name itself ends, by convention, with “Test”.

Inside each test we have references to “assert” methods. An assertion is basically taking an expected value and comparing it to an actual value. If the expected value does not match the actual value, then the test will fail. Knowing what to assert about in your tests is pretty much 90% of the battle. The other 10% is ensuring each test is totally isolated from any other test (using Fixtures and Mock Objects are the main tools for this). I only used assertEquals() above, but a full list for PHPUnit is available from here.

Now we will not present Mock Objects here but they are a hugely important component in maintaining a unit test’s isolation from all other classes and resources except the one under test. I’ll refer you to two PHP specific sources: the SimpleTest documentation for Mock Objects, Partial Mocks and Stubs, and the PHPUnit Pocket Guide
page on its new Mock Object functionality.

Running The Tests

Running PHPUnit Tests can be done from the console (even MS-DOS on Windows Vista) or a web browser. To keep this simple, we’ll rely on a quick console command executed from where we store this test class. PHPUnit also describes a method of organising tests into suites which is not covered here but is quite essential unless you intend running all tests one by one for eternity, and the PHPUnit Pocket Guide is a good place to read up on Test Suites and other elements of Unit Testing.

To run our single test class, open your console and navigate to where the test is stored. The console command is a simple:

phpunit MyFleetTest

The test name is assumed to be reflected in the file name saved, so a MyFleetTest class would be stored to the MyFleetTest.php file. Go run the test now. I’ll forewarn you that it will of course fail miserably since we haven’t gotten around to showing the class code yet! But here it comes…

Unit Testing and Test-Driven Development

You may have noticed that small problem by now. We wrote some tests, but where’s the actual class being tested? It’s safe to say we could have written the class and then tested it afterwards (almost the normal practice in PHP) but to add a broader perspective I haven’t done that.

One popular use for Unit Testing is to practice Test-Driven Development (TDD). TDD is not a necessary component of Unit Testing, rather it’s an additional practice which utilises Unit Testing since it’s an available standard for writing executable examples. The idea behind TDD is that by describing the behaviour of a class in executable code (i.e. the tests) before coding, it provides a tool for driving how the class should be designed. This has given rise to the battle-cry “Test First”. And also to a population of “Test Infected”. They should make a horror movie one day.

Consider the tests we just wrote. In writing the tests before we wrote the class, we have been forced to do a few things. First of all, we had to invent the class’s public API before anything else. It’s worth noting than in Unit Testing and TDD, it is virtually pointless to test private methods (what’s called “Testing State”) since we’re rarely interested in how the code does something as opposed to what the expected end result should be (focus on results, not intermediate state). In a lot of code the result will remain the same, but the working code will evolve over time due to refactoring, new PHP functionality, or system dependent requirements – so testing all this stuff only forces us to constantly rewrite our tests for no good reason.

Now by defining the public API so early we find ourselves designing. We’re not actually testing per se (there’s no code yet!), instead we’re specifying our expectations of how the class WILL act, and then writing the code so it DOES act as we specified.

Once we get down to actual coding, we have all this design work already done and a set of tests which will continually verify the new code we write. So while TDD is a design methodology it still ends up providing a nice set of tests. So let’s get our class written.

<?php

class My_Fleet implements Countable
{

    protected $_ships = array();

    public function addShip($shipName)
    {
	$this->_ships[] = $shipName;
    }

    public function count()
    {
	return count($this->_ships);
    }

    public function getShip($index)
    {
	return $this->_ships[ intval($index) ];
    }

}

A working class, and our tests will all pass! Maybe later we’ll change the tests to allow for Ship objects which implement __toString() to print the ship name. A small change to the tests to strval() the getShip() return value and our tests would be updated for this changed behaviour.

Some Benefits of Unit Testing

We’ve run through a lot of theory and some examples, so hopefully you’re half convinced to try out SimpleTest or PHPUnit. To push you further over the edge here’s a quick look at the benefits Unit Testing can provide to your development practices.

1. Unit Testing reduces the level of bugs in production code.

If you run your growing suite of tests frequently you will detect bugs. It’s an inevitability. In detecting bugs earlier, you can often fix them in less time since the source code is still fresh in your mind. In addition, for new bugs your tests do not find immediately you can write new tests (Regression Testing) to detect those bugs in future releases. Never let an old bug re-occur!

2. Unit Testing saves you development time.

If you detect and solve bugs closer to the time they are introduced, it’s a lot faster than having to locate, debug, and resolve a bug in the future. Add to this the time saved from making changes which break other functions, and constant forward momentum in progress and you spend less time on a project. This doesn’t necessarily mean you’ll always finish a project faster, but it definitely means you’ll spend a lot less time maintaining it over its lifetime. Maintenance is always one of those costly jobs we’d all love to limit.

3. Automated tests can be run as frequently as required.

Manual testing sucks. With automated tests you can execute them all with a quick console command or browser refresh. You can continually verify your source code is operating as expected and catch bugs almost the moment they are introduced into source code. Writing tests is a one-time cost for a continuing level of assurance.

4. Unit Testing makes it easier to change and refactor code.

We all know that changing code carries a risk. Will the new code work the same way as the old? Will I add new bugs or logical errors by accident? Will I wind up breaking backwards compatibility with previous public APIs or PHP versions? A suite of tests can check every one of those questions at any time. Since your tests are always against the public API of classes and only focus on results, not state, you can refactor, change and adapt code while relying on your tests to let you know when go awry. A continuous integration system could also be setup to test against various PHP versions and operating systems.

5. Unit Testing can improve the design of code especially with Test-Driven Development.

As we’ve already seen, writing examples of what our future code should do help us nail down how our class will be designed. Once you add in Mock Objects this can even drive the design of multiple classes and their interactions with each other. Even without TDD, writing tests as you’re coding will still improve design because testable classes must necessarily be more flexible and decoupled.

6. Unit Tests are a form of documentation.

Think about what a test does. It describes a use case or example. An example is often worth a thousand words in DocBook to a developer. Many Unit Testing frameworks also offer a summarisation document tool called “testdox” which basically lists (based on test method names) what each examples asserts. In essence providing a summary specification of the class which is more readable by non-programmers (like your DBA!).

7. Unit Testing forces you to confront the problem head on.

There is no more evasion. A mistake often encountered is that a developer tries to solve a problem by writing code without structure or planning. It’s like feeling your way through a dark room – you make mistakes, need to backtrack and detour, and it wastes a lot of time in reaching the exit! By writing tests you have no choice but to confront how a problem could be solved by focusing on the public API and writing tests that make sense. In addition it encourages simpler, more elegant solutions which are practical. Overall, your forward momentum is kept constant – backtracking becomes a rarity.

8. Unit Testing inspires confidence!

If you consider what testing, and Unit Testing, offers it shouldn’t be surprising that it adds confidence. When you can verify your source code, detect current and future bugs more easily, and change is no longer a risky proposition you spend less time fixing and more time time developing features. You are constantly looking forward. Add in that many developers will show preference for a well tested library, framework or application and this is quite a valuable benefit.

9. Unit Tests are a measure of completion.

If you’ve written your tests to cover all required features and functionality of classes, and all are passing, then it’s reasonably safe to assume you’ve finished the code. One day soon, somebody might request a new feature but until that day arrives you should stop writing code. In a sense this meshes well with Agile/XP practices where tests can not only drive design, but also capture functional requirements.

10. Unit Testing is FUN!

Okay, there are more benefits I could mention, but this is not a Unit Testing book. But having fun is definitely something worth mentioning. I’ve had a lot of fun since taking up Unit Testing and although it’s hard to point to any one reason it’s usually every other benefit contributing. When you have confidence in your code, change is cheap, problems are solved in a more structured and elegant way, refactoring is possible, and you can look upon the finished code with pride, you are going to enjoy the process. The alternative will very quickly look bleak and forbidding.

Conclusion

Here we end our journey all too quickly. This introduction to Unit Testing will, I hope, set your feet on the road to testing your next project. Testing is such an essential step in programming today that’s it’s very difficult to avoid. So if you feel suddenly encouraged and enthused, take your time and remember the learning curve. Experienced Testers are not born overnight so take advantage of your preferred help forum where you’re bound to find a few of the “Test-Infected” lying in wait to pounce on your problems and give you a welcome helping hand.

11 Responses to “An Introduction to the Art of Unit Testing in PHP”

  1. risnandar Says:

    I like how you describe non technical factor why we should use Unit Testing in our php application.
    I am sure this unit teting is needed for complex application or for enterprise php application.

    For zend framework application i am using phpunit testing that was integrated with zend studio eclipse.

    thanks

  2. xaevir Says:

    Just getting started on testing, so thanks for this article as it is a gentle and exiting intoduction for me!

  3. pritisolanki Says:

    I tried the above example and receiving the "Argument #1 of PHPUnit_Util_Fileloader:checkAndLoad() is no existing file" error.

    I have install phpunit framwork on XMAP 1.7.3.Kindly let me know what this error is? and how to resolve it?

    Thanks

  4. bitnotion Says:

    To the user above that had the checkAndLoad() error. I had the same error, and resolved it by fixing a syntax error in my code. It was strange, because not all tests were throwing the exception.

    I hope this helps.

  5. thomas777neo Says:

    Good Day

    I am using XAMPP 1.7.2 which has PEAR already installed

    I created the class and test class according to your tutorial. I then ran the following:

    C:\xampp\php\PEAR>c:\xampp\php\phpunit MyFleetTest
    PHPUnit 3.4.2 by Sebastian Bergmann.

    Argument #1 of PHPUnit_Util_Fileloader:checkAndLoad() is no existing file

    I am unable to resolve this issue. What am I doing incorrectly?

  6. japulickal Says:

    I am working on framework which will help to run the test from the function documentation. With all the mock services. Its in the initial stage of development. Feel free to try it

    http://ojesunit.blogspot.com/

    Thanks,
    Jose Antony

  7. strgt Says:

    Ok, I have an application, using a Front controller with 2 methods: setup and run. In my index.php I use Mine_Controller_Front::setup() and then Mine_controller_Front::run().

    Setup will read my config file, create a Zen_Config and store it in Zend_Registry… and so on and so on

    run will then call the Zend_Controller_Front->dispatch()

    I have then a test folder that does just the same to set eveything using Mine_controller_Front::setup() in AllTests, so I can create emulate the MVC architecture that my app is using.

    The problem now is that I cannot run the test only for one single folder/suit I have to run them all everytime.

    Do I explane myself?

    Anyone has some similar architecture?

  8. fable Says:

    Any plans to integrate PHPUnit with Zend Studio????

  9. richardfriedman Says:

    Unit testing is a must do! Test then code, I find it addictive.

    I have been using PHPUnit and have the command line set to generate code coverage, metrics and test reports. In progress of integrating into a continuous build as well.

  10. davidmintz Says:

    it’s great, but — I want more!

    Seriously, it would be nice to see a follow-up article about testing Zend Framework applications. I admit it — the main way I test is write the code and then run it in a browser and see what happens. Horrible, I know. I have heard ZF is designed to facilitate unit testing and would love to learn more.

  11. vorozhko Says:

    Hi padraic,
    very nice article.
    I think I will translate it into russian language to my blog http://pro100pro.com, of course if you permit it to me.