[php]代码库
<?php
// 简单的单元测试组件
/**
* 模型测试组件
*
* 自身并没有实现类自动加载机制,故测试时必须手动加载所有的测试类文件
*/
class UnitFramework {
/**
* 测试用例 运行类对象
* @var UnitRunner
*/
private static $_runner = null ;
/**
* 配置选项
* @var array
*/
private static $_config = null ;
/**
* 测试结果集
* @var array
*/
private static $_resultset = null ;
/**
* 初始化 单元测试资源
*/
static function init(array $config){
self::$_runner = new UnitRunner();
// 解析设置数组
$must = array('classes') ;
foreach ($must as $opt) {
if (!isset($config[$opt]))
throw new Exception(
sprintf("%s::%s(array) 参数数组设置错误,必设选项[%s]",__FILE__,__METHOD__,implode(',',$must))
);
}
self::$_config = $config ;
self::$_resultset = array();
}
/**
* 打印 测试报表
*/
static function report(){
header('Content-Type: text/html; charset=utf-8');
dump(self::$_resultset,'测试结果');
}
/**
* 运行 测试用例对象
*/
static function run(){
// 使用内部的错误处理机制
// function __kenxu_unit_errorHandle($errno, $errstr, $errfile, $errline){
// echo "$errstr <br/>" ;
// }
// 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();
}
<?php
CoreApp::import('/modules/default/models/Book.php',null,true);
class BookTest extends UnitTestCase {
/**
* @var Book
*/
private $_modBook = null ;
function setUp(){
/* Setup Routine */
$this->_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 ;
}
}
<?php
/**
* 图书 模型
*/
class Book {
function __construct(){
}
function fetchBooks(){
$books = SingleTableCRUD::find('books');
return $books ;
}
}