3 // PHP framework for testing, based on the design of "JUnit".
5 // Written by Fred Yankowski <fred@ontosys.com>
6 // OntoSys, Inc <http://www.OntoSys.com>
8 // $Id: phpunit.php,v 1.1 2002/03/30 19:32:17 bmatzelle Exp $
10 // Copyright (c) 2000 Fred Yankowski
12 // Permission is hereby granted, free of charge, to any person
13 // obtaining a copy of this software and associated documentation
14 // files (the "Software"), to deal in the Software without
15 // restriction, including without limitation the rights to use, copy,
16 // modify, merge, publish, distribute, sublicense, and/or sell copies
17 // of the Software, and to permit persons to whom the Software is
18 // furnished to do so, subject to the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
27 // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
28 // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
29 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32 error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE |
33 E_CORE_ERROR | E_CORE_WARNING
);
37 function run(&$aTestResult);
38 function countTestCases();
42 function trace($msg) {
50 /* Emulate a Java exception, sort of... */
52 function Exception($message) {
53 $this->message
= $message;
55 function getMessage() {
56 return $this->message
;
61 function assert($boolean, $message=0) {
63 $this->fail($message);
66 function assertEquals($expected, $actual, $message=0) {
67 if ($expected != $actual) {
68 $this->failNotEquals($expected, $actual, "expected", $message);
72 function assertRegexp($regexp, $actual, $message=false) {
73 if (! preg_match($regexp, $actual)) {
74 $this->failNotEquals($regexp, $actual, "pattern", $message);
78 function failNotEquals($expected, $actual, $expected_label, $message=0) {
79 // Private function for reporting failure to match.
80 $str = $message ?
($message . ' ') : '';
81 $str .= "($expected_label/actual)<br>";
82 $htmlExpected = htmlspecialchars($expected);
83 $htmlActual = htmlspecialchars($actual);
84 $str .= sprintf("<pre>%s\n--------\n%s</pre>",
85 $htmlExpected, $htmlActual);
90 class TestCase
extends Assert
/* implements Test */ {
91 /* Defines context for running tests. Specific context -- such as
92 instance variables, global variables, global state -- is defined
93 by creating a subclass that specializes the setUp() and
94 tearDown() methods. A specific test is defined by a subclass
95 that specializes the runTest() method. */
98 var $fExceptions = array();
100 function TestCase($name) {
101 $this->fName
= $name;
104 function run($testResult=0) {
105 /* Run this single test, by calling the run() method of the
106 TestResult object which will in turn call the runBare() method
107 of this object. That complication allows the TestResult object
108 to do various kinds of progress reporting as it invokes each
109 test. Create/obtain a TestResult object if none was passed in.
110 Note that if a TestResult object was passed in, it must be by
113 $testResult = $this->_createResult();
114 $this->fResult
= $testResult;
115 $testResult->run(&$this);
120 function countTestCases() {
125 $name = $this->name();
126 // Since isset($this->$name) is false, no way to run defensive checks
130 function setUp() /* expect override */ {
131 //print("TestCase::setUp()<br>\n");
134 function tearDown() /* possible override */ {
135 //print("TestCase::tearDown()<br>\n");
138 ////////////////////////////////////////////////////////////////
141 function _createResult() /* protected */ {
142 /* override this to use specialized subclass of TestResult */
143 return new TestResult
;
146 function fail($message=0) {
147 //printf("TestCase::fail(%s)<br>\n", ($message) ? $message : '');
148 /* JUnit throws AssertionFailedError here. We just record the
149 failure and carry on */
150 $this->fExceptions
[] = new Exception(&$message);
153 function error($message) {
154 /* report error that requires correction in the test script
155 itself, or (heaven forbid) in this testing infrastructure */
156 printf('<b>ERROR: ' . $message . '</b><br>');
157 $this->fResult
->stop();
161 return count($this->fExceptions
);
164 function getExceptions() {
165 return $this->fExceptions
;
180 class TestSuite
/* implements Test */ {
181 /* Compose a set of Tests (instances of TestCase or TestSuite), and
183 var $fTests = array();
185 function TestSuite($classname=false) {
187 // Find all methods of the given class whose name starts with
188 // "test" and add them to the test suite. We are just _barely_
189 // able to do this with PHP's limited introspection... Note
190 // that PHP seems to store method names in lower case, and we
191 // have to avoid the constructor function for the TestCase class
192 // superclass. This will fail when $classname starts with
193 // "Test" since that will have a constructor method that will
194 // get matched below and then treated (incorrectly) as a test
195 // method. So don't name any TestCase subclasses as "Test..."!
196 if (floor(phpversion()) >= 4) {
197 // PHP4 introspection, submitted by Dylan Kuhn
198 $names = get_class_methods($classname);
199 while (list($key, $method) = each($names)) {
200 if (preg_match('/^test/', $method) && $method != "testcase") {
201 $this->addTest(new $classname($method));
206 $dummy = new $classname("dummy");
207 $names = (array) $dummy;
208 while (list($key, $value) = each($names)) {
209 $type = gettype($value);
210 if ($type == "user function" && preg_match('/^test/', $key)
211 && $key != "testcase") {
212 $this->addTest(new $classname($key));
219 function addTest($test) {
220 /* Add TestCase or TestSuite to this TestSuite */
221 $this->fTests
[] = $test;
224 function run(&$testResult) {
225 /* Run all TestCases and TestSuites comprising this TestSuite,
226 accumulating results in the given TestResult object. */
227 reset($this->fTests
);
228 while (list($na, $test) = each($this->fTests
)) {
229 if ($testResult->shouldStop())
231 $test->run(&$testResult);
235 function countTestCases() {
236 /* Number of TestCases comprising this TestSuite (including those
237 in any constituent TestSuites) */
240 while (list($na, $test_case) = each($this->fTests
)) {
241 $count +
= $test_case->countTestCases();
249 /* Record failure of a single TestCase, associating it with the
250 exception(s) that occurred */
251 var $fFailedTestName;
254 function TestFailure(&$test, &$exceptions) {
255 $this->fFailedTestName
= $test->name();
256 $this->fExceptions
= $exceptions;
259 function getExceptions() {
260 return $this->fExceptions
;
262 function getTestName() {
263 return $this->fFailedTestName
;
269 /* Collect the results of running a set of TestCases. */
270 var $fFailures = array();
274 function TestResult() { }
276 function _endTest($test) /* protected */ {
277 /* specialize this for end-of-test action, such as progress
281 function getFailures() {
282 return $this->fFailures
;
285 function run($test) {
286 /* Run a single TestCase in the context of this TestResult */
287 $this->_startTest($test);
292 /* this is where JUnit would catch AssertionFailedError */
293 $exceptions = $test->getExceptions();
295 $this->fFailures
[] = new TestFailure(&$test, &$exceptions);
296 $this->_endTest($test);
299 function countTests() {
300 return $this->fRunTests
;
303 function shouldStop() {
307 function _startTest($test) /* protected */ {
308 /* specialize this for start-of-test actions */
312 /* set indication that the test sequence should halt */
316 function countFailures() {
317 return count($this->fFailures
);
322 class TextTestResult
extends TestResult
{
323 /* Specialize TestResult to produce text/html report */
324 function TextTestResult() {
325 $this->TestResult(); // call superclass constructor
329 /* report result of test run */
330 $nRun = $this->countTests();
331 $nFailures = $this->countFailures();
332 printf("<p>%s test%s run<br>", $nRun, ($nRun == 1) ?
'' : 's');
333 printf("%s failure%s.<br>\n", $nFailures, ($nFailures == 1) ?
'' : 's');
338 $failures = $this->getFailures();
339 while (list($i, $failure) = each($failures)) {
340 $failedTestName = $failure->getTestName();
341 printf("<li>%s\n", $failedTestName);
343 $exceptions = $failure->getExceptions();
345 while (list($na, $exception) = each($exceptions))
346 printf("<li>%s\n", $exception->getMessage());
352 function _startTest($test) {
353 printf("%s ", $test->name());
357 function _endTest($test) {
358 $outcome = $test->failed()
359 ?
"<font color=\"red\">FAIL</font>"
360 : "<font color=\"green\">ok</font>";
361 printf("$outcome<br>\n");
368 /* Run a suite of tests and report results. */
369 function run($suite) {
370 $result = new TextTestResult
;
371 $suite->run($result);