Wednesday 25 July 2007

Testing PHP_CodeSniffer without installing

PHP_CodeSniffer has always contained extensive unit tests for all code sniffs, but the testing structure required the package to be installed so that tests could be placed into an expected location. The tests also use PHP_CodeSniffer itself, and it requires the package to be installed so that the require statements that rely on the PEAR include path would work correctly. So basically, neither PHP_CodeSniffer or the unit tests could be run without installing the package first.

PHP_CodeSniffer is not a library, it is a development tool, so this shouldn't matter. However, PEAR has recently pushed to get repository-wide code coverage stats for unit testing. All unit tests must now run correctly when the package is checked-out from CVS and without being installed.

To solve the problem, I turned to the newly proposed PEAR2 standards, which are being hotly debated at the moment on pear-dev.

To be honest, I wasn't sure about those standards when I first read them. I could see the merit for some libraries, but it was hard to see how those changes would make PHP_CodeSniffer better. I still don't think they are going to make PHP_CodeSniffer easier to use, but they have certainly made it easier to develop and test for.

The change that I made was to remove all my require_once() statements and replace them with class_exists() and interface_exists() calls with the optional second argument for autoloading. The PHP_CodeSniffer class now has an autoload method that includes the class files from different locations depending on if the package is installed or not.

The benefit of doing this is that the AllTests.php file that starts the unit testing off only needs to find the CodeSniffer.php file and its own test files. Once CodeSniffer.php is included, all other files are included automatically. Couple this change with a few other minor tweaks to remove some hard-coded assumptions about file paths and lengths, and you've got a unit test suite that runs from CVS. As an added benefit, modifying a single include inside the phpcs script allows you to run PHP_CodeSniffer itself without installing the package.

Index: scripts/phpcs
===============================================
RCS file: /repository/pear/PHP_CodeSniffer/scripts/phpcs,v
retrieving revision 1.16
diff -r1.16 phpcs
20c20
< require_once 'PHP/CodeSniffer.php';
---
> require_once '../CodeSniffer.php';

While these changes were made primarily for the PEAR unit testing system, they have also meant that I can stop installing the package to test every change I make. That is making development easier for me, and that's a good thing.