Powered By Blogger

jueves, 16 de agosto de 2012

Automatic Testing for TDD with PHP

Traditional test driven development (TDD) can sometimes be cumbersome; you have to stop writing code in order to run your tests. But that is starting to change as new solutions provide a means to automatically run your tests as you write code. In this tutorial, you will learn how to use a smart Ruby gem, called watchr, to monitor your code and automatically run the appropriate test whenever you save your work.


Step 1: Software Requirements

Whenever you program using TDD, any tool helping you obtain quicker feedback is a valuable asset.

This tutorial uses PHP for the code example, but it is applicable for any language which has a CLI utility for unit testing. Ruby is also required because we will use the watchr gem. So, make sure you have a working installation of Ruby and PHP with PHPUnit. You also need enough rights to install Ruby gems and other applications on your system.

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

Now 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

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, type the following code:

  <?php    class Nettuts {    }    ?>  

Now add the following code to the NettutsTest.phpfile:

  <?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 just a skeleton, and as you can see in the image, the tests pass.


Step 4: Create Our 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 in any regular expression, parentheses are used to specify a match variable, and in this code, we use parentheses in the matching condition to get 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 our 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

Our 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 like the output 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 output similar to the following:

  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 mechanism about the test results. We’ll modify the run_tests method to call 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, just 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  

MacOS X and Windows users, replace the notify-send command with the appropriate Growl alternative. Modify something in your 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 my result:

Tests Passed Successfuly

Now 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  

And 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: Extra Tip: Clear the Console Before each Test Run

Add the following method to your Ruby script, and make sure to call it in the run_test method. The code works on Linux and MacOS X; I don’t know about Windows.

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

Conclusion

Whenever you program using TDD, any tool helping 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; we cannot 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 use scripts like this daily. I love them, and I surely recommend them to any agile software developer.



No hay comentarios: