Continuous testing in PHP with Eclipse

If you already write test cases over your code, then continuous testing is a simple idea: Get fast feedback on your code by running your project’s unit tests very frequently. From PHP, the de facto standard testing tool is PHPUnit, so running tests usually involves typing phpunit into a terminal.

This post will show you how to set up a project with the Eclipse MakeGood plugin, which is a continuous test runner for the Eclipse IDE. When it is enabled, the plugin runs a set of tests every time you save a file.

This is best used on projects which are:

  • Small — so you tests run in a reasonable timeframe.
  • Complex — reasonably high risk of introducing defects.
  • Well-tested — so your unit tests are reasonably likely to identify defects.

Set up your machine

Your machine needs Eclipse PDT, MakeGood and composer.

Eclipse PDT

If you’ve found this post via Google, then you probably already run Eclipse and PHP. If not, fetch the “Eclipse for PHP developers” distribution from here

You need to pick up the XDebug extension to really make use of this setup. On Debian-based distributions, this is achieved via:

sudo apt-get install php5-xdebug

If you don’t use Eclipse yet, then check whether languages or tools you’ve used before have plugins in the Eclipse Marketplace. There’s a good chance that Eclipse will do the whole lot.

MakeGood

MakeGood is simply an Eclipse plugin, so you can fetch it from the Eclipse Marketplace.

Navigate to Help → Eclipse Marketplace and search for “makegood”:

2015-06-eclipse-makegood-01

Wait for the info to be fetched:

Check all the boxes, and then click Confirm:

2015-06-eclipse-makegood-03

Accept the terms, and wait for the install:

2015-06-eclipse-makegood-02

At one point, you will be prompted to accept a developer’s certificate, which you need to do before continuing:

2015-06-eclipse-makegood-04

This feature will not work unless you have a PHP binary set up. You need to configure this under Window → Preferences → PHP → PHP executables.:

2015-07-eclipse-add-php-executable

This is system dependent: Windows users will find a file somewhere called php-cli.exe, while Linux users might find it at /usr/bin/php.

After this, MakeGood is installed. It is configured per-project, so we’ll get to that after the next section.

Composer

Composer is a dependency manager which is widely used in the PHP world.

You can get it from https://getcomposer.org/download/

Rather than use the install process which is suggested there, you can simply download the phar, make it executable, and place it somewhere like /usr/bin:

wget https://getcomposer.org/composer.phar
chmod +x composer.phar
sudo mv composer.phar /usr/bin/composer
composer --version

By using composer to load PHPUnit, MakeGood becomes much easier to configure.

Project setup

To start, make a new PHP project by clicking File → New → PHP Project. Name it something like “Example”.

We are using composer here because it provides an easy way to make sure MakeGood can see the PHPUnit code. So let’s set a few things up:

First, add a composer.json file which adds a recent version of PHPUnit as a require-dev dependency. Right-click Example project, and navigate to New → File, naming it composer.json:

{
    "require-dev": {
        "phpunit/phpunit": "4.5.*"
    }
}

Note that with the way that MakeGood constructs its command-line, newer versions of PHPUnit do not appear to work correctly at the time of writing.

Next up, install the new dependency with the help of Composer:

$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev)
  - Installing sebastian/version (1.0.6)
  - Installing sebastian/global-state (1.0.0)
  - Installing sebastian/recursion-context (1.0.1)
  - Installing sebastian/exporter (1.2.1)
  - Installing sebastian/environment (1.3.2)
  - Installing sebastian/diff (1.3.0)
  - Installing sebastian/comparator (1.2.0)
  - Installing symfony/yaml (v2.7.3)
  - Installing doctrine/instantiator (1.0.5)
  - Installing phpdocumentor/reflection-docblock (2.0.4)
  - Installing phpspec/prophecy (v1.4.1)
  - Installing phpunit/php-text-template (1.2.1)
  - Installing phpunit/phpunit-mock-objects (2.3.6)
  - Installing phpunit/php-timer (1.0.7)
  - Installing phpunit/php-token-stream (1.4.3)
  - Installing phpunit/php-file-iterator (1.3.4)
  - Installing phpunit/php-code-coverage (2.2.2)
  - Installing phpunit/phpunit (4.5.1)

sebastian/global-state suggests installing ext-uopz (*)
phpdocumentor/reflection-docblock suggests installing dflydev/markdown (~1.0)
phpdocumentor/reflection-docblock suggests installing erusev/parsedown (~1.0)
phpunit/phpunit suggests installing phpunit/php-invoker (~1.1)
Writing lock file
Generating autoload files

Set up an Example.php class with a bit of code:

<?php
/**
 * Example of some code that could do with test cases
 */
class Example {
	/**
	 * Faulty palindrome checker. A palindrome is a word which is the same when read orward and backward.
	 * 
	 * @param string $str
	 * @return boolean True if $str is a palindrome, false if it is not.
	 */
	function isPalindrone($str) {
		$halfLen = (int)(strlen($str) / 2);
		for($i = 0; $i < $halfLen; $i++) {
			// Compare characters from the front and back of the string, and move toward the middle.
			$c1 = substr($str, $i, 1);
			$c2 = substr($str, (2 * $halfLen - 1) - $i, 1);
			if($c1 != $c2) {
				return false;
			}
		}		
		return true;
	}

}

Set up a test/unit folder and create a ExampleTest class with a useful test case in it, like so:

<?php
class ExampleTest extends PHPUnit_Framework_TestCase {
	public function testIsPalindrome() {
		$e = new Example();
		$this -> assertTrue($e -> isPalindrone(""));
		$this -> assertTrue($e -> isPalindrone("a"));
		$this -> assertTrue($e -> isPalindrone("ABBA"));
		$this -> assertFalse($e -> isPalindrone("test"));
	}
}

The unit tests go in their own folder, in case you decide to add more extensive (slower) tests later. MakeGood should only be configured to run fast tests!

Now add a test/bootstrap.php for test cases to load classes (NB: At scale, you would have composer autoload your classes, and use that as your PHPUnit class loader).

<?php
require_once 'vendor/autoload.php';
require_once 'Example.php';

Optionally, also add a phpunit.xml file in your project root to configure PHPUnit with, so that you can run it on the command-line:

<phpunit
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.5/phpunit.xsd"
         bootstrap="test/bootstrap.php">
</phpunit>

If all goes to plan, your project should look something like this:

- Continuous testing example
  |- (.. code ..)
  |- Example.php
  |- test/
  |  |- (.. other types of tests ..) 
  |  |- unit/
  |  | |- ExampleTest.php
  |  |- bootstrap.php
  |- vendor
  |  |- (.. dependencies ..)
  |  |- autoload.php
  |- composer.json
  |- phpunit.xml

And more to the point, you should be able to use PHPUnit on the command-line to run the test case. It will look something like this:

Example$ php vendor/bin/phpunit test/unit/
PHPUnit 4.7.7 by Sebastian Bergmann and contributors.

.

Time: 71 ms, Memory: 4.50Mb

OK (1 test, 4 assertions)

Configure MakeGood

Now that the project is coming along nicely, it’s time to configure MakeGood to run the test automatically from within Eclipse.

Go to Project → Properties → MakeGood, and add the path to your unit tests, along with a link to the bootstrap.php. This bootstrap file will let MakeGood load the classes to test, along with PHPUnit itself.

2015-06-makegood-demo1

Now fire up the MakeGood view. If it doesn’t come up automatically, then open it via Window → Show View → Other. Clicking the “play” button should kick off all available tests, adn if your project structure is correct, then everything will work at this point:

2015-06-makegood-demo2

Once you save a file, a small box will appear in the bottom-right to indicate whether the tests worked.

2015-06-makegood-demo3

As you introduce changes and save them, MakeGood will flag whether the tests are passing by changing the colour of the icon.

In this case, it is green. From the small delay that is caused when running the tests, you can see why it’s important to keep unit tests fast if you use this tool.

A test-driven bugfix cycle

Of course, Linus’ Law suggests that any code will probably have more bugs. So, say somebody reports that the palindrome “abcdedcba” is causing incorrect results. In test-driven development, you would expose this as a test case over the isPalindrome() function:

<php
...	
	public function testIsPalindromeOddLength() {
		$e = new Example();
		$this -> assertTrue($e -> isPalindrone("abcdedcba"));
		$this -> assertFalse($e -> isPalindrone("abc"));
	}
...

As soon as this is submitted, things start to turn red in MakeGood. In response to the broken test, you can then tweak the code.

2015-06-makegood-demo4

You would then do the smallest amount of programming needed to pass all the tests. A palindrome checker which does not contain intentional bugs is as simple as this one-liner:

<php
//...
	function isPalindrone($str) {
		return $str == strrev($str);
	}
//...

This change causes MakeGood to report that the unit tests are all passing.

2015-06-makegood-demo5

Summary

A continuous test runner adds faster feedback for tedious but well-tested code. If you have to write anything in PHP which does some real computer work, then I suggest setting up MakeGood so that you can make changes to tricky code, and have a good idea of whether it’s producing sane outputs.

As this post has touched on before, slow tests should not run each time you save the code. This is why we worked in a Unit test folder. Slower integration or functional tests can still be run via the command-line in this model. An example of a project which uses this setup is escpos-php, which is a heavily test-driven PHP driver for thermal line printers.

Leave a Reply

Your email address will not be published. Required fields are marked *