Revision
2
Author
emsmith
Date
2006-08-09 07:26:37 -0700 (Wed, 09 Aug 2006)

Log Message

Initial commit of first draft of basic unit test core

Added Paths

Diff

Added: library/trunk/README.txt ( => )



Added: library/trunk/docs/TODO.txt
===================================================================
--- library/trunk/docs/TODO.txt	2006-08-07 13:45:57 UTC (rev 1)
+++ library/trunk/docs/TODO.txt	2006-08-09 14:26:37 UTC (rev 2)
@@ -0,0 +1,15 @@
+Test:
+
+1) Testing
+ a) timing/profiling
+ b) code coverage (xdebug)
+ c) runner script with setup options for reporting
+ d) line count/comment/stripping information
+ e) Reflection for grabbing assert statement? Invoking method?
+2) Reporting
+ a) html, xml, text formatting
+ b) echo, store as files (compressed or not), email (compressed or not)
+ c) profiling, code coverage, timing, line/comment count information
+
+Runner needs to - run via command line (shebang or windows assoc or bat or shortcut)
+and run via html (and tell the difference) - allow command line args, get or post args, php, ini, xml files or forms/command line questions for setup, needs to register any observers and run any/all suites requested...sigh.. and do any/all including on top of that
\ No newline at end of file

Added: library/trunk/lib/test/error.class.php (1 => 2)


--- library/trunk/lib/test/error.class.php	2006-08-07 13:45:57 UTC (rev 1)
+++ library/trunk/lib/test/error.class.php	2006-08-09 14:26:37 UTC (rev 2)
@@ -0,0 +1,46 @@
+<?php
+/**
+ * error.class.php - exception class to move php errors into exceptions
+ *
+ * wrapper for unit testing php error -> exception conversion
+ *
+ * This is released under the GPL, see license.txt for details
+ *
+ * @author       Elizabeth Smith <emsmith@callicore.net>
+ * @copyright    Elizabeth Smith (c)2006
+ * @link         http://callicore.net
+ * @license      http://www.opensource.org/licenses/gpl-license.php GPL
+ * @version      $Id$
+ * @since        Php 5.2.0
+ * @package      callicore library
+ * @subpackage   test
+ * @category     lib
+ * @filesource
+ */
+
+/**
+ * CCL_TestError - wrapper for php error -> exception conversion
+ */
+class CCL_TestError extends Exception
+{
+	/**
+	 * public function __construct
+	 *
+	 * throw php error information into an exception
+	 *
+	 * @param string $message error message
+	 * @param int $code error level
+	 * @param string $file error location
+	 * @param int $line error line number
+	 * @param array $trace backtrace for exception
+	 * @return void
+	 */
+	public function __construct($message, $code, $file, $line, $trace)
+	{
+		parent::__construct($message, $code);
+		$this->file = $file;
+		$this->line = $line;
+		$this->trace = $trace;
+	}
+}
+?>
\ No newline at end of file

Added: library/trunk/lib/test/result.class.php (1 => 2)


--- library/trunk/lib/test/result.class.php	2006-08-07 13:45:57 UTC (rev 1)
+++ library/trunk/lib/test/result.class.php	2006-08-09 14:26:37 UTC (rev 2)
@@ -0,0 +1,431 @@
+<?php
+/**
+ * result.class.php - class holding the final results for the test
+ *
+ * contains basic information about the results of the test
+ *
+ * This is released under the GPL, see license.txt for details
+ *
+ * @author       Elizabeth Smith <emsmith@callicore.net>
+ * @copyright    Elizabeth Smith (c)2006
+ * @link         http://callicore.net
+ * @license      http://www.opensource.org/licenses/gpl-license.php GPL
+ * @version      $Id$
+ * @since        Php 5.2.0
+ * @package      callicore library
+ * @subpackage   test
+ * @category     lib
+ * @filesource
+ */
+
+/**
+ * CCL_TestResult - result class for testing
+ *
+ * Handles report generation and consolidating test results
+ */
+class CCL_TestResult implements SplSubject, Countable
+{
+
+	/**
+	 * holding observers to call
+	 * @var $observers array of observers
+	 */
+	private $observers = array();
+
+	/**
+	 * last call name
+	 * @var $call string
+	 */
+	protected $call;
+
+	/**
+	 * argument stored from last call
+	 * @var $arg mixed
+	 */
+	protected $arg;
+
+	/**
+	 * count the number of test units shown
+	 * @var $units int
+	 */
+	protected $units = 0;
+
+	/**
+	 * total number of tests completed, for countable
+	 * @var $total int
+	 */
+	protected $total = 0;
+
+	/**
+	 * count the number of tests shown
+	 * @var $tests int
+	 */
+	protected $tests = 0;
+
+	/**
+	 * count the number of tests
+	 * @var $testpass int
+	 */
+	protected $testpass = 0;
+
+	/**
+	 * count the number of tests
+	 * @var $testsfail int
+	 */
+	protected $testsfail = 0;
+
+	/**
+	 * count the number of tests
+	 * @var $testsskip int
+	 */
+	protected $testsskip = 0;
+
+	/**
+	 * count the number of tests
+	 * @var $testserror int
+	 */
+	protected $testserror = 0;
+
+	/**
+	 * count the number of tests
+	 * @var $testsimcomplete int
+	 */
+	protected $testsimcomplete = 0;
+
+	/**
+	 * count the number of assertions
+	 * @var $asserts int
+	 */
+	protected $asserts = 0;
+
+	/**
+	 * count the number of assertions
+	 * @var $assertspass int
+	 */
+	protected $assertspass = 0;
+
+	/**
+	 * count the number of assertions
+	 * @var $assertsfail int
+	 */
+	protected $assertsfail = 0;
+
+
+	//----------------------------------------------------------------
+	//             Observer Handling
+	//----------------------------------------------------------------
+
+	/**
+	 * public function attach
+	 *
+	 * registers observer object
+	 *
+	 * @param object $observer object to add to observer stack
+	 * @return void
+	 */
+	public function attach(SplObserver $observer)
+	{
+		$this->observers[] = $observer;
+	}
+
+	/**
+	 * public function detach
+	 *
+	 * removes a specific observer
+	 *
+	 * @param object $observer object to remove to observer stack
+	 * @return void
+	 */
+	public function detach(SplObserver $observer)
+	{
+		$key = array_search($observer, $this->observers);
+		if($key !== FALSE && isset($this->observers[$key]))
+		{
+			unset($this->observers[$key]);
+		}
+	}
+
+	/**
+	 * public function notify
+	 *
+	 * notify all the observers that a change has been made
+	 *
+	 * @return void
+	 */
+	public function notify()
+	{
+		foreach ($this->observers as $object)
+		{
+			$object->update($this);
+		}
+	}
+
+	/**
+	 * public function count
+	 *
+	 * count the total number of tests
+	 *
+	 * @return int
+	 */
+	public function count()
+	{
+		return $this->total;
+	}
+
+	//----------------------------------------------------------------
+	//             testUnit Methods
+	//----------------------------------------------------------------
+
+	/**
+	 * public function startTestUnit
+	 *
+	 * signals the start of a testUnit
+	 *
+	 * @param string $name name of the testUnit running
+	 * @return void
+	 */
+	public function startTestUnit($name)
+	{
+		$this->call = 'startTestUnit';
+		$this->arg = $name;
+		$this->tests = 0;
+		$this->testspass = 0;
+		$this->testsfail = 0;
+		$this->testserror = 0;
+		$this->testsskip = 0;
+		$this->testsincomplete = 0;
+		$this->units++;
+		$this->notify();
+		return;
+	}
+
+	/**
+	 * public function endTestUnit
+	 *
+	 * signals a completed testUnit
+	 *
+	 * @return void
+	 */
+	public function endTestUnit()
+	{
+		$this->call = 'endTestUnit';
+		$this->arg = NULL;
+		$this->notify();
+		return;
+	}
+
+	//----------------------------------------------------------------
+	//             Test Methods
+	//----------------------------------------------------------------
+
+	/**
+	 * public function startTest
+	 *
+	 * signals the start of a test
+	 *
+	 * @param string $name name of the test
+	 * @return void
+	 */
+	public function startTest($name)
+	{
+		$this->call = 'startTest';
+		$this->arg = $name;
+		$this->asserts = 0;
+		$this->assertspass = 0;
+		$this->assertsfail = 0;
+		$this->tests++;
+		$this->total++;
+		$this->notify();
+		return;
+	}
+
+	/**
+	 * public function passTest
+	 *
+	 * signals that a test has passed
+	 *
+	 * @return void
+	 */
+	public function passTest()
+	{
+		$this->call = 'passTest';
+		$this->arg = NULL;
+		$this->testspass++;
+		$this->notify();
+		return;
+	}
+
+	/**
+	 * public function failTest
+	 *
+	 * signals that a test has failed
+	 *
+	 * @param string $exception an exception
+	 * @return void
+	 */
+	public function failTest($exception)
+	{
+		$this->call = 'failTest';
+		$this->arg = $exception;
+		$this->testsfail++;
+		$this->notify();
+		return;
+	}
+
+	/**
+	 * public function skipTest
+	 *
+	 * signals that a test has been skipped
+	 *
+	 * @param string $exception an exception
+	 * @return void
+	 */
+	public function skipTest($exception)
+	{
+		$this->call = 'skipTest';
+		$this->arg = $exception;
+		$this->testsskip++;
+		$this->notify();
+		return;
+	}
+
+	/**
+	 * public function errorTest
+	 *
+	 * signals that there has been an error in the test
+	 *
+	 * @param object $exception the exception thrown
+	 * @return void
+	 */
+	public function errorTest($exception)
+	{
+		$this->call = 'errorTest';
+		$this->arg = $exception;
+		$this->testserror++;
+		$this->notify();
+		return;
+	}
+
+	/**
+	 * public function incompleteTest
+	 *
+	 * signals that the test is incomplete
+	 *
+	 * @param object $exception the exception thrown
+	 * @return void
+	 */
+	public function incompleteTest($exception)
+	{
+		$this->call = 'incompleteTest';
+		$this->arg = $exception;
+		$this->testsincomplete++;
+		$this->notify();
+		return;
+	}
+
+	/**
+	 * public function endTest
+	 *
+	 * signals that the last test is complete
+	 *
+	 * @return void
+	 */
+	public function endTest()
+	{
+		$this->call = 'endTest';
+		$this->arg = NULL;
+		$this->notify();
+		return;
+	}
+
+	//----------------------------------------------------------------
+	//             Assert Methods
+	//----------------------------------------------------------------
+
+	/**
+	 * public function startAssert
+	 *
+	 * signals the start of an assert
+	 *
+	 * @param string $code code of the assert
+	 * @return void
+	 */
+	public function startAssert($code)
+	{
+		$this->call = 'startAssert';
+		$this->arg = $code;
+		$this->asserts++;
+		$this->notify();
+		return;
+	}
+
+	/**
+	 * public function passAssert
+	 *
+	 * signals that an assert has passed
+	 *
+	 * @return void
+	 */
+	public function passAssert()
+	{
+		$this->call = 'passAssert';
+		$this->arg = NULL;
+		$this->assertspass++;
+		$this->notify();
+		return;
+	}
+
+	/**
+	 * public function failAssert
+	 *
+	 * signals that an assert has failed
+	 *
+	 * @param object $exception the exception thrown
+	 * @return void
+	 */
+	public function failAssert($exception)
+	{
+		$this->call = 'failAssert';
+		$this->arg = $exception;
+		$this->assertsfail++;
+		$this->notify();
+		return;
+	}
+
+	/**
+	 * public function endAssert
+	 *
+	 * signals that an assert is complete
+	 *
+	 * @return void
+	 */
+	public function endAssert()
+	{
+		$this->call = 'endAssert';
+		$this->arg = NULL;
+		$this->notify();
+		return;
+	}
+
+	//----------------------------------------------------------------
+	//             All properties are read-only
+	//----------------------------------------------------------------
+
+	/**
+	 * public function __get
+	 *
+	 * make all properties read-only
+	 *
+	 * @param string $name name of property to grab
+	 * @return mixed
+	 */
+	public function __get($name)
+	{
+		if (isset($this->$name))
+		{
+			return $this->$name;
+		}
+		return;
+	}
+}
+?>
\ No newline at end of file

Added: library/trunk/lib/test/unit.abstract.php (1 => 2)


--- library/trunk/lib/test/unit.abstract.php	2006-08-07 13:45:57 UTC (rev 1)
+++ library/trunk/lib/test/unit.abstract.php	2006-08-09 14:26:37 UTC (rev 2)
@@ -0,0 +1,286 @@
+<?php
+/**
+ * unit.abstract.php - contains abstract class CCL_TestUnit
+ *
+ * extend this to create a single test unit - a group of tests usually for
+ * a single class
+ *
+ * This is released under the GPL, see license.txt for details
+ *
+ * @author       Elizabeth Smith <emsmith@callicore.net>
+ * @copyright    Elizabeth Smith (c)2006
+ * @link         http://callicore.net
+ * @license      http://www.opensource.org/licenses/gpl-license.php GPL
+ * @version      $Id$
+ * @since        Php 5.2.0
+ * @package      callicore library
+ * @subpackage   test
+ * @category     lib
+ * @filesource
+ */
+
+/**
+ * CCL_TestUnit - groups methods that start with test into a cohesive group
+ *
+ * Extend this class in order to create a test unit - each method starting with
+ * test (case sensitive) will be executed in isolation when run is called, you
+ * can run all the tests or run just a single test.  Results are passed to the object
+ * sent to the constructor which implements the ccl_testreporter interface
+ */
+abstract class CCL_TestUnit
+{
+
+	/**
+	 * a name for the testcase - if left blank by extending class use classname
+	 * @var $name string
+	 */
+	protected $name;
+
+	/**
+	 * result class to ping
+	 * @var $result object
+	 */
+	protected $result;
+
+	/**
+	 * test message - transient per test
+	 * @var $message string
+	 */
+	protected $message;
+
+	//----------------------------------------------------------------
+	//             Setup
+	//----------------------------------------------------------------
+
+	/**
+	 * public function __construct
+	 *
+	 * the constructor just sets up the testcase name
+	 *
+	 * @param object $reporter instanceof CC_Reporter subclass
+	 * @return void
+	 */
+	public function __construct()
+	{
+		if (is_null($this->name))
+		{
+			$this->name = get_class($this);
+		}
+		$this->result = new CCL_TestResult();
+		return;
+	}
+
+	/**
+	 * public function getResult
+	 *
+	 * returns the result class, only really useful for attaching observers
+	 *
+	 * @return object
+	 */
+	public function getResult()
+	{
+		return $this->result;
+	}
+
+	//----------------------------------------------------------------
+	//             Overrideable by Subclass
+	//----------------------------------------------------------------
+
+	/**
+	 * protected function listTests
+	 *
+	 * returns a list of all methods that begin with test
+	 * can be overridden if you want a different method of
+	 * naming tests
+	 *
+	 * @return array list of methods
+	 */
+	protected function listTests()
+	{
+		static $list;
+		if (is_null($list))
+		{
+			$list = array();
+			$temp = get_class_methods($this);
+			foreach ($temp as $method)
+			{
+				if (preg_match('/^test/', $method))
+				{
+					$list[] = $method;
+				}
+			}
+		}
+		return $list;
+	}
+
+	/**
+	 * protected function setUp
+	 *
+	 * override this in extending class if you want a setup method
+	 *
+	 * @return void
+	 */
+	protected function setUp()
+	{
+		return;
+	}
+
+	/**
+	 * protected function tearDown
+	 *
+	 * override this in extending class if you want a teardown method
+	 *
+	 * @return void
+	 */
+	protected function tearDown()
+	{
+		return;
+	}
+
+	//----------------------------------------------------------------
+	//             Run and Assert
+	//----------------------------------------------------------------
+
+	/**
+	 * final public function assert
+	 *
+	 * asserts whether something is true or false - will throw an exception to
+	 * stop the test on an error
+	 *
+	 * @param string $string data to check if it's true
+	 * @param string $message message on failure
+	 * @return void
+	 */
+	final protected function assert($item, $message = '')
+	{
+		// rip assertion code out - yuck
+		$info = debug_backtrace();
+		$file = file($info[0]['file']);
+		preg_match('/\$this->assert\((.*),/', $file[$info[0]['line'] - 1], $matches);
+		$code = $matches[1];
+		unset($info, $file, $matches);
+		$this->result->startAssert($code);
+		if ($item == TRUE)
+		{
+			$this->result->passAssert();
+			$this->result->endAssert();
+		}
+		else
+		{
+			$this->result->failAssert($message);
+			$this->result->endAssert();
+			throw new CCL_TestFailedException();
+		}
+		return;
+	}
+
+	/**
+	 * public function run
+	 *
+	 * run all tests in the test class or just one class
+	 *
+	 * @param string $test name of test to run
+	 * @return void
+	 */
+	final public function run($test = NULL)
+	{
+		$this->result->startTestUnit($this->name);
+		if (is_null($test))
+		{
+			foreach ($this->listTests() as $method)
+			{
+				$this->runTest($method);
+			}
+		}
+		else
+		{
+			$this->runTest($test);
+		}
+		$this->result->endTestUnit();
+		return;
+	}
+
+	/**
+	 * final protected function runTest
+	 *
+	 * run a single test from this class in isolate environment
+	 *
+	 * @param string $method name of a test method to run
+	 * @return void
+	 */
+	final protected function runTest($method)
+	{
+		if (!in_array($method, $this->listTests()))
+		{
+			throw new Exception('The method ' . $method .' is not in the class '
+				. get_class($this) . ' or is not a test method');
+			return;
+		}
+		set_error_handler(array($this, 'errorHandler'));
+		$this->message = NULL;
+		$this->setUp();
+		$this->result->startTest($method);
+		try
+		{
+			$this->$method();
+			$this->result->passTest();
+		}
+		catch (CCL_TestSkippedException $e)
+		{
+			$this->result->skipTest($e);
+		}
+		catch (CCL_TestIncompleteException $e)
+		{
+			$this->result->incompleteTest($e);
+		}
+		catch (CCL_TestFailedException $e)
+		{
+			$this->result->failTest(new CCL_TestFailedException($this->message));
+		}
+		catch (Exception $e)
+		{
+			$this->result->errorTest($e);
+		}
+		restore_error_handler();
+		$this->result->endTest();
+		$this->tearDown();
+		return;
+	}
+
+	/**
+	 * protected function errorHandler
+	 *
+	 * error handler -> basic exception
+	 *
+	 * @param int $errno error level number
+	 * @param string $errstr error message as string
+	 * @param string $errfile file where message occured
+	 * @param int $errline line where message occured
+	 * @return void
+	 */
+	public function errorHandler($errno, $errstr, $errfile, $errline)
+	{
+		$trace = debug_backtrace();
+		array_shift($trace);
+		throw new CCL_TestError($errstr, $errno, $errfile, $errline, $trace);
+	}
+}
+
+/**
+ * CCL_TestFailedException - empty exception class used to break out for
+ * failed test
+ */
+class CCL_TestFailedException extends Exception{}
+
+/**
+ * CCL_TestSkippedException - empty exception class used to break out for
+ * skipped test
+ */
+class CCL_TestSkippedException extends Exception{}
+
+/**
+ * CCL_TestIncompleteException - empty exception class used to break out for
+ * incomplete test
+ */
+class CCL_TestIncompleteException extends Exception{}
+?>
\ No newline at end of file