用户注册



邮箱:

密码:

用户登录


邮箱:

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

发表随想


还能输入: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、请勿发布广告信息或其他无关评论,否则将会删除评论并扣分,严重者给予封号处理。


扫码下载

加载中,请稍后...

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

加载中,请稍后...