David Ogilo

menu search

TDD in WordPress using PHPUnit

In this article, I’ll show you how to create and run unit tests on your plugins using PHPUnit as the framework. I’ll also be highlighting the importance of Test Driven Development.

WHY TDD?

There are some developers out there who are not fans of writing unit tests to test their code for bugs etc, but I’m not one of them. On the contrary, I always write tests for my code. There are so many instances where these tests have picked up bugs. Not only that, but writing tests has also made my code more efficient and in turn, made me a better developer.

MAKING PHPUnit PLAY NICE WITH WORDPRESS

My testing framework of choice is the popular PHPUnit. The install process of the framework is really easy and straight forward. Once that’s installed, we’ll need to create a build script and bootstrap which will both be used to load the WordPress instance and create dummy tables and settings. Before I go ahead, I just wanted to say it’s a good idea to read up on how to write and organise tests. I write my plugins in an Object-Oriented way so I have my classes in a folder and tests in a different folder mimicking the structure of my class folders.

Below is an example of my bootstrap.php which I use to load the WordPress instance, this file will be loaded automatically by PHPUnit.

/**
 * The bootstrap used by PHPUnit and Build
 */
 
( PHP_SAPI === 'cli' ) || die( 'Access Denied' );
 
define( 'PHPUNIT_DB_PREFIX', 'phpunit_' );
defined( 'PHPUNIT_BUILD_SETUP' ) || define( 'PHPUNIT_BUILD_SETUP', false );
 
global $wp_rewrite, $wpdb;
 
//Required for code coverage to run.
//define( 'WP_MAX_MEMORY_LIMIT', '1024M' );
define( 'WP_MEMORY_LIMIT', '100M' );
 
require_once( dirname( __FILE__ ) . '/../../wp-load.php' );
require_once( ABSPATH . 'wp-admin/includes/admin.php' );
 
if ( !PHPUNIT_BUILD_SETUP ) wp_set_current_user( 1 );

Depending on the amount of memory your plugin uses, you might need to increase the memory limits.

Because we’re testing our code we don’t want run tests on live data, instead, we should use the dummy data that we set during the test process. This dummy data will need to be stored in the database, so we’ll need to create dummy tables mimicking the WordPress tables. To do this we need to create these tables just before we run our tests. We could do this in the bootstrap but I decided to create a build script which will do this all for us automatically.

The build script will also enable custom plugins and can be extended to do other tasks. The build script is hosted on Github, along with all the code required to make PHPUnit play nice with WordPress. The $table_prefix option needs to be replaced in wp-config.php with the following code:

$table_prefix = ( defined( 'PHPUNIT_DB_PREFIX' ) )? PHPUNIT_DB_PREFIX : 'wp_';

This will make sure that both the test process and live site use the correct database table prefix.

It is very easy to pass a list of plugins to the build script, as shown below (when running it manually):

php build.php --plugins=dummy,dummy/child-plugin.php

The above command enables two plugins, dummy and child-plugin (which is located as a child of the dummy plugin). To make sure your custom table is replicated, it has to have the same table prefix as the default WordPress tables.

The next step is to configure PHPUnit by creating a phpunit.xml configuration file. This is where we tell the framework which test suite we need to run and how to output the test results.  Here is an example of my configuration file.

<phpunit backupGlobals="false"
		 backupStaticAttributes="false"
		 bootstrap="bootstrap.php">
 
	 <testsuites>
	 	<testsuite name="Dummy Test Suite">
	 		<directory>dummy/tests</directory>
	 	</testsuite>
	 </testsuites>
 
	<filter>
  		<whitelist>
  			<directory suffix=".php">classes</directory>
  		</whitelist>
 	 </filter>
 
	 <logging>
		<log type="coverage-html" target="logs/coverage" charset="UTF-8" yui="true" highlight="true" lowUpperBound="35" highLowerBound="70"/>
		<log type="junit" target="logs/logfile.xml" logIncompleteSkipped="false"/>
	</logging>
</phpunit>

The best approach is to have a test suite for each custom plugin installed, this way you can easily manage each test suite (either enabling or disabling them in the configuration file).

PUTTING IT ALL TOGETHER – BUILDING WITH ANT

As stated above, I created a build script which will run before the tests. Using a build tool as Ant makes it easier to run both the build and test processes, with the test process being dependent on the build script. Below is an example of my build.xml.

<?xml version="1.0" encoding="UTF-8"?>
<project name="dummy" default="build" basedir=".">
	<target name="build" depends="prepare,phpunit" />
 
	<target name="prepare" description="Prepares the build">
		<delete dir="./logs/coverage" />
		<delete file="./logs/logfile.xml" />
 
		<mkdir dir="./logs" />
 
		<exec executable="php" failonerror="true" dir=".">
			<arg line="build.php --plugins=dummy" />
		</exec>
 
		<echo message="Logs directory cleared and build instance prepared." />
	</target>
 
	<target name="phpunit" description="Runs the PHPUnit tests">
		<exec executable="phpunit" failonerror="true" dir="." />
		<available file="./logs/logfile.xml" property="logfile.exists" />
 
   		<fail unless="logfile.exists" message="XML report processing files missing - PHPUnit hasn't exported the test results."/>
   		<echo message="PHPUnit tests completed." />
	</target>
</project>

The Ant project runs the build script which, as stated earlier on, enables plugins and creates dummy tables. Once the build script has finished running the build process, the Ant build tool then runs the PHPUnit tests which then creates a JUnit XML file. This file can then be used in different ways, for example importing it into a continuous integration server. The command to run the Ant build is shown below:

ant -f build.xml

One last note, all the files in the repository will need to be placed in your plugins directory.

And that’s about it really, very simple to set up. You can download all the code used here from the Github repository, please feel free to extend or refactor it as you wish.

Tags: , , , , ,

Old school browser detected!

Your browser is out of date, which means you can't use any of the site's required features. You can either upgrade your browser or switch to either Mozilla Firefox or Google Chrome