File: 1.04.2a/server/test/phpunit.php (View as Code)

1: 2: // 3: // PHP framework for testing, based on the design of "JUnit". 4: // 5: // Written by Fred Yankowski 6: // OntoSys, Inc 7: // 8: // $Id: phpunit.php,v 1.1 2002/03/30 19:32:17 bmatzelle Exp $ 9: 10: // Copyright (c) 2000 Fred Yankowski 11: 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: 19: // 20: // The above copyright notice and this permission notice shall be 21: // included in all copies or substantial portions of the Software. 22: // 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 30: // SOFTWARE. 31: // 32: error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE | 33: E_CORE_ERROR | E_CORE_WARNING); 34: 35: /* 36: interface Test { 37: function run(&$aTestResult); 38: function countTestCases(); 39: } 40: */ 41: 42: function trace($msg) { 43: return; 44: print($msg); 45: flush(); 46: } 47: 48: 49: class Exception { 50: /* Emulate a Java exception, sort of... */ 51: var $message; 52: function Exception($message) { 53: $this->message = $message; 54: } 55: function getMessage() { 56: return $this->message; 57: } 58: } 59: 60: class Assert { 61: function assert($boolean, $message=0) { 62: if (! $boolean) 63: $this->fail($message); 64: } 65: 66: function assertEquals($expected, $actual, $message=0) { 67: if ($expected != $actual) { 68: $this->failNotEquals($expected, $actual, "expected", $message); 69: } 70: } 71: 72: function assertRegexp($regexp, $actual, $message=false) { 73: if (! preg_match($regexp, $actual)) { 74: $this->failNotEquals($regexp, $actual, "pattern", $message); 75: } 76: } 77: 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)
";
82: $htmlExpected = htmlspecialchars($expected); 83: $htmlActual = htmlspecialchars($actual); 84: $str .= sprintf("
%s\n--------\n%s
",
85: $htmlExpected, $htmlActual); 86: $this->fail($str); 87: } 88: } 89: 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. */ 96: var $fName; 97: var $fResult; 98: var $fExceptions = array(); 99: 100: function TestCase($name) { 101: $this->fName = $name; 102: } 103: 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 111: reference. */ 112: if (! $testResult) 113: $testResult = $this->_createResult(); 114: $this->fResult = $testResult; 115: $testResult->run(&$this); 116: $this->fResult = 0; 117: return $testResult; 118: } 119: 120: function countTestCases() { 121: return 1; 122: } 123: 124: function runTest() { 125: $name = $this->name(); 126: // Since isset($this->$name) is false, no way to run defensive checks 127: $this->$name(); 128: } 129: 130: function setUp() /* expect override */ { 131: //print("TestCase::setUp()
\n");
132: } 133: 134: function tearDown() /* possible override */ { 135: //print("TestCase::tearDown()
\n");
136: } 137: 138: //////////////////////////////////////////////////////////////// 139: 140: 141: function _createResult() /* protected */ { 142: /* override this to use specialized subclass of TestResult */ 143: return new TestResult; 144: } 145: 146: function fail($message=0) { 147: //printf("TestCase::fail(%s)
\n", ($message) ? $message : '');
148: /* JUnit throws AssertionFailedError here. We just record the 149: failure and carry on */ 150: $this->fExceptions[] = new Exception(&$message); 151: } 152: 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('ERROR: ' . $message . '
');
157: $this->fResult->stop(); 158: } 159: 160: function failed() { 161: return count($this->fExceptions); 162: } 163: 164: function getExceptions() { 165: return $this->fExceptions; 166: } 167: 168: function name() { 169: return $this->fName; 170: } 171: 172: function runBare() { 173: $this->setup(); 174: $this->runTest(); 175: $this->tearDown(); 176: } 177: } 178: 179: 180: class TestSuite /* implements Test */ { 181: /* Compose a set of Tests (instances of TestCase or TestSuite), and 182: run them all. */ 183: var $fTests = array(); 184: 185: function TestSuite($classname=false) { 186: if ($classname) { 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)); 202: } 203: } 204: } 205: else { 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)); 213: } 214: } 215: } 216: } 217: } 218: 219: function addTest($test) { 220: /* Add TestCase or TestSuite to this TestSuite */ 221: $this->fTests[] = $test; 222: } 223: 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()) 230: break; 231: $test->run(&$testResult); 232: } 233: } 234: 235: function countTestCases() { 236: /* Number of TestCases comprising this TestSuite (including those 237: in any constituent TestSuites) */ 238: $count = 0; 239: reset($fTests); 240: while (list($na, $test_case) = each($this->fTests)) { 241: $count += $test_case->countTestCases(); 242: } 243: return $count; 244: } 245: } 246: 247: 248: class TestFailure { 249: /* Record failure of a single TestCase, associating it with the 250: exception(s) that occurred */ 251: var $fFailedTestName; 252: var $fExceptions; 253: 254: function TestFailure(&$test, &$exceptions) { 255: $this->fFailedTestName = $test->name(); 256: $this->fExceptions = $exceptions; 257: } 258: 259: function getExceptions() { 260: return $this->fExceptions; 261: } 262: function getTestName() { 263: return $this->fFailedTestName; 264: } 265: } 266: 267: 268: class TestResult { 269: /* Collect the results of running a set of TestCases. */ 270: var $fFailures = array(); 271: var $fRunTests = 0; 272: var $fStop = false; 273: 274: function TestResult() { } 275: 276: function _endTest($test) /* protected */ { 277: /* specialize this for end-of-test action, such as progress 278: reports */ 279: } 280: 281: function getFailures() { 282: return $this->fFailures; 283: } 284: 285: function run($test) { 286: /* Run a single TestCase in the context of this TestResult */ 287: $this->_startTest($test); 288: $this->fRunTests++; 289: 290: $test->runBare(); 291: 292: /* this is where JUnit would catch AssertionFailedError */ 293: $exceptions = $test->getExceptions(); 294: if ($exceptions) 295: $this->fFailures[] = new TestFailure(&$test, &$exceptions); 296: $this->_endTest($test); 297: } 298: 299: function countTests() { 300: return $this->fRunTests; 301: } 302: 303: function shouldStop() { 304: return $this->fStop; 305: } 306: 307: function _startTest($test) /* protected */ { 308: /* specialize this for start-of-test actions */ 309: } 310: 311: function stop() { 312: /* set indication that the test sequence should halt */ 313: $fStop = true; 314: } 315: 316: function countFailures() { 317: return count($this->fFailures); 318: } 319: } 320: 321: 322: class TextTestResult extends TestResult { 323: /* Specialize TestResult to produce text/html report */ 324: function TextTestResult() { 325: $this->TestResult(); // call superclass constructor 326: } 327: 328: function report() { 329: /* report result of test run */ 330: $nRun = $this->countTests(); 331: $nFailures = $this->countFailures(); 332: printf("

%s test%s run
", $nRun, ($nRun == 1) ? '' : 's'); 333: printf("%s failure%s.
\n", $nFailures, ($nFailures == 1) ? '' : 's');
334: if ($nFailures == 0) 335: return; 336: 337: print("

    \n"); 338: $failures = $this->getFailures(); 339: while (list($i, $failure) = each($failures)) { 340: $failedTestName = $failure->getTestName(); 341: printf("
  1. %s\n", $failedTestName); 342: 343: $exceptions = $failure->getExceptions(); 344: print("
      "); 345: while (list($na, $exception) = each($exceptions)) 346: printf("
    • %s\n", $exception->getMessage()); 347: print("
    ");
    348: } 349: print("
\n");
350: } 351: 352: function _startTest($test) { 353: printf("%s ", $test->name()); 354: flush(); 355: } 356: 357: function _endTest($test) { 358: $outcome = $test->failed() 359: ? "FAIL" 360: : "ok"; 361: printf("$outcome
\n");
362: flush(); 363: } 364: } 365: 366: 367: class TestRunner { 368: /* Run a suite of tests and report results. */ 369: function run($suite) { 370: $result = new TextTestResult; 371: $suite->run($result); 372: $result->report(); 373: } 374: } 375: 376: ?> 377: