" ; // } // set_error_handler('__kenxu_unit_errorHandle') ; self::$_config['classes'] = array_unique(self::$_config['classes']) ; foreach (self::$_config['classes'] as $testcaseClass) { try { $testcase = new $testcaseClass() ; // 如果 加载的类中存在语法错误,此处也不会提示,需要手动捕捉 // dump(error_get_last()); } catch (Exception $e) { self::$_resultset[] = array( 'class' => $testcaseClass ,'message' => $e->getMessage()); continue ; } if ($testcase instanceof UnitTestCase){ self::$_resultset[] = self::$_runner->execute($testcase) ; } else { self::$_resultset[] = array( 'class' => $testcaseClass , 'message' => "{$testcaseClass} not a UnitTestCase instance" , ); } } // 恢复外部的错误处理机制 // restore_error_handler(); } } /** * 测试用例 运行类 */ class UnitRunner { /** * 测试用例对象 * @var UnitTestCase */ private $_testCase = null ; /** * 测试用例对象的反射 * @var ReflectionClass */ private $_reflect = null ; /** * 测试结果 * @var array */ private $_result = null ; public function execute(UnitTestCase $testCase){ $this->_testCase = $testCase ; $this->_reflect = new ReflectionClass($testCase); // 向 结果集中注入类的名称 $this->_result = array( 'class' => $this->_reflect->getName() , 'file' => $this->_reflect->getFileName() ); // 获取 测试用例运行前断言的执行次数 $start = UnitAssert::getCount(); // 测试用例运行时如果抛出异常 则终止这个测试用例,并且此时不包含测试信息 try { // 此处加上 基准测试的 起点 $this->_testCase->setUp(); // 执行测试方法 $testMethods = $this->_fetchTestMethods($this->_testCase); if (!empty($testMethods)){ $this->_result['failed'] = 0 ; $this->_result['methods'] = array() ; foreach ($testMethods as $testMethod) { $this->_evaluate($testMethod); $this->_result['failed'] += $this->_result['methods'][$testMethod]['failed'] ; } } $this->_testCase->tearDown(); // 此处加上 基准测试的 终结 // 获取 测试用例执行完成后断言的执行次数 $stop = UnitAssert::getCount(); $total = $stop - $start ; $this->_result['total'] = $total ; $this->_result['success'] = $total - $this->_result['failed'] ; } catch (Exception $ex){ $this->_result['bug'] = $ex->getMessage() ; $this->_result['bug_trace'] = $ex->getTraceAsString() ; } return $this->_result ; } /** * 执行方法 * @param string $testMethod */ private function _evaluate($testMethod){ // 获取 当前测试方法运行前断言的执行次数 $start = UnitAssert::getCount(); $this->_testCase->{$testMethod}(); // 获取 当前测试方法执行完成后断言的执行次数 $stop = UnitAssert::getCount(); $total = $stop - $start ; // 获取方法 对应的 断言失败时 的错误信息集合 $assertFaileds = UnitAssert::getAssertionFailedTrace($this->_result['class'],$testMethod) ; $this->_result['methods'][$testMethod] = array( 'total' => $total , 'success' => $total - count($assertFaileds) , 'failed' => count($assertFaileds) , 'asserts' => $assertFaileds ) ; } /** * 获取测试用例对象的Test方法集合: 方法以Test结尾,缺省忽略大小写 * * @param UnitTestCase $testCase * @param bool $ignoreCase 方法名称是否忽略大小写 * @return array */ private function _fetchTestMethods(UnitTestCase $testCase ,$ignoreCase = true){ // PHP5 开始 返回的方法名称 区分 大小写 $methods = get_class_methods($testCase); // array_map('strtolower', $methods); $testMethods = array(); $match = $ignoreCase ? '/Test$/i' : '/Test$/' ; foreach ($methods as $method) { if (preg_match($match,$method) && is_callable(array($testCase,$method))){ $testMethods[] = $method ; } } return $testMethods ; } } /** * 断言功能实现 */ class UnitAssert { /** * 使用断言的次数 * @var int */ private static $_count = 0 ; /** * 断言失败时 错误信息的存放位置,格式如下: * array( * ':class' => array( * ':method' => :msg * ) * ) * @var array */ protected static $_assertionFailedTrace = array() ; /** * 按测试用例的类/方法名称 获取 断言失败时 的错误信息集合 * * @param string $class * @param string $method * @return array | null */ static function getAssertionFailedTrace($class,$method=null){ if (!empty($class) && is_string($class) && isset(self::$_assertionFailedTrace[$class])){ $classTrace = self::$_assertionFailedTrace[$class] ; if (empty($method)) return $classTrace ; if (is_string($method) && isset($classTrace[$method])) return $classTrace[$method] ; } return null ; } /** * 用一组规则 测试值,每个规则的第一个元素是 回调函数,成功返回true,否则为false * * @param mixed $value 值 * @param array $rules 测试规则 * @param string $description 测试目的字符串 * * @return boolean */ static function assertThat($value,array $rules=null,$description=null){ self::$_count ++ ; $failed = null ; if ( assertValue($value,$rules,$failed,true)) return true ; try { throw new UnitAssertionFailed($failed['fr'],$failed['ft'],$description); } catch (UnitAssertionFailed $ex) { self::_fail($ex) ; } return false ; } static function assertNotNull($value,$description=null){ return self::assertThat($value,array(array('not_empty','值不能为空')),$description); } /** * 断言失败时抛出的异常信息捕获 * * @param UnitAssertionFailed $ex */ protected static function _fail(UnitAssertionFailed $ex){ $traces = $ex->getTrace(); // dump($traces,'错误$traces') ; // 1是测试用例的测试方法代码的信息 $testMethodTrace = $traces[1] ; $testcaseClass = $testMethodTrace['class'] ; $testMethod = $testMethodTrace['function'] ; // 验证初始化信息 if (!isset(self::$_assertionFailedTrace[$testcaseClass])) self::$_assertionFailedTrace[$testcaseClass] = array() ; if (!isset(self::$_assertionFailedTrace[$testcaseClass][$testMethod])) self::$_assertionFailedTrace[$testcaseClass][$testMethod] = array() ; // 0 是断言调用处代码的信息 $assertFailedTrace = $traces[0] ; // 调用代码字符串 $argsType = array_map('gettype',$assertFailedTrace['args']); $code = sprintf("{$assertFailedTrace['class']}{$assertFailedTrace['type']}{$assertFailedTrace['function']}(%s)", implode(',',$argsType)); // 操作 $assertTrace 信息 self::$_assertionFailedTrace[$testcaseClass][$testMethod][] = array( 'assertDestination' => $ex->getMessage() , // 断言目的 'code' => $code , // 调用代码 'line' => $assertFailedTrace['line'] , // 代码行 'failedRule' => $ex->getAssertRule() , // 失败规则 'failedMessage' => $ex->getAssertMessage() // 失败规则的验证信息 ) ; // // dump($testMethodTrace); // dump($assertFailedTrace); unset($ex) ; } /** * 返回当前执行的断言次数 * @return int */ static function getCount(){ return self::$_count ;} } /** * 测试用例 基类,测试用例测试方法定义规范: * 1. 以 Test 结尾 * 2. 测试方法不能声明参数 * 3. 测试方法不能声明成static */ class UnitTestCase { function setUp(){ /* Setup Routine */ } function tearDown(){ /* Tear Down Routine */ } } /** * 自定义的断言异常,由UnitAssert类使用 */ class UnitAssertionFailed extends RuntimeException { /** * 断言失败触发的规则 * @var string */ private $_failedRule = null ; /** * 断言失败的错误回馈信息 * @var string */ private $_failedMessage = null ; /** * 断言 异常构造器 * * @param string $failedRule 断言失败触发的规则 * @param string $failedMessage 断言失败的错误回馈信息 * @param string $description 断言测试的目的 */ function __construct($failedRule,$failedMessage=null,$description){ parent::__construct($description); $this->_failedRule = $failedRule ; $this->_failedMessage = $failedMessage ; } /** * 返回 断言失败触发的规则 * * @return string */ function getAssertRule(){ return $this->_failedRule ; } /** * 返回 断言失败的错误回馈信息 * * @return string */ function getAssertMessage(){ return $this->_failedMessage ; } } 测试例子如下: // 应用程序 登录入口 function default_application_index(){ // test case entry CoreApp::import('/include/unit.php',null,true); CoreApp::import('/modules/default/cases/BookTest.php',null,true); // 设置测试类 $config = array( 'classes' => array( 'BookTest'//,'BookTest1', ) ); UnitFramework::init($config); UnitFramework::run(); CoreApp::dumpLoadFiles(); UnitFramework::report(); } _modBook = new Book() ; } function fetchBooksTest(){ $books = $this->_modBook->fetchBooks() ; UnitAssert::assertThat( count($books),array(array('equal',3, '图书个数为3')) ,'测试图书元素' ); UnitAssert::assertThat( !$books,array(array('not_empty','值不能为空')) ,'测试图书元素' ); UnitAssert::assertNotNull( !$books,'图书表中数据为空' ); } function tearDown(){ /* Tear Down Routine */ $this->_modBook = null ; } }