PHP DevCenter
oreilly.comSafari Books Online.Conferences.

advertisement


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.


Valuable Online Certification Training

Online Certification for Your Career
Earn a Certificate for Professional Development from the University of Illinois Office of Continuing Education upon completion of each online certificate program.

PHP/SQL Programming Certificate — The PHP/SQL Programming Certificate series is comprised of four courses covering beginning to advanced PHP programming, beginning to advanced database programming using the SQL language, database theory, and integrated Web 2.0 programming using PHP and SQL on the Unix/Linux mySQL platform.

Enroll today!


Sponsored by: