Hack80.Generate Your Unit Tests


Hack 80. Generate Your Unit Tests

Use PHP to build your unit tests from code comments.

Unit tests [Hack #79] are so critical to the development of a stable application that it's worth going to some effort to create them. However, there's some nice middle ground between writing unit tests completely by hand (including the routine portion of those tests that is the same, over and over again), and automating test creation. This hack shows how to use a script to generate unit test code from comments embedded in your PHP code. The comments are test specific, but this does cut down on the redundant code you have to type in.

8.3.1. The Code

Save the code in Example 8-3 as Add.php. Note that several tests are laid out using the == and != operators all in the PHP script comments.

Example 8-3. The code from which to build unit tests
 <?php // UNIT_TEST_START // ( 1, 2 ) == 3 // ( 1, -1 ) == 0 // ( 1, 1 ) != 3 // ( 1, -1 ) != 1 // UNIT_TEST_END function add( $a, $b ) { return $a + $b; } // UNIT_TEST_START // ( 1, 2 ) == -1 // ( 1, -1 ) == 2 // ( 1, 1 ) != 1 // ( 1, -1 ) != 1 // UNIT_TEST_END function minus( $a, $b ) { return $a - $b; } ?> 

Save the code in Example 8-4 as GenUnit.php. This script handles generation of tests from another script's comments.

Example 8-4. The unit test generator
 <?php if ( count( $argv ) < 2 ) {   print "GenUnit.php usage:\n";   print " php GenUnit.php <PHP Script>\n";   exit; } $infile = $argv[1]; define( 'STATE_NORMAL', 0 ); define( 'STATE_IN_UNIT_DEF', 1 ); define( 'STATE_WAITING_FOR_FUNC', 2 ); $state = STATE_NORMAL; $fh = fopen( $infile, "r" ); $tests = array(); $funcs = array(); while( $str = fgets( $fh ) ) {   if ( $state == STATE_NORMAL )   {     if ( preg_match( "|UNIT_TEST_START|", $str ) )   $state = STATE_IN_UNIT_DEF;   }   else if ( $state == STATE_IN_UNIT_DEF )   {     if ( preg_match( "|UNIT_TEST_END|", $str ) )   $state = STATE_WAITING_FOR_FUNC; else {   $str = preg_replace( "|^//\s*|", "", $str );   $str = preg_replace( "|\s*$|", "", $str );   $tests []= $str;     }   }   else if ( $state == STATE_WAITING_FOR_FUNC )   {     if ( preg_match( "|function\s+(.*?)\(|", $str, $out ) ) {   $funcs []= array(     'function' => $out[1], 'tests' => $tests   ); $state = STATE_NORMAL; $tests = array();     }   } } fclose( $fh ); ob_start(); $outfile = "Test".$infile; $classname = preg_replace( "|[.]php$|i", "", $outfile ); echo( "<?php\n" ); ?> // This code was written by GenUnit.php // // Do not alter the code manually or your revisions // will be lost the next time GenUnit.php is run. require_once '<?php echo( $infile ); ?>'; require_once 'PHPUnit2/Framework/TestCase.php'; class <?php echo( $classname ); ?> extends PHPUnit2_Framework_TestCase { <?php $id = 1; foreach( $funcs as $func ) {   foreach( $func['tests'] as $test ) { ?>   function test<?php echo($id); ?>() { $this->assertTrue( <?php echo( $func['function'].$test ) ?> ); } <?php $id+=1; } } ?> } <?php echo( "?>\n" ); $test_php = ob_get_clean(); print ($id-1)." tests created in $outfile\n"; $fh = fopen( $outfile, "w" ); fwrite( $fh, $test_php ); fclose( $fh ); ?> 

The GenUnit.php script is actually pretty interesting. Its job is to read in a PHP script, which it does at the top of the file. It then uses a state machine to parse through each line of the file, looking for the start and end of the special test comment blocks; for each comment, it interprets and stores the tests within the comment blocks into the $funcs array.

You can easily move from the UNIT_TEST_START and UNIT_TEST_END delimiters to anything you choose by modifying GenUnit.php.


The second half of the script then uses the $funcs array to create PHP test code. First, the script buffers the PHP output and creates the test class and each test function. Then it shuts down the text buffer and stores the buffer's output into the PHP test file.

8.3.2. Running the Hack

To run GenUnit.php, use the PHP command-line interpreter:

 % php GenUnit.php Add.php 8 tests created in TestAdd.php 

The script reads the Add.php file and creates TestAdd.php. That file (using Add.php) looks like this:

 <?php // This code was written by GenUnit.php // // Do not alter the code manually or your revisions // will be lost the next time GenUnit.php is run. require_once 'Add.php'; require_once 'PHPUnit2/Framework/TestCase.php'; class TestAdd extends PHPUnit2_Framework_TestCase {   function test1() { $this->assertTrue( add( 1, 2 ) == 3 ); }   function test2() { $this->assertTrue( add( 1, -1 ) == 0 ); }   function test3() { $this->assertTrue( add( 1, 1 ) != 3 ); }   function test4() { $this->assertTrue( add( 1, -1 ) != 1 ); }   function test5() { $this->assertTrue( minus( 1, 2 ) == -1 ); }   function test6() { $this->assertTrue( minus( 1, -1 ) == 2 ); }   function test7() { $this->assertTrue( minus( 1, 1 ) != 1 ); }   function test8() { $this->assertTrue( minus( 1, -1 ) != 1 ); }     } ?> 

You can see that the code that was in the comments is now embedded in test functions. Also note the comment at the top of the file, which tells potential coders that the file was generated (rather than hand-coded); it also clearly lets users know that if they modify the file, their changes will be lost the next time the generator is run.

You can run the generated script just like any other PHP code:

 % phpunit TestAdd.php PHPUnit 2.2.1 by Sebastian Bergmann. ........ Time: 0.010335922241211 OK (8 tests) 

This shows that eight tests were run and that they all worked properly.

8.3.3. See Also

  • "Test Your Code with Unit Tests" [Hack #79]



PHP Hacks
PHP Hacks: Tips & Tools For Creating Dynamic Websites
ISBN: 0596101392
EAN: 2147483647
Year: 2006
Pages: 163

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net