Using Phing from TextMate to run PHPUnit tests

Jul 4 2009

I have recently converted some of my PHP projects over to use the awesome Phing build tool. Phing is similar to Apache Ant, a tool I am familiar with from my Java development days. I have crafted Phing's build.xml for QueryPath to handle building packages, generating API docs, running coverage analyses, linting, and (of course) running QueryPath's ~250 tests.

Having left IDEs behind for the time being, I have been working hard to build an personalized toolchain for PHP development. I've settled on TextMate as my editor (though I still use vim quite often). One thing I wanted to be able to do is run unit tests from within TextMate.

Initially I built a shell script to run the unit tests. But after moving to Phing, it seemed like it should be possible to take advantage of Phing's unit test running abilities (and lovely HTML output) from within TextMate. Working this out took two major stages:

  1. Writing a custom target in Phing's build.xml
  2. Creating a simple TextMate bundle for running Phing tasks

This article shows how to accomplish each of these steps. <!--break-->

Wring a Unit Test Target for Phing

Already I have created basic unit testing targets for Phing. In fact, by the time I began this particular endeavor, I already had three PHPUnit tasks in my build.xml file: One to do coverage analysis, one to generate full HTML PHPUnit output, and one to do a quick unit test so that I could quickly check if all unit tests passed.

But for the TextMate output, I decided to add a new build target. Here's what it looks like:

  <target name="tmtest" description="Run test, optimized for TextMate output.">
    <phpunit>
      <formatter type="xml" todir="test/reports"/>
      <batchtest>
        <fileset id="unittests" dir="./test/Tests">
          <include name="**/*Test.php" />
        </fileset>
      </batchtest>
    </phpunit>
     <phpunitreport 
        infile="test/reports/testsuites.xml" 
        format="noframes" 
        todir="test/reports/html" 
        />
  </target>

(The portion above has been slightly modified to remove reliance on external properties and filesets. The intent was to make it easier to read.)

Effectively, there are two parts to the example above. The first is the running of the unit tests, which is handled by the phpunit task. In a nutshell, it scans the ./test/Tests directory and all subdirectories, running any PHP file that matches the pattern *Test.php. The results of all of the unit tests are formatted in XML and stored in test/reports/testsuites.xml

The second task run in the above target is the phpunitreport task, which takes the XML file produced by the first script and transforms it into HTML. The resulting bundle of HTML reports will be stored in test/reports/html.

From the command line, then, I can run $ phing tmtest and Phing will run the unit tests, store the results in an intermediate XML file, and then transform the data in that XML file into a series of HTML files. To view the HTML files, I can point my browser to test/reports/html/phpunit2-noframes.html and see the results of my unit testing.

With the build script complete, the next thing to do is make it possible to execute this inside of TextMate (and display the results from TextMate, as well.) To do that, I created a simple TextMate bundle with a single command.

Creating a Phing TextMate Bundle

Caveat: My current solution is not a generalizable one. It works perfectly for my current tasks, but assumes certain naming conventions and locations that certainly are not generalizable.

From within TextMate, I opened the Bundle Editor (Bundles > Bundle Editor > Show Bundle Editor) and created a new Bundle called Phing.

Inside of this bundle, I created one command called phing tmtest (named after the command like command I would issue to achieve the same output). Here's what the bundle looks like:

The important settings are these:

  • Save is set to Nothing
  • Input is set to None
  • Output is set to Show as HTML

To fire the command, I have mapped Command-Shift-I. Any command sequence will do, though.

Now let's take a closer look at the command itself.

phing -buildfile $TM_PROJECT_DIRECTORY/build.xml tmtest > /dev/null

echo "<meta http-equiv='Refresh'
        content='0;URL=tm-file:///$TM_PROJECT_DIRECTORY/test/reports/html/phpunit2-noframes.html'>"

There are two parts to this command. The first is the actual running of the unit tests: phing --buildfile $TMPROJECTDIRECTORY/build.xml tmtest > /dev/null

Note that because I want to be able to issue this command anywhere in a project, I have to use the --buildfile flag. And since I don't directly want to see the command's output (which consists largely of informational messages telling me that the task was run), I redirect the output to /dev/null. In essence, though, this is just a slightly more elaborate way of calling phing tmtest.

The second line simply tells TextMate's embeded webkit browser component to redirect to the location where the HTML output has been generated. This little trick comes straight out of the TextMate help text, as does one other minor detail: The URL above uses the tm-file:// protocol instead of just file://. This is done to work around a security feature of webkit that prevents it from redirecting to local files (a good thing for web browsers, but not so good for local file tools like TextMate).

Putting it Together

With the bundle created, I can now run the debugging task from within TextMate simply by hitting Command-Shift-I. Doing so will generate a screen that looks something like this:

And that is all there is to it.

Now that I have a basic Phing bundle, I will begin working on other ways of integrating Phing and TextMate. Clearly, it is simple to launch Phing tasks using TextMate commands. It should be easy to, for example, add support to lookup QueryPath documentation from within TextMate. And perhaps there are some clever debugger tricks that can be accomplished this way, too.