用户注册



邮箱:

密码:

用户登录


邮箱:

密码:
记住登录一个月忘记密码?

发表随想


还能输入:200字
云代码 - php代码库

PHP单元测试最佳方式

2014-11-03 作者: php源代码大全举报

[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 ;
    }
     
}


网友评论    (发表评论)


发表评论:

评论须知:

  • 1、评论每次加2分,每天上限为30;
  • 2、请文明用语,共同创建干净的技术交流环境;
  • 3、若被发现提交非法信息,评论将会被删除,并且给予扣分处理,严重者给予封号处理;
  • 4、请勿发布广告信息或其他无关评论,否则将会删除评论并扣分,严重者给予封号处理。


扫码下载

加载中,请稍后...

输入口令后可复制整站源码

加载中,请稍后...