3 // PHP framework for testing, based on the design of "JUnit".
\r
5 // Written by Fred Yankowski <fred@ontosys.com>
\r
6 // OntoSys, Inc <http://www.OntoSys.com>
\r
10 // Copyright (c) 2000 Fred Yankowski
\r
12 // Permission is hereby granted, free of charge, to any person
\r
13 // obtaining a copy of this software and associated documentation
\r
14 // files (the "Software"), to deal in the Software without
\r
15 // restriction, including without limitation the rights to use, copy,
\r
16 // modify, merge, publish, distribute, sublicense, and/or sell copies
\r
17 // of the Software, and to permit persons to whom the Software is
\r
18 // furnished to do so, subject to the following conditions:
\r
20 // The above copyright notice and this permission notice shall be
\r
21 // included in all copies or substantial portions of the Software.
\r
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
\r
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
\r
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
\r
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
\r
27 // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
\r
28 // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
\r
29 // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
\r
32 error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE |
\r
33 E_CORE_ERROR | E_CORE_WARNING);
\r
37 function run(&$aTestResult);
\r
38 function countTestCases();
\r
42 function trace($msg) {
\r
49 class phpUnitException {
\r
50 /* Emulate a Java exception, sort of... */
\r
53 function phpUnitException($message, $type = 'FAILURE') {
\r
54 $this->message = $message;
\r
55 $this->type = $type;
\r
57 function getMessage() {
\r
58 return $this->message;
\r
63 function assert($boolean, $message=0) {
\r
65 $this->fail($message);
\r
68 function assertEquals($expected, $actual, $message=0) {
\r
69 if ($expected != $actual) {
\r
70 $this->failNotEquals($expected, $actual, "expected", $message);
\r
74 function assertRegexp($regexp, $actual, $message=false) {
\r
75 if (! preg_match($regexp, $actual)) {
\r
76 $this->failNotEquals($regexp, $actual, "pattern", $message);
\r
80 function failNotEquals($expected, $actual, $expected_label, $message=0) {
\r
81 // Private function for reporting failure to match.
\r
82 $str = $message ? ($message . ' ') : '';
\r
83 $str .= "($expected_label/actual)<br>";
\r
84 $htmlExpected = htmlspecialchars($expected);
\r
85 $htmlActual = htmlspecialchars($actual);
\r
86 $str .= sprintf("<pre>%s\n--------\n%s</pre>",
\r
87 $htmlExpected, $htmlActual);
\r
92 class TestCase extends Assert /* implements Test */ {
\r
93 /* Defines context for running tests. Specific context -- such as
\r
94 instance variables, global variables, global state -- is defined
\r
95 by creating a subclass that specializes the setUp() and
\r
96 tearDown() methods. A specific test is defined by a subclass
\r
97 that specializes the runTest() method. */
\r
100 var $fExceptions = array();
\r
102 function TestCase($name) {
\r
103 $this->fName = $name;
\r
106 function run($testResult=0) {
\r
107 /* Run this single test, by calling the run() method of the
\r
108 TestResult object which will in turn call the runBare() method
\r
109 of this object. That complication allows the TestResult object
\r
110 to do various kinds of progress reporting as it invokes each
\r
111 test. Create/obtain a TestResult object if none was passed in.
\r
112 Note that if a TestResult object was passed in, it must be by
\r
115 $testResult = $this->_createResult();
\r
116 $this->fResult = $testResult;
\r
117 $testResult->run(&$this);
\r
118 $this->fResult = 0;
\r
119 return $testResult;
\r
122 function countTestCases() {
\r
126 function runTest() {
\r
127 $name = $this->name();
\r
128 // Since isset($this->$name) is false, no way to run defensive checks
\r
132 function setUp() /* expect override */ {
\r
133 //print("TestCase::setUp()<br>\n");
\r
136 function tearDown() /* possible override */ {
\r
137 //print("TestCase::tearDown()<br>\n");
\r
140 ////////////////////////////////////////////////////////////////
\r
143 function _createResult() /* protected */ {
\r
144 /* override this to use specialized subclass of TestResult */
\r
145 return new TestResult;
\r
148 function fail($message=0) {
\r
149 //printf("TestCase::fail(%s)<br>\n", ($message) ? $message : '');
\r
150 /* JUnit throws AssertionFailedError here. We just record the
\r
151 failure and carry on */
\r
152 $this->fExceptions[] = new Exception(&$message);
\r
155 function error($message) {
\r
156 /* report error that requires correction in the test script
\r
157 itself, or (heaven forbid) in this testing infrastructure */
\r
158 printf('<b>ERROR: ' . $message . '</b><br>');
\r
159 $this->fResult->stop();
\r
162 function failed() {
\r
163 return count($this->fExceptions);
\r
166 function getExceptions() {
\r
167 return $this->fExceptions;
\r
171 return $this->fName;
\r
174 function runBare() {
\r
182 class TestSuite /* implements Test */ {
\r
183 /* Compose a set of Tests (instances of TestCase or TestSuite), and
\r
185 var $fTests = array();
\r
187 function TestSuite($classname=false) {
\r
189 // Find all methods of the given class whose name starts with
\r
190 // "test" and add them to the test suite. We are just _barely_
\r
191 // able to do this with PHP's limited introspection... Note
\r
192 // that PHP seems to store method names in lower case, and we
\r
193 // have to avoid the constructor function for the TestCase class
\r
194 // superclass. This will fail when $classname starts with
\r
195 // "Test" since that will have a constructor method that will
\r
196 // get matched below and then treated (incorrectly) as a test
\r
197 // method. So don't name any TestCase subclasses as "Test..."!
\r
198 if (floor(phpversion()) >= 4) {
\r
199 // PHP4 introspection, submitted by Dylan Kuhn
\r
200 $names = get_class_methods($classname);
\r
201 while (list($key, $method) = each($names)) {
\r
202 if (preg_match('/^test/', $method) && $method != "testcase") {
\r
203 $this->addTest(new $classname($method));
\r
208 $dummy = new $classname("dummy");
\r
209 $names = (array) $dummy;
\r
210 while (list($key, $value) = each($names)) {
\r
211 $type = gettype($value);
\r
212 if ($type == "user function" && preg_match('/^test/', $key)
\r
213 && $key != "testcase") {
\r
214 $this->addTest(new $classname($key));
\r
221 function addTest($test) {
\r
222 /* Add TestCase or TestSuite to this TestSuite */
\r
223 $this->fTests[] = $test;
\r
226 function run(&$testResult) {
\r
227 /* Run all TestCases and TestSuites comprising this TestSuite,
\r
228 accumulating results in the given TestResult object. */
\r
229 reset($this->fTests);
\r
230 while (list($na, $test) = each($this->fTests)) {
\r
231 if ($testResult->shouldStop())
\r
233 $test->run(&$testResult);
\r
237 function countTestCases() {
\r
238 /* Number of TestCases comprising this TestSuite (including those
\r
239 in any constituent TestSuites) */
\r
242 while (list($na, $test_case) = each($this->fTests)) {
\r
243 $count += $test_case->countTestCases();
\r
250 class TestFailure {
\r
251 /* Record failure of a single TestCase, associating it with the
\r
252 exception(s) that occurred */
\r
253 var $fFailedTestName;
\r
256 function TestFailure(&$test, &$exceptions) {
\r
257 $this->fFailedTestName = $test->name();
\r
258 $this->fExceptions = $exceptions;
\r
261 function getExceptions() {
\r
262 return $this->fExceptions;
\r
264 function getTestName() {
\r
265 return $this->fFailedTestName;
\r
271 /* Collect the results of running a set of TestCases. */
\r
272 var $fFailures = array();
\r
273 var $fRunTests = 0;
\r
274 var $fStop = false;
\r
276 function TestResult() { }
\r
278 function _endTest($test) /* protected */ {
\r
279 /* specialize this for end-of-test action, such as progress
\r
283 function getFailures() {
\r
284 return $this->fFailures;
\r
287 function run($test) {
\r
288 /* Run a single TestCase in the context of this TestResult */
\r
289 $this->_startTest($test);
\r
290 $this->fRunTests++;
\r
294 /* this is where JUnit would catch AssertionFailedError */
\r
295 $exceptions = $test->getExceptions();
\r
297 $this->fFailures[] = new TestFailure(&$test, &$exceptions);
\r
298 $this->_endTest($test);
\r
301 function countTests() {
\r
302 return $this->fRunTests;
\r
305 function shouldStop() {
\r
306 return $this->fStop;
\r
309 function _startTest($test) /* protected */ {
\r
310 /* specialize this for start-of-test actions */
\r
314 /* set indication that the test sequence should halt */
\r
318 function countFailures() {
\r
319 return count($this->fFailures);
\r
324 class TextTestResult extends TestResult {
\r
325 /* Specialize TestResult to produce text/html report */
\r
326 function TextTestResult() {
\r
327 $this->TestResult(); // call superclass constructor
\r
330 function report() {
\r
331 /* report result of test run */
\r
332 $nRun = $this->countTests();
\r
333 $nFailures = $this->countFailures();
\r
334 printf("<p>%s test%s run<br>", $nRun, ($nRun == 1) ? '' : 's');
\r
335 printf("%s failure%s.<br>\n", $nFailures, ($nFailures == 1) ? '' : 's');
\r
336 if ($nFailures == 0)
\r
340 $failures = $this->getFailures();
\r
341 while (list($i, $failure) = each($failures)) {
\r
342 $failedTestName = $failure->getTestName();
\r
343 printf("<li>%s\n", $failedTestName);
\r
345 $exceptions = $failure->getExceptions();
\r
347 while (list($na, $exception) = each($exceptions))
\r
348 printf("<li>%s\n", $exception->getMessage());
\r
354 function _startTest($test) {
\r
355 printf("%s ", $test->name());
\r
359 function _endTest($test) {
\r
360 $outcome = $test->failed()
\r
361 ? "<font color=\"red\">FAIL</font>"
\r
362 : "<font color=\"green\">ok</font>";
\r
363 printf("$outcome<br>\n");
\r
370 /* Run a suite of tests and report results. */
\r
371 function run($suite) {
\r
372 $result = new TextTestResult;
\r
373 $suite->run($result);
\r