In my previous articles, PHP Unit Testing with PHPUnit and Using Mocks and Stubs in PHPUnit, I have showed how to set up PHPUnit and how to get started with unit tests and how to handle mocking and stubbing objects in order to effectively isolate your code under test. This article will explore a few ways to get the most out of your PHPUnit tests.
Author: Kendrick Curtis, Stainless Software, http://www.stainless-software.com/
Working With Static Methods
Any reasonably large Object Oriented PHP codebase will probably contain some static methods, and these are often more likely to be in need of thorough testing than many class methods, particularly if they are powerful singletons or factories.
Testing these methods is easy: create a test class as normal and call the static method as the code under test, stubbing out any code that the static method calls out to. However, trying to test other methods that rely upon these static methods is problematic: it’s not possible to mock out a static method using
$this->getMock(“classname”) as you might expect. If you’re running PHP5.3+ and PHPUnit3.5+, you can mock out statics in a very few limited circumstances. Sebastian Bergmann, author of PHPUnit, has documented this on his blog but not in the PHPUnit manual.
Instead, you will have to alter your production code to turn your static method into a non-static method, potentially creating new classes to house static functionality as required. So you will end up transforming your code into this:
$static = new Static();
But of course, the ‘new’ cannot remain in the useStatic method for stubbing, so you must really do this:
return new Static();
$static = $this->newStatic();
That’s hardly concise, but it does at least work in exactly the same way as when creating non-static objects for test
Working With Frameworks
The standard MVC pattern you will see employed in many frameworks, including Zend, Cake, Yii and more, all rely on a combination of a router and a set of controllers to operate. Testing the operation of such a router is difficult and really ought to be left to the authors of the framework you are using. If your router relies on a particularly complex piece of logic – to parse a URL in a particular way, for instance, then it’s easy enough to spin out that URL parsing code into a separate method within the router class or into a separate class that forms part of your business logic layer. Once so removed from the router, testing is straightforward enough.
You may be tempted to write code to test the operation of controllers themselves but this can lead to all sorts of headaches: depending on how your application operates, controllers may be writing directly to the output stream. You could capture this data with output buffering but in my opinion it would be better to test the elements that your controllers are relying upon first before worrying about whether your controllers are utilizing your business logic correctly. If your controllers have significant complex decision-making requirements, think about whether it ought to be put into a library which is more easily testable and separate from the controllers.
Working With Databases
Automated testing involving databases is always a headache. You have two basic choices:
- Either maintain a test database and ensure that every set of unit tests always returns the database to the same state (which could be a blank schema, which your setUp populates with relevant data) – this option is extremely time-consuming and the larger your database gets, the harder it is to guarantee that you can return to a stable state.
- Or, stub out your DB access code and check that your code under test is calling the queries that you expect. Unfortunately, if your DB connector is SQL-based, you can run into all sorts of problems parsing SQL strings that are non-identical textually, but are identical functionally (due to whitespace differences, differences in quoting style and so on).
Fortunately, most modern frameworks provide a non-SQL DB query language integrated into PHP as classes and methods, which you can stub out in the regular way. For testability, this is by far the most preferable outcome. Yet a third alternative would be to code all of your DB queries in your RDBMS as procedures and then only reference the procedures from your PHP code. In this instance testing the calls to the DB procedures would be considerably simpler than stubbing and mocking multiple PHP query objects and methods.
Effective Unit Testing For Existing Projects
It can be daunting looking at an existing project and considering adding unit tests simply due to the volume of pre-existing code. Particularly in commercial organizations, where adding unit tests can be seen as less important than bugfixes and feature development. There are unfortunately few workplaces where code quality is top priority.
In this case, unit testing is likely to be most effective when deployed against the absolutely key parts of the application, and that means understanding your codebase and the business domain that you operate in. If your site is an insurance brokerage, for instance, then the code that ensures that the price you quote to the customer is always higher than the price that you are paying for the affiliate link plus the underwriter’s fee is probably the single most important piece of code on the site. Write tests for this code first, and ensure that your tests cover 100% of the decision tree of these important modules. Most importantly, keep the tests up to date as the code evolves.
Effective Unit Testing For New Projects
When starting a project from a blank slate, it’s a whole different ball game. You have the opportunity to adopt test-driven development (TDD), in which tests are written either before or simultaneously with the code being tested.
However, quality does simply take time compared to rushing through without TDD. Your organization has to understand and buy in to the idea of TDD or at least into unit tests for certain high-profile pieces of code in order for you as a developer to have the time allocated to write the unit tests. Not all managers will agree that you need 100% line and decision coverage across your entire application, and they may well be right! Much web code today is about gluing well-understood libraries together to produce common sorts of output like tables and charts on webpages. Much of this code is not business-critical in the sense that the business will start losing money if it is not working correctly: consider which areas of your application really need unit tests to ensure correctness, and write unit tests from the start in those areas.
Versioning and Continuous Integration
Unit tests are no use if they don’t keep up with the production code they are associated with, so it’s important that they are checked in to the same code versioning system as the code itself. SVN and git are currently the most popular version control tools.
If your code and tests are in the same version control repository you might think about building a continuous integration server which will automatically run your tests against any new code checked in, and alert you if tests are starting to fail. Continuous Integration for PHP is usually delivered using Hudson or Jenkins.
About the Author
Kendrick Curtis is a web developer with ten years experience. He is co-founder of Stainless Software, a freelance web design, development, testing and content authoring company. More info on http://www.stainless-software.com/