1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4/**
5 * PHP Version 4
6 *
7 * Copyright (c) 2002-2005, Sebastian Bergmann <sb@sebastian-bergmann.de>.
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 *
14 *   * Redistributions of source code must retain the above copyright
15 *     notice, this list of conditions and the following disclaimer.
16 *
17 *   * Redistributions in binary form must reproduce the above copyright
18 *     notice, this list of conditions and the following disclaimer in
19 *     the documentation and/or other materials provided with the
20 *     distribution.
21 *
22 *   * Neither the name of Sebastian Bergmann nor the names of his
23 *     contributors may be used to endorse or promote products derived
24 *     from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
29 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
30 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
32 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
36 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
38 *
39 * @category   Testing
40 * @package    PHPUnit
41 * @author     Scott Mattocks <scott@crisscott.com>
42 * @copyright  2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>
43 * @license    http://www.opensource.org/licenses/bsd-license.php  BSD License
44 * @version    CVS: $Id: Skeleton.php,v 1.8 2005/11/10 09:47:14 sebastian Exp $
45 * @link       http://pear.php.net/package/PHPUnit
46 * @since      File available since Release 1.1.0
47 */
48
49/**
50 * Class for creating a PHPUnit_TestCase skeleton file.
51 *
52 * This class will take a classname as a parameter on construction and will
53 * create a PHP file that contains the skeleton of a PHPUnit_TestCase
54 * subclass. The test case will contain a test foreach method of the class.
55 * Methods of the parent class will, by default, be excluded from the test
56 * class. Passing and optional construction parameter will include them.
57 *
58 * Example
59 *
60 *   <?php
61 *   require_once 'PHPUnit/Skeleton.php';
62 *   $ps = new PHPUnit_Skeleton('PHPUnit_Skeleton', 'PHPUnit/Skeleton.php');
63 *
64 *   // Generate the test class.
65 *   // Default settings will not include any parent class methods, but
66 *   // will include private methods.
67 *   $ps->createTestClass();
68 *
69 *   // Write the new test class to file.
70 *   // By default, code to run the test will be included.
71 *   $ps->writeTestClass();
72 *   ?>
73 *
74 * Now open the skeleton class and fill in the details.
75 * If you run the test as is, all tests will fail and
76 * you will see plenty of undefined constant errors.
77 *
78 * @category   Testing
79 * @package    PHPUnit
80 * @author     Scott Mattocks <scott@crisscott.com>
81 * @copyright  2002-2005 Sebastian Bergmann <sb@sebastian-bergmann.de>
82 * @license    http://www.opensource.org/licenses/bsd-license.php  BSD License
83 * @version    Release: 1.3.2
84 * @link       http://pear.php.net/package/PHPUnit
85 * @since      Class available since Release 1.1.0
86 */
87class PHPUnit_Skeleton {
88    /**
89     * Path to the class file to create a skeleton for.
90     * @var string
91     */
92    var $classPath;
93
94    /**
95     * The name of the class
96     * @var string
97     */
98    var $className;
99
100    /**
101     * Path to the configuration file needed by class to test.
102     * @var string
103     */
104    var $configFile;
105
106    /**
107     * Whether or not to include the methods of the parent class when testing.
108     * @var boolean
109     */
110    var $includeParents;
111
112    /**
113     * Whether or not to test private methods.
114     * @var boolean
115     */
116    var $includePrivate;
117
118    /**
119     * The test class that will be created.
120     * @var string
121     */
122    var $testClass;
123
124    /**
125     * Constructor. Sets the class members and check that the class
126     * to test is accessible.
127     *
128     * @access public
129     * @param  string  $className
130     * @param  string  $classPath
131     * @param  boolean $includeParents Wheter to include the parent's methods in the test.
132     * @return void
133     */
134    function PHPUnit_Skeleton($className, $classPath, $includeParents = FALSE, $includePrivate = TRUE) {
135        // Set up the members.
136        if (@is_readable($classPath)) {
137            $this->className = $className;
138            $this->classPath = $classPath;
139        } else {
140            $this->_handleErrors($classPath . ' is not readable. Cannot create test class.');
141        }
142
143        // Do we want to include parent methods?
144        $this->includeParents = $includeParents;
145
146        // Do we want to allow private methods?
147        $this->includePrivate = $includePrivate;
148    }
149
150    /**
151     * The class to test may require a special config file before it can be
152     * instantiated. This method lets you set that file.
153     *
154     * @access public
155     * @param  string $configPath
156     * @return void
157     */
158    function setConfigFile($configFile) {
159        // Check that the file is readable
160        if (@is_readable($configFile)) {
161            $this->configFile = $configFile;
162        } else {
163            $this->_handleErrors($configFile . ' is not readable. Cannot create test class.');
164        }
165    }
166
167    /**
168     * Create the code that will be the skeleton of the test case.
169     *
170     * The test case must have a clss definition, one var, a constructor
171     * setUp, tearDown, and methods. Optionally and by default the code
172     * to run the test is added when the class is written to file.
173     *
174     * @access public
175     * @param  none
176     * @return void
177     */
178    function createTestClass() {
179        // Instantiate the object.
180        if (isset($this->configFile)) {
181            require_once $this->configFile;
182        }
183
184        require_once $this->classPath;
185
186        // Get the methods.
187        $classMethods = get_class_methods($this->className);
188
189        // Remove the parent methods if needed.
190        if (!$this->includeParents) {
191            $parentMethods = get_class_methods(get_parent_class($this->className));
192
193            if (count($parentMethods)) {
194                $classMethods = array_diff($classMethods, $parentMethods);
195            }
196        }
197
198        // Create the class definition, constructor, setUp and tearDown.
199        $this->_createDefinition();
200        $this->_createConstructor();
201        $this->_createSetUpTearDown();
202
203        if (count($classMethods)) {
204            // Foreach method create a test case.
205            foreach ($classMethods as $method) {
206                // Unless it is the constructor.
207                if (strcasecmp($this->className, $method) !== 0) {
208                  // Check for private methods.
209                  if (!$this->includePrivate && strpos($method, '_') === 0) {
210                      continue;
211                  } else {
212                      $this->_createMethod($method);
213                  }
214                }
215            }
216        }
217
218        // Finis off the class.
219        $this->_finishClass();
220    }
221
222    /**
223     * Create the class definition.
224     *
225     * The definition consist of a header comment, require statment
226     * for getting the PHPUnit file, the actual class definition,
227     * and the definition of the class member variable.
228     *
229     * All of the code needed for the new class is stored in the
230     * testClass member.
231     *
232     * @access private
233     * @param  none
234     * @return void
235     */
236    function _createDefinition() {
237        // Create header comment.
238        $this->testClass =
239          "/**\n" .
240          " * PHPUnit test case for " . $this->className . "\n" .
241          " * \n" .
242          " * The method skeletons below need to be filled in with \n" .
243          " * real data so that the tests will run correctly. Replace \n" .
244          " * all EXPECTED_VAL and PARAM strings with real data. \n" .
245          " * \n" .
246          " * Created with PHPUnit_Skeleton on " . date('Y-m-d') . "\n" .
247          " */\n";
248
249        // Add the require statements.
250        $this->testClass .= "require_once 'PHPUnit.php';\n";
251
252        // Add the class definition and variable definition.
253        $this->testClass .=
254          "class " . $this->className . "Test extends PHPUnit_TestCase {\n\n" .
255          "    var \$" . $this->className . ";\n\n";
256    }
257
258    /**
259     * Create the class constructor. (PHP4 style)
260     *
261     * The constructor simply calls the PHPUnit_TestCase method.
262     * This code is taken from the PHPUnit documentation.
263     *
264     * All of the code needed for the new class is stored in the
265     * testClass member.
266     *
267     * @access private
268     * @param  none
269     * @return void
270     */
271    function _createConstructor() {
272        // Create the test class constructor.
273        $this->testClass.=
274          "    function " . $this->className . "Test(\$name)\n" .
275          "    {\n" .
276          "        \$this->PHPUnit_TestCase(\$name);\n" .
277          "    }\n\n";
278    }
279
280    /**
281     * Create setUp and tearDown methods.
282     *
283     * The setUp method creates the instance of the object to test.
284     * The tearDown method releases the instance.
285     * This code is taken from the PHPUnit documentation.
286     *
287     * All of the code needed for the new class is stored in the
288     * testClass member.
289     *
290     * @access private
291     * @param  none
292     * @return void
293     */
294    function _createSetUpTearDown() {
295        // Create the setUp method.
296        $this->testClass .=
297          "    function setUp()\n" .
298          "    {\n";
299
300        if (isset($this->configFile)) {
301            $this->testClass .=
302            "        require_once '" . $this->configFile . "';\n";
303        }
304
305        $this->testClass .=
306          "        require_once '" . $this->classPath . "';\n" .
307          "        \$this->" . $this->className . " =& new " . $this->className . "(PARAM);\n" .
308          "    }\n\n";
309
310        // Create the tearDown method.
311        $this->testClass .=
312          "    function tearDown()\n" .
313          "    {\n" .
314          "        unset(\$this->" . $this->className . ");\n" .
315          "    }\n\n";
316    }
317
318    /**
319     * Create a basic skeleton for test methods.
320     *
321     * This code is taken from the PHPUnit documentation.
322     *
323     * All of the code needed for the new class is stored in the
324     * testClass member.
325     *
326     * @access private
327     * @param  none
328     * @return void
329     */
330    function _createMethod($methodName) {
331        // Create a test method.
332        $this->testClass .=
333          "    function test" . $methodName . "()\n" .
334          "    {\n" .
335          "        \$result   = \$this->" . $this->className . "->" . $methodName . "(PARAM);\n" .
336          "        \$expected = EXPECTED_VAL;\n" .
337          "        \$this->assertEquals(\$expected, \$result);\n" .
338          "    }\n\n";
339    }
340
341    /**
342     * Add the closing brace needed for a proper class definition.
343     *
344     * All of the code needed for the new class is stored in the
345     * testClass member.
346     *
347     * @access private
348     * @param  none
349     * @return void
350     */
351    function _finishClass() {
352        // Close off the class.
353        $this->testClass.= "}\n";
354    }
355
356    /**
357     * Create the code that will actually run the test.
358     *
359     * This code is added by default so that the test can be run
360     * just by running the file. To have it not added pass false
361     * as the second parameter to the writeTestClass method.
362     * This code is taken from the PHPUnit documentation.
363     *
364     * All of the code needed for the new class is stored in the
365     * testClass member.
366     *
367     * @access private
368     * @param  none
369     * @return void
370     */
371    function _createTest() {
372        // Create a call to the test.
373        $test =
374          "// Running the test.\n" .
375          "\$suite  = new PHPUnit_TestSuite('" . $this->className . "Test');\n" .
376          "\$result = PHPUnit::run(\$suite);\n" .
377          "echo \$result->toString();\n";
378
379        return $test;
380    }
381
382    /**
383     * Write the test class to file.
384     *
385     * This will write the test class created using the createTestClass
386     * method to a file called <className>Test.php. By default the file
387     * is written to the current directory and will have code to run
388     * the test appended to the bottom of the file.
389     *
390     * @access public
391     * @param  string  $destination The directory to write the file to.
392     * @param  boolean $addTest     Wheter to add the test running code.
393     * @return void
394     */
395    function writeTestClass($destination = './', $addTest = TRUE) {
396        // Check for something to write to file.
397        if (!isset($this->testClass)) {
398            $this->_handleErrors('Noting to write.', PHPUS_WARNING);
399            return;
400        }
401
402        // Open the destination file.
403        $fp = fopen($destination . $this->className . 'Test.php', 'w');
404        fwrite($fp, "<?php\n");
405
406        // Write the test class.
407        fwrite($fp, $this->testClass);
408
409        // Add the call to test the class in the file if we were asked to.
410        if ($addTest) {
411            fwrite($fp, $this->_createTest());
412        }
413
414        // Close the file.
415        fwrite($fp, "?>\n");
416        fclose($fp);
417    }
418
419    /**
420     * Error handler.
421     *
422     * This method should be rewritten to use the prefered error
423     * handling method. (PEAR_ErrorStack)
424     *
425     * @access private
426     * @param  string  $message The error message.
427     * @param  integer $type    An indication of the severity of the error.
428     * @return void             Code may cause PHP to exit.
429     */
430    function _handleErrors($message, $type = E_USER_ERROR) {
431        // For now just echo the message.
432        echo $message;
433
434        // Check to see if we should quit.
435        if ($type == E_USER_ERROR) {
436            exit;
437        }
438    }
439}
440
441/*
442 * Local variables:
443 * tab-width: 4
444 * c-basic-offset: 4
445 * c-hanging-comment-ender-p: nil
446 * End:
447 */
448?>
449