Try Tuts+ Premium, Get Cash Back!
Automatic Testing for TDD with PHP

Automatic Testing for TDD with PHP

Tutorial Details
    • Languages: Ruby and PHP
    • Difficulty: Medium
    • Estimated Completion Time: 60+ min.
This entry is part 4 of 12 in the Test-Driven PHP Session
« PreviousNext »

Traditional test-driven development can, at times, be cumbersome. You have to stop writing code in order to run your tests. Luckily, there are solutions, which provide the ability to automatically run your tests as you code. In this tutorial, you will learn how to use a Ruby gem, called watchr, to monitor your code and automatically run the appropriate tests whenever you save your work.


Step 1: Software Requirements

Any tool that helps you obtain quicker feedback is a valuable asset.

This tutorial uses PHP for the code example, however, the techniques are applicable for any language, which offers a CLI utility for unit testing. Ruby is required because we will use the watchr gem. So, make sure you have a working installation of Ruby and PHP with PHPUnit.

Next, ensure that you have libnotify installed, if you’re on Linux; Windows and Mac OSX users need “Growl.” This tutorial is directly applicable on Linux, but I will suggest alternative commands and settings where possible.

Now, it’s time to install the watchr gem. Open a console, and make sure you are in the folder where you can directly run gem. Type the following command:

gem install watchr

Step 2: Technical Background

When a file or folder is modified, watchr can trigger a callback function.

The watchr gem is an executable program written in Ruby, and it wraps around features found in an operating system’s file system to provide the ability to watch for changes made to a specific file or folder. Naturally, these file system features differ for each operating system and file system.

watchr provides a unified application programming interface (API) for all operating systems. On Linux, it uses inotify, the kernel’s file system event library; on other operating systems, it uses the appropriate alternative. If, for some reason, the operating system does not have an available event service, watchr periodically polls the watched file or folder.

When a file or folder is modified, watchr can trigger a callback function. We will use this function to run our tests.


Step 3: Create a PHP Project

Our project is rather simple. Replicate the simple directory structure shown in the following image:

New PHP Project

In the Nettuts.php file, add the following code:

<?php

class Nettuts {

}

?>

Next, add the following code to NettutsTest.php:

<?php

require_once dirname(__FILE__) . '/../Classes/Nettuts.php';

class NettutsTest extends PHPUnit_Framework_TestCase {

  protected $object;

  protected function setUp() {
    $this->object = new Nettuts;
  }

  protected function tearDown() {

  }
}

?>

At this point, the test file is simply a skeleton, and, as you can see in the image above, the tests pass.


Step 4: Create the First watchr Script

Now, we need to create a Ruby file in our project’s folder; let’s call it autotest_watchr.rb. Next, add the following code to the file:

watch("Classes/(.*).php") do |match|
  run_test %{Tests/#{match[1]}Test.php}
end

Automated tests are IDE independent – a big plus in my book.

This code uses the watch method to watch all the .php files in our project’s Classes folder. When a .php file changes, the operating system issues an event, and our watch method will be triggered. The name of the .php file is returned (minus the extension) in a match array’s position of 1. As with any regular expression, parentheses are used to specify a match variable, and in this code, we use them in the matching condition to fetch the file name. Then, we call the run_test method with the path of the composed test file name.

We should also watch our test files; so, add the following code to the Ruby file:

watch("Tests/.*Test.php") do |match|
  run_test match[0]
end

Note that the match array contains the full file name at position 0, and we pass it directly to the run_test method.


Step 5: Make the Script Run the Tests

The Ruby script is set up to watch our .php files, and now we need to implement the run_test method. In our case, we want to run PHPUnit for the specific file.

def run_test(file)
  unless File.exist?(file)
    puts "#{file} does not exist"
    return
  end

  puts "Running #{file}"
  result = `phpunit #{file}`
  puts result
end

We first ensure that the file exists, and simply return if it doesn’t. Next, we run the test with PHPUnit and send the result to the console. Let’s run our watchr script. Open your console, navigate to your project’s directory, and then run:

watchr ./autotest_watchr.rb

Windows users should omit “./” from the above command.

Now modify one of the .php files (just add an empty line at the end of the file), save it, and observe the output in the console. You should see something similar to what’s shown below:

Running Tests/NettutsTest.php
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 3.75Mb

There was 1 failure:

1) Warning
No tests found in class "NettutsTest".

/usr/bin/phpunit:46

FAILURES!
Tests: 1, Assertions: 0, Failures: 1.

Yep, we don’t yet have a test to run; so let’s put in a dummy test. Add the following code to the test PHP file:

function testDummyPassingTest() {
  $this->assertTrue(true);
}

Run the Ruby script again, and you should see:

Running Tests/NettutsTest.php
PHPUnit 3.6.0 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 3.75Mb

OK (1 test, 1 assertion)

Step 6: Parse the Test Output

Let’s notify the user, via the system’s notification mechanismm about the test results. We’ll modify the run_tests method to trigger a method, called notify. Below is the modified run_tests:

def run_tests(file)
  unless File.exist?(file)
    puts "#{file} does not exist"
    return
  end

  puts "Running #{file}"
  result = `phpunit #{file}`
  puts result

  if result.match(/OK/)
    notify "#{file}", "Tests Passed Successfuly", "success.png", 2000
  end
end

The name of the image file, success.png, points to the image you want to display in the notification area. This image is not provided in this tutorial; so you will need to find your own. Now, let’s write the notify method:

def notify title, msg, img, show_time
  images_dir='~/.autotest/images'
  system "notify-send '#{title}' '#{msg}' -i #{images_dir}/#{img} -t #{show_time}"
end

Mac OSX and Windows users: replace the notify-send command with the appropriate Growl alternative. Modify something in either your test or code file so that the test still passes. Save the modified PHP file, and watch the magic happen. Below is an image of the result on my system:

Tests Passed Successfuly

Next, we need to catch the failures. The following code adds a couple of lines to run_tests:

def run_tests(file)
  unless File.exist?(file)
    puts "#{file} does not exist"
    return
  end

  puts "Running #{file}"
  result = `phpunit #{file}`
  puts result

  if result.match(/OK/)
    notify "#{file}", "Tests Passed Successfuly", "success.png", 2000
  elsif result.match(/FAILURES\!/)
    notify_failed file, result
  end
end

Also, let’s add the notify_failed method to the file:

def notify_failed cmd, result
  failed_examples = result.scan(/failure:\n\n(.*)\n/)
  notify "#{cmd}", failed_examples[0], "failure.png", 6000
end

Modify either of your PHP files to make the test fail; save the modified file. Observe the notification message. It contains the name of the first failing test. This name is selected by the regular expression in the method notify_failed, which parses the PHPUnit output.

Tests Failed

Step 7: Clear the Console Before Each Test Run

Add the following method to your Ruby script, and be sure to call it in the run_test method. The code should work in Linux and Mac OSX, though you might need to do some research for Windows.

def clear_console
  puts "\e[H\e[2J"  #clear console
end

Conclusion

Whenever you program using TDD, any tool that helps you obtain quicker feedback is a valuable asset. My coworkers use similar scripts with watchr or alternatives (some are written around fs_event on MacOS). Needless to say, we’re spoiled now, and can’t imagine developing anything without automatically running tests.

Automated tests are IDE independent – a big plus in my book. Too many IDEs force you to use a specific testing framework, and don’t get me started on remote testing. I prefer to use scripts like this daily, and surely recommend them to any agile software developer.

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • tungd

    Another way is use `guard` with `guard-phpunit`. Write a simple Guardfile and it just works. It supports not only notification but also special things like keep running failed tests and rerun all the tests after changed test pass.

    • villimagg

      @tungd

      How would you do this autotest using Guard on Linux?
      I got Guard working but I don’t know anything in Ruby or how to properly use Guard.

      Hope you see this message. Thanks!

  • Gabor

    Very useful! – de jó látni itt egy magyar srácot is, gratula.

    • http://my.opera.com/patkoscsaba/blog/ Patkos Csaba
      Author

      Thanks – koszi, jo tudni hogy magyarok is olvassak amiket itt irnak.

  • Mohit Bumb

    Cool

  • XYZ

    Helo

  • http://www.redcrown.co Matthew Strickland

    How would you change the notify commands to use Mountain Lion’s Notifications instead of using Growl?

    • http://my.opera.com/patkoscsaba/blog/ Patkos Csaba
      Author

      Sorry, I don’t have Mountain Lion.

  • https://github.com/netzpirat Michael Kessler

    I recommend to have a look at Guard, which is similar to Watchr but still maintained. There exists a Guard plugin for PHPUnit, which makes most of the code shown in this article obsolete and enhances the workflow by starting all specs on start, remember failed specs to run them until they pass and rerun all test after a failing spec passes again.

  • http://muraliprashanth.me Murali Prashanth

    Thanx….

  • Ian

    Awesome! Thank you!

    I got my first “learn by doing” project in ruby from this too :-)

    Had some issues with quotes and double quotes sending the growlnotify commands. This probably isn’t correct considering I’m completely new to writing ruby code but this worked for me in Windows.

    def notify title, msg
    cmd = “growlnotify /t:\”#{title}\” \”#{msg}\”"
    puts cmd
    system “growlnotify /t:\”#{title}\” \”#{msg}\”"

    end

    def notify_failed cmd, result
    failed_examples = result.scan(/failure:\n\n(.*)\n/)
    example = “#{failed_examples[0]}”
    example = example.gsub(/”/, ”)
    notify “FAILURE! — #{cmd}”, “#{example}”
    end

    I’m sure after learning more about strings in ruby I’ll find this was all unnecessary but to get the correct growlnotify command (growlnotify /t:”title text” “message text”) this is what I had to do.

    Thanks again.

    • ian

      Oh, and by the way, I just left out the image and duration parameters because I’m also new to using growl. I’ll add those back in with some custom icons later when I get it figured out.

  • http://blog.jphpsf.com/ JP

    Hi,

    Great post, I’ve used watchr in the past.

    Another trick I’ve used is using inotify on Linux. If you install the inotify-tools package, then you get access to a command called inotifywait right in your shell. This is Linux only, but it does not require any Ruby knowledge. As an example, this is a bash function I have in my profile:

    function rebuild {
    while inotifywait -qr -e close_write $2; do clear && $1 $2; done
    }

    Then, in my shell, I use like this:

    rebuild /command/to/run /path/to/watch

    For example, to run my Cucumber test suite:

    rebuild cucumber ./tests/features

    A last project I’d like to mention: rebuild from Nicholas Zakas. See the GitHub repo at https://github.com/nzakas/rebuild. It’s essentially like watchr, but built in Javascript and running on Node.js.

  • http://my.opera.com/patkoscsaba/blog/ Patkos Csaba
    Author

    Thank you for your appreciations. I’m happy you find this little tutorial useful.

    I also, sometimes, use versions of this in a not so common way, but I tend to make this auto test as easy to maintain as possible and as portable as possible. For example I prefer to make it in a way that it’s easy to adapt both to my Linux home PC and to my MacOS at work.

  • http://mac-blog.org.ua Marchenko Alexandr

    If you are on Ubuntu linux, you already have needed icons, here is examples:

    notify-send Hello World -i /usr/share/icons/Humanity/actions/48/dialog-ok.svg

    notify-send Hello World -i /usr/share/icons/Humanity/status/48/error.svg

  • http://yuyat.jp yuyat
  • Villi Magg

    Any ideas on how to install this Growl package on Fedora?
    I get errors when I run ‘make’ and ‘sudo make install’ when installing Growl-for-Linux.
    I think that dbus-glib-1 package is most definitely called something else in Fedora (if you see the installation instructions https://github.com/mattn/growl-for-linux)

    Regards.

    • http://my.opera.com/patkoscsaba/blog/ Patkos Csaba
      Author

      On any Linux including Fedora, all you need is libnotify as I mentioned in Step1. All the code presented in this tutorial is on Linux with libnotify. It doesn’t matter if you have Gnome or KDE.

  • http://twitter.com/omgcarlos Carlos Ramos

    Nice! There’s a javascript library called LiveReload which attempts to do a similar thing, even comes with a browser extension/desktop app which increases web development time dramatically. In fact, it functions similarly to Meteor.js in that the second you save it automatically refreshes the page (although states aren’t saved). Not quite the same as reloading the app, but for web development, it’s essentially the same thing anyways.

    Couple that with dual monitors and I never ever have to leave Sublime throughout the entire development process unless I need to edit some images. Automatic testing is simply…amazing.

  • http://my.opera.com/patkoscsaba/blog/ Patkos Csaba
    Author

    I respond here to all suggesting Guard and Guard-PHPUnit. I didn’t know about them but following your suggestions I’ve taken a look.

    Guard is basically just another Watchr. However I think Watchr has a better implementation for watching files.

    Guard-PHPUnit is just a wrapper over Guard. Basically the same thing with what we did here. We made a wrapper over Watchr in a sense.

    As a final thought, Guard-PHPUnit is probably as good a solution as this tutorial. It’s up to each of you to decide which one to use. I prefer to have more control and be able to use on several operating systems. Does Guard-PHPUnit run on OpenIndiana? If yes, how does it check for file system events on ZFS?

  • http://ditscheri.com Dan Bakan

    That’s indeed very helpful! Thanks for the detailed information!

  • https://github.com/hpbuniat/testy Hans-Peter Buniat

    My approach to solve the issue was creating testy:
    https://github.com/hpbuniat/testy

  • http://www.van.web.id han

    awesome nice tutorial
    very useful sir

    • http://my.opera.com/patkoscsaba/blog/ Patkos Csaba
      Author

      You are welcome.

  • http://www.facebook.com/tamerlan.soziev Tamerlan Soziev

    why is this tutorial in ruby section?

  • http://www.facebook.com/susanbuck Susan Buck

    Got it working, thanks! Wrote up some additional notes on how to get the Growl notifications working (including icons), as well as more details for those not so familiar with Ruby gems: http://codagogy.com/blog/2012/12/18/tdd-part-2-making-your-tests-run-on-their-own

  • adrian

    .http://adrian-stolarski.pl/exercices/Example-unit-test.html

    What do you think about unit tests?

    How do you like a section devoted to exercise?

    it’s your feedback

    That is I need to go in the right direction

  • Aldo

    Nice post. practical information on TDD is welcome. I’d like to find something about tdd applied to ios and objective c, the TDD course i’ll take is about that. Thanks!