[PHP] PHP单元测试最佳方式 →→→→→进入此内容的聊天室

来自 , 2020-05-31, 写在 PHP, 查看 149 次.
URL http://www.code666.cn/view/53a1320c
  1. <?php
  2. // 简单的单元测试组件
  3.  
  4. /**
  5. * 模型测试组件
  6. *
  7. * 自身并没有实现类自动加载机制,故测试时必须手动加载所有的测试类文件
  8. */
  9. class UnitFramework {
  10.    
  11.     /**
  12.      * 测试用例 运行类对象
  13.      * @var UnitRunner
  14.      */
  15.     private static $_runner = null ;
  16.    
  17.     /**
  18.      * 配置选项
  19.      * @var array
  20.      */
  21.     private static $_config = null ;
  22.    
  23.     /**
  24.      * 测试结果集
  25.      * @var array
  26.      */
  27.     private static $_resultset = null ;
  28.    
  29.     /**
  30.      * 初始化 单元测试资源
  31.      */
  32.     static function init(array $config){
  33.         self::$_runner = new UnitRunner();
  34.         // 解析设置数组
  35.         $must = array('classes') ;
  36.         foreach ($must as $opt) {
  37.             if (!isset($config[$opt]))
  38.                 throw new Exception(
  39.                     sprintf("%s::%s(array) 参数数组设置错误,必设选项[%s]",__FILE__,__METHOD__,implode(',',$must))
  40.                 );
  41.         }
  42.         self::$_config = $config ;
  43.         self::$_resultset = array();
  44.     }
  45.    
  46.     /**
  47.      * 打印 测试报表
  48.      */
  49.     static function report(){
  50.         header('Content-Type: text/html; charset=utf-8');
  51.         dump(self::$_resultset,'测试结果');
  52.     }  
  53.    
  54.     /**
  55.      * 运行 测试用例对象
  56.      */
  57.     static function run(){
  58.         // 使用内部的错误处理机制
  59. //      function __kenxu_unit_errorHandle($errno, $errstr, $errfile, $errline){
  60. //          echo "$errstr <br/>" ;
  61. //      }
  62. //      set_error_handler('__kenxu_unit_errorHandle') ;
  63.        
  64.         self::$_config['classes'] = array_unique(self::$_config['classes']) ;
  65.         foreach (self::$_config['classes'] as $testcaseClass) {
  66.             try {
  67.                 $testcase = new $testcaseClass() ;
  68.                 // 如果 加载的类中存在语法错误,此处也不会提示,需要手动捕捉
  69. //              dump(error_get_last());
  70.             } catch (Exception $e) {
  71.                
  72.                 self::$_resultset[] = array(
  73.                     'class' => $testcaseClass ,'message' => $e->getMessage());
  74.                 continue ;
  75.             }
  76.            
  77.             if ($testcase instanceof UnitTestCase){
  78.                 self::$_resultset[] = self::$_runner->execute($testcase) ;
  79.             }
  80.             else {
  81.                 self::$_resultset[] = array(
  82.                     'class' => $testcaseClass ,
  83.                     'message' => "{$testcaseClass} not a UnitTestCase instance" ,
  84.                 );
  85.             }
  86.         }
  87.        
  88.         // 恢复外部的错误处理机制
  89. //      restore_error_handler();
  90.     }
  91. }
  92.  
  93. /**
  94. * 测试用例 运行类
  95. */
  96. class UnitRunner {
  97.    
  98.     /**
  99.      * 测试用例对象
  100.      * @var UnitTestCase
  101.      */
  102.     private $_testCase = null ;
  103.    
  104.     /**
  105.      * 测试用例对象的反射
  106.      * @var ReflectionClass
  107.      */
  108.     private $_reflect = null ;
  109.    
  110.     /**
  111.      * 测试结果
  112.      * @var array
  113.      */
  114.     private $_result = null ;
  115.    
  116.     public function execute(UnitTestCase $testCase){
  117.        
  118.         $this->_testCase = $testCase ;
  119.        
  120.         $this->_reflect = new ReflectionClass($testCase);
  121.        
  122.         // 向 结果集中注入类的名称
  123.         $this->_result = array(
  124.             'class' => $this->_reflect->getName() ,
  125.             'file' =>  $this->_reflect->getFileName()
  126.         );      
  127.        
  128.         // 获取 测试用例运行前断言的执行次数
  129.         $start = UnitAssert::getCount();
  130.        
  131.         // 测试用例运行时如果抛出异常 则终止这个测试用例,并且此时不包含测试信息      
  132.         try {
  133.            
  134.             // 此处加上 基准测试的 起点
  135.            
  136.             $this->_testCase->setUp();
  137.            
  138.             // 执行测试方法
  139.             $testMethods = $this->_fetchTestMethods($this->_testCase);
  140.            
  141.             if (!empty($testMethods)){
  142.                
  143.                 $this->_result['failed'] = 0 ;
  144.                
  145.                 $this->_result['methods'] = array() ;
  146.                
  147.                 foreach ($testMethods as $testMethod) {
  148.                     $this->_evaluate($testMethod);
  149.                    
  150.                     $this->_result['failed'] += $this->_result['methods'][$testMethod]['failed'] ;
  151.                 }
  152.                
  153.             }
  154.            
  155.             $this->_testCase->tearDown();
  156.            
  157.             // 此处加上 基准测试的 终结
  158.            
  159.             // 获取 测试用例执行完成后断言的执行次数
  160.             $stop = UnitAssert::getCount();
  161.            
  162.             $total = $stop - $start ;
  163.            
  164.             $this->_result['total'] = $total ;
  165.             $this->_result['success'] = $total - $this->_result['failed'] ;
  166.            
  167.            
  168.         } catch (Exception $ex){            
  169.             $this->_result['bug'] = $ex->getMessage() ;
  170.             $this->_result['bug_trace'] = $ex->getTraceAsString() ;          
  171.         }
  172.        
  173.         return $this->_result ;
  174.     }
  175.    
  176.     /**
  177.      * 执行方法
  178.      * @param string $testMethod
  179.      */
  180.     private function _evaluate($testMethod){
  181.         // 获取 当前测试方法运行前断言的执行次数
  182.         $start = UnitAssert::getCount();
  183.        
  184.         $this->_testCase->{$testMethod}();
  185.        
  186.         // 获取  当前测试方法执行完成后断言的执行次数
  187.         $stop = UnitAssert::getCount();
  188.        
  189.         $total = $stop - $start ;
  190.        
  191.         // 获取方法 对应的 断言失败时 的错误信息集合
  192.         $assertFaileds = UnitAssert::getAssertionFailedTrace($this->_result['class'],$testMethod) ;
  193.         $this->_result['methods'][$testMethod] = array(
  194.             'total' => $total ,
  195.             'success' => $total - count($assertFaileds) ,
  196.             'failed' => count($assertFaileds) ,
  197.             'asserts' => $assertFaileds
  198.         ) ;
  199.     }
  200.    
  201.     /**
  202.      * 获取测试用例对象的Test方法集合: 方法以Test结尾,缺省忽略大小写
  203.      *
  204.      * @param UnitTestCase $testCase
  205.      * @param bool $ignoreCase 方法名称是否忽略大小写
  206.      * @return array
  207.      */
  208.     private function _fetchTestMethods(UnitTestCase $testCase ,$ignoreCase = true){
  209.         // PHP5 开始 返回的方法名称 区分 大小写
  210.         $methods = get_class_methods($testCase);
  211.         // array_map('strtolower', $methods);
  212.         $testMethods = array();
  213.        
  214.         $match = $ignoreCase ? '/Test$/i' : '/Test$/' ;
  215.        
  216.         foreach ($methods as $method) {
  217.             if (preg_match($match,$method) && is_callable(array($testCase,$method))){
  218.                 $testMethods[] = $method ;
  219.             }
  220.         }
  221.         return $testMethods ;
  222.     }
  223.    
  224. }
  225.  
  226. /**
  227. * 断言功能实现
  228. */
  229. class UnitAssert {
  230.    
  231.     /**
  232.      * 使用断言的次数
  233.      * @var int
  234.      */
  235.     private static $_count = 0 ;
  236.    
  237.     /**
  238.      * 断言失败时 错误信息的存放位置,格式如下:
  239.      *  array(
  240.      *      ':class' => array(
  241.      *          ':method' => :msg
  242.      *      )
  243.      *  )
  244.      * @var array
  245.      */
  246.     protected static $_assertionFailedTrace = array() ;
  247.    
  248.     /**
  249.      * 按测试用例的类/方法名称 获取 断言失败时 的错误信息集合
  250.      *
  251.      * @param string $class
  252.      * @param string $method
  253.      * @return array | null
  254.      */
  255.     static function getAssertionFailedTrace($class,$method=null){
  256.         if (!empty($class) && is_string($class) && isset(self::$_assertionFailedTrace[$class])){
  257.             $classTrace = self::$_assertionFailedTrace[$class] ;
  258.             if (empty($method)) return $classTrace ;
  259.             if (is_string($method) && isset($classTrace[$method]))
  260.                 return $classTrace[$method] ;
  261.         }
  262.         return null ;
  263.     }
  264.    
  265.     /**
  266.      * 用一组规则 测试值,每个规则的第一个元素是 回调函数,成功返回true,否则为false
  267.      *
  268.      * @param mixed $value 值
  269.      * @param array $rules 测试规则
  270.      * @param string $description 测试目的字符串
  271.      *
  272.      * @return boolean
  273.      */
  274.     static function assertThat($value,array $rules=null,$description=null){
  275.         self::$_count ++ ;
  276.         $failed = null ;
  277.         if ( assertValue($value,$rules,$failed,true)) return true ;
  278.        
  279.         try {
  280.             throw new UnitAssertionFailed($failed['fr'],$failed['ft'],$description);
  281.         } catch (UnitAssertionFailed $ex) {
  282.             self::_fail($ex) ;
  283.         }
  284.         return false ;
  285.     }
  286.    
  287.     static function assertNotNull($value,$description=null){
  288.         return self::assertThat($value,array(array('not_empty','值不能为空')),$description);
  289.     }
  290.    
  291.     /**
  292.      * 断言失败时抛出的异常信息捕获
  293.      *
  294.      * @param UnitAssertionFailed $ex
  295.      */
  296.     protected static function _fail(UnitAssertionFailed $ex){
  297.         $traces = $ex->getTrace();
  298.        
  299. //      dump($traces,'错误$traces') ;
  300.        
  301.         // 1是测试用例的测试方法代码的信息
  302.         $testMethodTrace = $traces[1] ;
  303.        
  304.         $testcaseClass = $testMethodTrace['class'] ;
  305.         $testMethod = $testMethodTrace['function'] ;
  306.        
  307.         // 验证初始化信息
  308.         if (!isset(self::$_assertionFailedTrace[$testcaseClass]))
  309.             self::$_assertionFailedTrace[$testcaseClass] = array() ;
  310.         if (!isset(self::$_assertionFailedTrace[$testcaseClass][$testMethod]))
  311.             self::$_assertionFailedTrace[$testcaseClass][$testMethod] = array() ;
  312.        
  313.         // 0 是断言调用处代码的信息
  314.         $assertFailedTrace = $traces[0] ;  
  315.        
  316.         // 调用代码字符串
  317.         $argsType = array_map('gettype',$assertFailedTrace['args']);
  318.         $code = sprintf("{$assertFailedTrace['class']}{$assertFailedTrace['type']}{$assertFailedTrace['function']}(%s)",
  319.             implode(',',$argsType));        
  320.        
  321.         // 操作  $assertTrace 信息
  322.         self::$_assertionFailedTrace[$testcaseClass][$testMethod][] = array(
  323.             'assertDestination' => $ex->getMessage() , // 断言目的
  324.             'code' => $code , // 调用代码
  325.             'line' => $assertFailedTrace['line'] , // 代码行            
  326.             'failedRule' => $ex->getAssertRule() , // 失败规则
  327.             'failedMessage' => $ex->getAssertMessage() // 失败规则的验证信息
  328.         ) ;
  329. //      
  330. //      dump($testMethodTrace);
  331. //      dump($assertFailedTrace);
  332.        
  333.         unset($ex) ;
  334.     }
  335.    
  336.     /**
  337.      * 返回当前执行的断言次数
  338.      * @return int
  339.      */
  340.     static function getCount(){ return self::$_count ;}
  341. }
  342.  
  343. /**
  344. * 测试用例 基类,测试用例测试方法定义规范:
  345. *   1. 以 Test 结尾
  346. *   2. 测试方法不能声明参数
  347. *   3. 测试方法不能声明成static
  348. */
  349. class UnitTestCase {
  350.    
  351.     function setUp(){
  352.         /* Setup Routine */
  353.     }
  354.    
  355.     function tearDown(){
  356.         /* Tear Down Routine */
  357.     }
  358. }
  359.  
  360. /**
  361. * 自定义的断言异常,由UnitAssert类使用
  362. */
  363. class UnitAssertionFailed extends RuntimeException {
  364.     /**
  365.      * 断言失败触发的规则
  366.      * @var string
  367.      */
  368.     private $_failedRule = null ;
  369.    
  370.     /**
  371.      * 断言失败的错误回馈信息
  372.      * @var string
  373.      */
  374.     private $_failedMessage = null ;
  375.    
  376.     /**
  377.      * 断言 异常构造器
  378.      *
  379.      * @param string $failedRule 断言失败触发的规则
  380.      * @param string $failedMessage 断言失败的错误回馈信息
  381.      * @param string $description 断言测试的目的
  382.      */
  383.     function __construct($failedRule,$failedMessage=null,$description){
  384.         parent::__construct($description);
  385.         $this->_failedRule = $failedRule ;
  386.         $this->_failedMessage = $failedMessage ;
  387.     }
  388.    
  389.     /**
  390.      * 返回 断言失败触发的规则
  391.      *
  392.      * @return string
  393.      */
  394.     function getAssertRule(){
  395.         return $this->_failedRule ;
  396.     }
  397.    
  398.     /**
  399.      * 返回 断言失败的错误回馈信息
  400.      *
  401.      * @return string
  402.      */
  403.     function getAssertMessage(){
  404.         return $this->_failedMessage ;
  405.     }
  406. }
  407.  
  408. 测试例子如下:
  409. // 应用程序 登录入口
  410. function default_application_index(){
  411.     // test case entry
  412.     CoreApp::import('/include/unit.php',null,true);
  413.    
  414.     CoreApp::import('/modules/default/cases/BookTest.php',null,true);
  415.    
  416.     // 设置测试类
  417.     $config = array(
  418.         'classes' => array(
  419.             'BookTest'//,'BookTest1',
  420.         )
  421.     );
  422.    
  423.     UnitFramework::init($config);
  424.     UnitFramework::run();
  425.    
  426.     CoreApp::dumpLoadFiles();
  427.    
  428.     UnitFramework::report();
  429. }
  430.  
  431. <?php
  432.  
  433. CoreApp::import('/modules/default/models/Book.php',null,true);
  434.  
  435. class BookTest extends UnitTestCase {
  436.    
  437.     /**
  438.      * @var Book
  439.      */
  440.     private $_modBook = null ;
  441.    
  442.     function setUp(){
  443.         /* Setup Routine */
  444.         $this->_modBook = new Book() ;
  445.     }
  446.    
  447.    
  448.     function fetchBooksTest(){
  449.        
  450.         $books = $this->_modBook->fetchBooks() ;
  451.  
  452.         UnitAssert::assertThat( count($books),array(array('equal',3, '图书个数为3')) ,'测试图书元素' );
  453.         UnitAssert::assertThat( !$books,array(array('not_empty','值不能为空')) ,'测试图书元素' );
  454.         UnitAssert::assertNotNull( !$books,'图书表中数据为空' );
  455.     }
  456.        
  457.     function tearDown(){
  458.         /* Tear Down Routine */
  459.         $this->_modBook = null ;
  460.     }
  461. }
  462.  
  463. <?php
  464. /**
  465. * 图书 模型
  466. */
  467. class Book {
  468.    
  469.     function __construct(){
  470.        
  471.     }
  472.    
  473.     function fetchBooks(){
  474.         $books = SingleTableCRUD::find('books');
  475.         return $books ;
  476.     }
  477.    
  478. }

回复 "PHP单元测试最佳方式"

这儿你可以回复上面这条便签

captcha