最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • PHPUnit 单元测试使用 - 掘金

    正文概述 转载于:掘金(蜗牛有脚)   2021-11-12   433

    phpunit 是一个面向phper的测试框架。于python之pytest,golang之gotest 一样,是可以进行单元测试、代码覆盖率统计等功能的一款工具。phpunit使用手册

    PHPUnit 安装

    不推荐全局使用安装,phpunit 应该作为某一个项目的依赖进行管理。推荐使用composer来安装,composer的使用不在赘述。

    如要全局安装,进行如下操作:

    #查看composer的全局bin目录 将其加入系统 path 路径 方便后续直接运行安装的命令
    composer global config bin-dir --absolute
    #全局安装 phpunit
    composer global require --dev phpunit/phpunit
    #查看版本
    phpunit --version
    

    如要局部安装,只要在项目的 composer.json 文件中添加一行 "phpunit/phpunit": "^6.2"

    PHPUnit 单元测试使用 - 掘金 或者 命令行执行 composer require --dev phpunit/phpunit ^latest 然后执行composer install/update 安装成功后目录项目目录下会有 vendor/bin/phpunit 这个文件,因为我是局部安装,等下执行cmd命令时,就用这个命令文件。

    编写 PHPUnit 测试

    我的项目目录和编写的代码文件

    PHPUnit 单元测试使用 - 掘金 此处写的是一个校验类 Validate 及测试用例

    class Validator {
    
        // 对象本身
        private static $objValidate;
    
        // 错误信息
        private $_errMsg;
    
        /**
         * 这类似于一个校验注册表。
         * 校验规则和校验函数、错误信息 映射表。如果不存在校验规则,则不进行校验。
         * 扩展新的校验规则方法后,需要在映射表中增加相应项。
         */
        private static $_rule = [
            'inList'    => [
                'func' => 'checkInList',
                'msg'  => '%s is not in the array!',
            ],
            'int'       => [
                'func' => 'checkInt',
                'msg'  => '%s is not integer!',
            ],
            'float'     => [
                'func' => 'checkFloat',
                'msg'  => '%s is not float!',
            ],
            'string'    => [
                'func' => 'checkString',
                'msg'  => '%s is not string!',
            ],
            'bool'      => [
                'func' => 'checkBool',
                'msg'  => '%s is not bool!',
            ],
            'array'     => [
                'func' => 'checkArray',
                'msg'  => '%s is not array!',
            ],
            'object'    => [
                'func' => 'checkObject',
                'msg'  => '%s is not object!',
            ],
            'regex'     => [
                'func' => 'checkRegex',
                'msg'  => '%s is not match regex %s!',
            ],
            'max'       => [
                'func' => 'checkMax',
                'msg'  => '%s is not lt %d!',
            ],
            'min'       => [
                'func' => 'checkMin',
                'msg'  => '%s is not gt %d!',
            ],
            'maxLen'    => [
                'func' => 'checkMaxLen',
                'msg'  => '%s length is not lt %d!',
            ],
            'minLen'    => [
                'func' => 'checkMinLen',
                'msg'  => '%s length is not gt %d!',
            ],
            'arrayItem' => [
                'func' => 'checkArrayItem',
                'msg'  => '%s array element not match rule!',
            ],
            'func'      => [
                'func' => null,
                'msg'  => '%s not match customer function rule!',
            ],
        ];
    
        private function __construct()
        {
        }
    
        /**
         * 返回校验对象
         * @return Validator
         */
        public static function getInstance()
        {
            if (is_null(static::$objValidate)) {
                static::$objValidate = new self();
            }
            return static::$objValidate;
        }
    
        /**
         * 错误信息处理
         *
         * @param string $strField 验证字段
         * @param bool $mixRuleValue 验证字段对应值
         * @return string
         */
        private function _fmtErrMsg($strField, $mixRuleValue = true)
        {
            if (!$this->_errMsg) {
                $this->_errMsg = static::$_rule['func']['msg'];
            }
            if ($mixRuleValue === true) {
                return sprintf($this->_errMsg, $strField);
            }
            if (is_array($mixRuleValue)) {
                $mixRuleValue = json_encode($mixRuleValue);
            }
            return sprintf($this->_errMsg, $strField, $mixRuleValue);
        }
    
        /**
         * @param array $rules 校验规则 ['field' => $rule] 的格式 例如:
         * [
         *   'name'   => 'string|maxLen:20|minLen:1|',
         *   'age'    => 'int|inList:[4,5,6]|func',
         *   'sanwei' => ["key"=>"enable","rule"=>"bool"],        支持可选字段的校验,比如$arr['enable']字段如果存在,则必须为bool类型,如果不存在则不需要校验。
         *   'sanwei' => ["key"=>"user_id","rule"=>"int|min:0"],  可以递归地校验其中的某个特定元素,满足某个校验规则,比如$arr['user_id']必须是int且大于0。
         *   'sanwei' => ["key"=>"$$","rule"=>"int|min:0"],       可以递归地校验其中的每个元素,满足某个校验规则,比如array中的元素都必须是int且大于0。
         * ]
         * @param array $reqData 需要校验合法性的数
         * @param string $errMsg 如果检验不合法,需要返回详细信息
         * @param array $option 额外参数,例如 自定义校验函数
         * [
         *  'age' => $function,
         * ]
         * @return bool 返回值:如果检验合法,则返回true,如果不合法则返回false
         */
        public static function validate(array $arrRules, array $arrData, string &$errMsg, array $arrOption = []): bool
        {
            // 空规则不校验
            if (empty($arrRules)) {
                return true;
            }
            $objValidate  = static::getInstance();
            // 开始验证
            foreach ($arrRules as $strField => $mixRule) {
                $arrItemRules = [];
                // 存在array元素的验证
                if (is_array($mixRule)) {
                    $arrItemRules['arrayItem'] = $mixRule;
                } else {
                    $arrItemRules = $objValidate->_formatItemRule($mixRule);
                }
                if ($arrItemRules == false) {
                    continue;
                }
                // 逐条规则验证
                foreach ($arrItemRules as $strRuleName => $mixRuleValue) {
                    $mixValue = $arrData[$strField];
                    // 自定义函数
                    $funcName = $arrOption[$strField] ?? null;
                    $bolRet   = $objValidate->checkRule($mixValue, $strRuleName, $mixRuleValue, $funcName);
                    if (!$bolRet) {
                        $errMsg = $objValidate->_fmtErrMsg($strField, $mixRuleValue);
                        return false;
                    }
                }
            }
            return true;
        }
        
       
        /**
         * 格式化处理校验规则
         *
         * @param string $mixRule 校验规则
         * @return array|false
         */
        private function _formatItemRule($mixRule)
        {
            if (empty($mixRule)) {
                return false;
            }
            $arrRule = explode('|', $mixRule);
            // 过滤掉空项
            $arrRule = array_filter($arrRule, function ($item) {
                return !empty($item);
            });
            if (empty($arrRule)) {
                return false;
            }
            // 校验规则整理成数组
            $arrItemRules = [];
            foreach ($arrRule as $mixItemRule) {
                $arrItemRule = explode(':', $mixItemRule);
                $arrItemRules[$arrItemRule[0]] = isset($arrItemRule[1]) ? $arrItemRule[1] : true;
            }
            // 如果有自定义 func,其他规则则不进行校验
            if (array_key_exists('func', $arrItemRules)) {
                $arrItemRules         = [];
                $arrItemRules['func'] = true;
            }
            return $arrItemRules;
        }
    
        /**
         * 匹配规则
         *
         * @param mixed $mixValue 要校验的数据
         * @param string $strRule 列表中的规则
         * @param mixed $mixRuleValue 规则限定值
         * @param array $funcName 自定义函数
         * @return bool
         */
        private function checkRule($mixValue, $strRule, $mixRuleValue, $funcName = null): bool
        {
            $bolRet = true;
            try {
                // 运行注册校验的函数
                if (array_key_exists($strRule, static::$_rule)) {
                    // 存在 自定义函数,如果没有可执行函数,则不校验
                    if ($funcName) {
                        return $funcName($mixValue, $mixRuleValue);
                    }
                    $funcName = static::$_rule[$strRule]['func'] ?? null;
                    if (!$funcName) {
                        return true;
                    }
                    // 指定方法校验
                    $bolRet = $this->$funcName($mixValue, $mixRuleValue);
                    if (!$bolRet) {
                        $this->_errMsg = static::$_rule[$strRule]['msg'];
                    }
                }
            } catch (\Error $e) {
                $bolRet        = false;
                $this->_errMsg = '校验出错:' . $e->getMessage();
            }
            return $bolRet;
        }
    
        /**
         * 校验数据的值是否在某个列表内,比如某个数据只能取 'red', 'blue', 'green' 这三个值之一。
         * @return bool
         */
        public function checkInList($mixValue, $mixRuleList)
        {
            if (is_string($mixRuleList)) {
                $arrRuleList = json_decode($mixRuleList, true);
            } else {
                $arrRuleList = $mixRuleList;
            }
            if (is_array($arrRuleList) && in_array($mixValue, $arrRuleList)) {
                return true;
            }
            return false;
        }
    
        /**
         * 校验数据的类型是否是 integer
         * @return bool
         */
        public function checkInt($mixValue, $mixRuleValue = null)
        {
            return is_int($mixValue);
        }
    
        /**
         * 校验数据的类型是否是 float
         * @return bool
         */
        public function checkFloat($mixValue, $mixRuleValue = null)
        {
            return is_float($mixValue);
        }
    
        /**
         * 对于array类型,按校验规则校验
         * @return bool
         */
        public function checkArrayItem($arrValue, $arrRule)
        {
            if (!is_array($arrValue)) {
                return false;
            }
            // 规则
            $strKey       = $arrRule['key'];
            $arrItemRules = $this->_formatItemRule($arrRule['rule']);
            if ($arrItemRules == false) {
                return true;
            }
            return $this->checkItem($strKey, $arrValue, $arrItemRules);
        }
    
        /**
         * 对于array类型,递归进行校验
         *
         * @param string $strTargetKey 要校验的目标键
         * @param array $arrValue 要校验的数据值
         * @param array $arrItemRules 校验规则
         * @return bool
         */
        private function checkItem($strTargetKey, $arrValue, $arrItemRules)
        {
            foreach ($arrValue as $strKey => $mixValue) {
                if (is_array($mixValue)) {
                    return $this->checkItem($strTargetKey, $mixValue, $arrItemRules);
                }
                if ($strKey === $strTargetKey || $strTargetKey == '$$') {
                    // 逐条规则验证
                    foreach ($arrItemRules as $strRuleName => $strRuleValue) {
                        $bolRet = $this->checkRule($mixValue, $strRuleName, $strRuleValue);
                        if ($bolRet == false) {
                            return false;
                        }
                    }
                }
            }
            return true;
        }
    
    }
    

    相应的测试文件 test/ValidateTest.php 只写两个方法(只是示意)。PHPUnit\Framework\TestCase,命名类名+Test, 每个需要测试的方法命名都是 test+对应业务方法,如下所示。

    
    use PHPUnit\Framework\TestCase;
    
    class ValidateTest extends TestCase {
    
        /**
         * 测试Validator::validate()
         */
        public function testValidate()
        {
            $strErrMsg = '';
    
            // 校验规则
            $arrSchema = [
                'age'    => 'int|max:99|min:1',
                'name'   => 'string|require|maxLen:12|mixLen:1',
                'color'  => 'inList:["blue","red","green"]',
                'word'   => 'regex:\w+',
                'sanwei' => [
                    "key"  => "enable",
                    "rule" => "bool",
                ],
            ];
            // 测试输入数据
            $reqData = [
                'name'   => 'yyb',
                'age'    => 89,
                'color'  => 'blue',
                'word'   => 'hsoefheo',
                'sanwei' => [
                    'enable' => true,
                    'name'   => 'xxx',
                ],
            ];
            // 自定义函数
            $function = function ($value) {
                if ($value == 13) {
                    return true;
                }
                return false;
            };
            $option   = [
                'age' => $function,
            ];
            $mixRet   = Validator::validate($arrSchema, $reqData, $strErrMsg, $option);
            $this->assertFalse($mixRet);
    
            // 无自定义函数
            $mixRet   = Validator::validate($arrSchema, $reqData, $strErrMsg);
            $this->assertTrue($mixRet);
        }
    
        /**
         * 测试 checkInList
         */
        public function testCheckInList()
        {
            // 不在目标数组中
            $strTarget = 'yellow';
            $arrList   = ['red', 'blue', 'green'];
    
            $obj    = Validator::getInstance();
            $bolret = $obj->checkInList($strTarget, $arrList);
            $this->assertFalse($bolret);
    
            // 成功
            $strTarget = 'blue';
            $arrList   = ['red', 'blue', 'green'];
    
            $bolret = $obj->checkInList($strTarget, $arrList);
            $this->assertTrue($bolret);
        }
    }
    

    执行单元测试

    在项目目录下,命令行执行:./vendor/bin/phpunit test/ValidateTest.php

    MacintoshdeMacBook-Pro phpdir % ./vendor/bin/phpunit test/ValidateTest.php
    PHPUnit 6.5.14 by Sebastian Bergmann and contributors.
    
    ..............                                                    14 / 14 (100%)
    
    Time: 58 ms, Memory: 4.00MB
    
    OK (14 tests, 56 assertions)
    
    

    共14个单测,每一个方法有一个测试用例,共56个断言。

    代码覆盖率

    • phpunit的代码覆盖率功能利用了 Xdebug,在正确运行前需要安装xdebug,我用的是mac 电脑,mac电脑本身自带php 和xdebug扩展,在配置文件中启用配置即可。查看mac目录下文件夹ls /usr/lib/php/extensions,cd 对应的目录 cd no-debug-non-zts-20180731
    MacintoshdeMacBook-Pro no-debug-non-zts-20180731 % pwd
    /usr/lib/php/extensions/no-debug-non-zts-20180731
    MacintoshdeMacBook-Pro no-debug-non-zts-20180731 % ls
    opcache.so	xdebug.so
    
    

    然后配置php.ini 。vim /etc/php.ini ,如果没有php.ini,先执行 cp /etc/php.ini.default /etc/php.ini ,最后添加如下:

    [xdebug]
    zend_extension=/usr/lib/php/extensions/no-debug-non-zts-20180731/xdebug.so
    xdebug.remote_enable = 1
    xdebug.remote_connect_back=1
    xdebug.remote_port = 9123
    xdebug.scream=0
    xdebug.show_local_vars=1
    xdebug.idekey=PHPSTORM
    xdebug.remote_enable=On
    xdebug.remote_autostart=On
    

    执行 php -m 查看是否安装成功

    • 到这里终于可以执行代码覆盖率的测试了,其实没有必要追求100%的代码覆盖率,即使代码覆盖率达到了100%,也不代表测试质量就是很高,不代表涵盖了所有的情况。代码覆盖率只是一个参考值。

    可以生成的格式

    --coverage-clover <file>    Generate code coverage report in Clover XML format.
    --coverage-crap4j <file>    Generate code coverage report in Crap4J XML format.
    --coverage-html <dir>       Generate code coverage report in HTML format.
    --coverage-php <file>       Export PHP_CodeCoverage object to file.
    --coverage-text=<file>      Generate code coverage report in text format.
    --coverage-xml <dir>        Generate code coverage report in PHPUnit XML format.
    

    执行命令

    ./vendor/bin/phpunit \                    
    --bootstrap vendor/autoload.php \
    --coverage-html=reports/ \
    --whitelist src/ \
    test/ValidateTest.php
    
    

    其中 --whitelist dir 来设定需要覆盖率的业务代码路径。

    #查看覆盖率报告
    cd reports/ && php -S 0.0.0.0:8899
    

    PHPUnit 单元测试使用 - 掘金

    以上只是比较简单的使用phpunit ,具体更高级的用法还有很多,大家可以参照phpunit中文手册和文章开头的 phpunit使用手册。


    起源地 » PHPUnit 单元测试使用 - 掘金

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元