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:
In the Nettuts.php
file, type the following code:
<?php class Nettuts { } ?>
Now add the following code to the NettutsTest.php
file:
<?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:
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.
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:
Publicar un comentario