PHP DevCenter

oreilly.comSafari Books Online.Conferences.

We've expanded our LAMP news coverage and improved our search! Search for all things LAMP across O'Reilly!

Search
Search Tips

advertisement

Listen Print Discuss Subscribe to PHP Subscribe to Newsletters

Code As Data: Reflection in PHP
Pages: 1, 2

Code Generation

Once the test cases have been created via the user interface, all that is left is to actually build the code. This is a fairly simple operation in terms of setting up the code for each test and then running it. If the method returns the expected result, then the test passes; if not, then it fails. The tests are run in order by a test framework. For each test, a simple function must be created to create the object, call the method, check the result, and then do any cleanup that is needed. Each test will be a simple function with no parameters. The test functions will return PASS or FAIL.



The tests are built by a command-line tool. The tool reads in the XML file for the tests to be generated, and then outputs a PHP file containing the tests, which may then be run with the test runner script.

Each test is implemented as a PHP function with no parameters. The test runner will take a list of those functions and run them in order. If a test passes it will return the constant "PASS"; if it fails, it will return "FAIL." The test runner script can then iterate over all of the tests and return the data to the user. By default, it will run at a command line with the test name followed by "PASS" or "FAIL." It may also be useful to log how long each test took to run.

The basic format of each test is to call the class constructor (with any supplied parameters), then check that the constructor returned a valid object of the correct type. If it did not, the test will fail (unless that is the expected outcome). Then it calls the method to be tested. The test will then check that all of the assertions are true.

There are three types of possible assertions. The first is a return value, which can be true, false, null, a string, numeric value, a specific result, or anything else that can be returned by a PHP function. Any of these can be tested for. The test will PASS if the return value is what was expected, and FAIL otherwise.

The second type is to check whether a class value is set. A class property assertion can test for the same types of values as a return value assertion. In some cases, there will be more than one class property assertion, or a return value assertion and a property assertion. In this case, they both must be satisfied.

The third type of assertion is an exception. This can be a general Exception or a specific type of Exception. In this case, a test will pass if the expected Exception happens, and fail otherwise.

If there are several assertions for a test, then all of them must be satisfied for the test to PASS.

The test runner will have an ordered list of all the test functions. It will then run them in order. If a test fails, the runner will stop and report the error.

Here is the code to generate the tests:

  
  public function buildClassTests($class)
  {
    
    $methods = $class->xpath('method');
    $tests   = array();
    foreach($methods as $method)
      {
    $tests[] = build_Tests($method);
      }
    $filename  = $class['name'] . ".tests.php";
    $test_file =  join('',$tests);
    
    file_put_contents($filename, $test_file);
    
  }

  private function build_tests($method)
  {
    $tests      = $method->xpath('test');
    $test_name  = $method['name'];
    foreach($test as $test)
      {
    $test_name = $test['name'];
    
    $parameters = $test->xpath('cons_parameter');
    
    foreach($parameter as $param)
      {
        if($param['value'] == 'DEFAULT')
          break;
        $cons_p[] = "'".$param['value'] .".";
      }
    
    $parameters = $test->xpath('parameter');
    $p = array();
      
    foreach($parameter as $param)
    {
      if($param['value'] == 'DEFAULT')
        break;
      $p[] = "'" . $param['value'] ."'";
    }
    $p = join(',',$p);
    $assert     = array();

    $exception        = 'Exception';
    $exceptionReturn  = 'FAIL';
    
    $assertions = $test->xpath('assertion');
    /* Current code only supports one assertion, but it may sometimes be useful to have more than one*/
    foreach($assertions  as $a)
      {
        switch($a['type'])
          {
          case 'return':
        $assert[] = sprintf("%11s%20s%4s%20s","",'$result',$a['cond'],$a['value']);
        break;
          case 'exception':
        $exception = $a['exception'];
        $exceptionReturn = 'PASS';
          }
      }
    
    if($exception != 'Exception')
      $exep           = "           catch (Exception $e) \n".
        "           {\n".
        "               return FAIL;".
        "         \n}\n";
    else
      $exep = "";

    if($assert)
      {
        $assert_block = "           if(\n". join("&&\n",$assert) . "             )\n";
        $assert_block = "              return PASS\n";
        $assert_block = "           else\n";
        $assert_block = "              return FAIL\n";
        
      }
    

      $code = <<<CODE
    function test_$test_name()
    {
      try
      {
            /* this is outputting php code, so the $ signs need to be escaped */
        \$class  = new $ClassName();
        \$result = \$class->$name($p);
$assert
      }
      catch ($exception \$e)
      {
        return $exceptionReturn
      }
      $excp
      
CODE;
      $tests[] = $code;
    }
      return join("\n\n\n",$tests);
    }

This is a simple test case. If the method returns true, it passes; if the method returns false or throws an exception, it fails.

    function test_test4()
    {
      try
      {
           
         $class  = new testClass();
         $result = $class->execute('12345');
             if($result == true)
               return PASS;
             else 
               return FAIL;
      }
      catch (Exception $e)
      {
        return FAIL;
      }

Here is the generated code for the listing code runner:

<?php
$tests = array(list of tests);
$i     = 0;
$fail  = false;
foreach ($tests as $test)
{
  $i++;
  $result = $test();
  if($result == PASS)
   echo "$i Test $test PASS<br/>";
  else
  {
    echo "<span class='fail'>$i Test $test FAIL</span><br/>";
    $fail = true;
    break;
  }

} 
if($fail)
 echo "SOME TESTS FAILED";
else 
 echo "ALL TESTS PASSED";

?>

Zachary Kessin has worked with free software and web development for more than ten years. He is a frequent speaker at Jerusalem.pm and has spoken at YAPC::Israel.


Return to PHP DevCenter.


How could you use PHP reflection to automate some of your programming chores?
You must be logged in to the O'Reilly Network to post a talkback.
Post Comment


Tagged Articles

Be the first to post this article to del.icio.us

Sponsored Resources

  • Inside Lightroom
Advertisement

Sponsored by:

O'Reilly Media

©2009, O'Reilly Media, Inc.
(707) 827-7000 / (800) 998-9938
All trademarks and registered trademarks appearing on oreilly.com are the property of their respective owners.
About O'Reilly
Academic Solutions
Authors
Contacts
Customer Service
Jobs
Newsletters
O'Reilly Labs
Press Room
Privacy Policy
RSS Feeds
Terms of Service
User Groups
Writing for O'Reilly
Content Archive
Business Technology
Computer Technology
Google
Microsoft
Mobile
Network
Operating System
Digital Photography
Programming
Software
Web
Web Design
More O'Reilly Sites
O'Reilly Radar
Ignite
Tools of Change for Publishing
Digital Media
Inside iPhone
O'Reilly FYI
makezine.com
craftzine.com
hackszine.com
perl.com
xml.com

Partner Sites
InsideRIA
java.net
O'Reilly Insights on Forbes.com