Close

PHP Unit and XDebug

If you’re writing PHP, then you had better be testing your projects with PHPUnit. You’d also better be coupling this with XDebug to take full advantage of all the benefits pragmatic code coverage gives you. But unfortunately with XDebug comes a performance cost, and depending on the size of your codebase it may not be a small one. This is a short post about a useful tweak we made around this to our Continuous Integration (CI) setup at Box UK.

We use the Symfony 2 framework extensively with our PHP projects. We always strive for good code coverage levels, but having XDebug enabled on our Jenkins slaves was having dramatic effects on our builds times; in some cases driving it up from a few minutes to nearly half an hour! When you thrive on short feedback loops, this is just too long. You’ve probably moved onto your next task by the time the dreaded build fail notification comes along.

First attempt

Our first stab at this setup was simply to have two PHP Jenkins slaves. We’d then create one build to run our tests which would target the non-XDebug slave, and a second job to run code coverage which would target the slave with XDebug enabled. This worked fine, but felt like more moving parts than we needed… so let’s see if we can do a little better.

PHP_INI_SCAN_DIR

In many years with PHP I’d never come across this environment variable, but it allows specifying a single folder where PHP will look for .ini files. As many as you like. Brilliant!

So we figured the simplest solution would be to build a CI slave with XDebug installed, but disabled by default. We then planned to have this enabled explicitly by jobs that required it.

Ant target

We usually use an Ant build file, created originally using Sebastian Bergmann’s excellent PHP Template for Jenkins Jobs. To this we added a code coverage target to enable XDebug when needed. Here’s the snippet…

<target name="phpunit-coverage" description="Runs PHPUnit with XDebug enabled">
    <copy todir="${dir.phpd}">
        <fileset dir="${dir.phpd.original}"/>
    </copy>
    <move file="${dir.phpd}/xdebug.ini.disabled"
          tofile="${dir.phpd}/xdebug.ini"/>
    <!-- xml formatting intentional, don’t want whitespace at start of line -->
    <echo file="${dir.phpd}/coverage.ini">
memory_limit = 1536M
</echo>
    <exec executable="phpunit" failonerror="true">
        <env key="PHP_INI_SCAN_DIR" path="${dir.phpd}"/>
    </exec>
</target>

As you can see, what happens is we copy all PHP system configuration from the default location $dir.phpd.original (/etc/php.d) to our temporary build configuration directory $dir.phpd ($WORKSPACE/build/php.d). We then rename the disabled xdebug.ini.disabled so it will get picked up when PHP runs.

This then means that our standard build will run without XDebug, nice and fast. And that when it comes to code coverage time XDebug will be enabled and it’ll all Just Work. Just what we wanted!

Other configuration

This technique obviously isn’t limited to just enabling XDebug. Another problem we encountered was that while generating code coverage a LOT of memory is required. We didn’t want to up the system configured limit just to appease this.

<echo file="${dir.phpd}/coverage.ini">
memory_limit = 1536M
</echo>

So as you can see from the above example, we also write another coverage.ini file which ups the memory limit – but of course only in the cases where we’re generating code coverage. You can of course customise anything you like here, and have it only apply for individual targets!

Conclusion

This simple tweak has simplified our CI setup, and given us more per-job configurability without affecting our default server configuration. It seems all good so far, and we hope you find it as useful as we have.

(If there’s a much better way we’re missing though, then of course please let me know in the comments!)

About the author

Box UK

Box UK

Box UK's team of simply brilliant thinkers, consultants and application developers mastermind simply brilliant solutions to the world's toughest, performance-critical web and software assignments.

Comments

Gavin Davies

Jan 23rd, 2013

Good tips in there Rodders! Here’s my bro tips for test performance: * Make sure your test fixtures don’t ram the database with data unless you NEED tons of data in there * You can use test dependencies in PHPUnit to pass data between tests if you don’t need to completely tear down

Rhodri Pugh

Jan 23rd, 2013

Yup, also good tips Gav! This post kind of assumes you’ve been through all these optimisations with your build and concerns itself with XDebug. But in line with your tips, a few others that can help keep your builds speedy… * Enable Twig/Doctrine caching * http://kriswallsmith.net/post/27979797907/get-fast-an-easy-symfony2-phpunit-optimization

Add Your Comment

Related content

We're hiring. Let's talk. View available roles