PHPUnit + Laravel单元测试常用技能


Posted in PHP onNovember 06, 2019

1. 数据供给器

用来提供参数和结果,使用 @dataProvider 标注来指定使用哪个数据供给器方法。例如检测app升级数据是否符合预期,addProviderAppUpdateData()提供测试的参数和结果。testAppUpdateData()检测appUpdateData()返回的结果是否和给定的预期结果相等,即如果$appId='apple_3.3.2_117', $result=['status' => 0, 'isIOS' => false], 则$data中如果含有['status' => 0, 'isIOS' => false], 则断言成功。建议在数据提供器,逐个用字符串键名对其命名,这样在断言失败的时候将输出失败的名称,更容易定位问题

示例代码:

<?php
  namespace Tests\Unit;

  use App\Services\ClientService;
  use Tests\TestCase;

  class ClientServiceTest extends TestCase
  {
    /**
     * @dataProvider addProviderAppUpdateData
     *
     * @param $appId
     * @param $result
     */
    public function testAppUpdateData($appId, $result)
    {
      $data = (new ClientService($appId))->appUpdateData();

      $this->assertTrue(count(array_intersect_assoc($data, $result)) == count($result));
    }

    public function addProviderAppUpdateData()
    {
      return [
        'null'         => [null, ['status' => 0, 'isIOS' => false, 'latest_version' => 'V']],
        'error app id'     => ['sdas123123', ['status' => 0, 'isIOS' => false, 'latest_version' => 'V']],
        'android force update' => ['bx7_3.3.5_120', ['status' => 0, 'isIOS' => false]],
        'ios force update'   => ['apple_3.3.2_117', ['status' => 1, 'isIOS' => true]],
        'android soft update' => ['sanxing_3.3.2_117', ['status' => 2, 'isIOS' => false]],
        'ios soft update'   => ['apple_3.3.3_118', ['status' => 2, 'isIOS' => true]],
        'android normal'    => ['fhqd_3.3.6_121', ['status' => 1, 'isIOS' => false]],
        'ios normal'      => ['apple_3.3.5_120', ['status' => 1, 'isIOS' => true]],
        'h5'          => ['h5_3.3.3', ['status' => 1, 'isIOS' => false]]
      ];
    }
  }

断言成功结果:

PHPUnit + Laravel单元测试常用技能

2. 断言方法

常用有assertTrue(), assertFalse(), assertNull(), assertEquals(), assertThat()。

assertThat()自定义断言。常用的约束有isNull()、isTrue()、isFalse()、isInstanceOf();常用的组合约束logicalOr()、logicalAnd()。例如检测返回的结果是否是null或ApiApp类。

示例代码:

<?php
  namespace Tests\Unit;

  use App\Models\ApiApp;
  use App\Services\SystemConfigService;
  use Tests\TestCase;

  class SystemConfigServiceTest extends TestCase
  {
    /**
     * @dataProvider additionProviderGetLatestUpdateAppApi
     *
     * @param $appType
     */
    public function testGetLatestUpdateAppApi($appType)
    {
      $result = SystemConfigService::getLatestUpdateAppApi($appType);
      $this->assertThat($result, $this->logicalOr($this->isNull(), $this->isInstanceOf(ApiApp::class)));
    }

    public function additionProviderGetLatestUpdateAppApi()
    {
      return [
        'apple'  => [1],
        'android' => [2],
        'null'  => [9999]
      ];
    }
  }

断言成功结果:

PHPUnit + Laravel单元测试常用技能

3. 对异常进行测试

使用expectExceptionCode()对错误码进行检测,不建议对错误信息文案进行检测。例如检测设备被锁后是否抛出3026错误码。

示例代码:

<?php
  namespace Tests\Unit;

  use App\Services\UserSecurityService;
  use Illuminate\Support\Facades\Cache;
  use Tests\TestCase;

  class UserSecurityServiceTest extends TestCase
  {
    public static $userId = 4;

    /**
     * 设备锁检测
     * @throws \App\Exceptions\UserException
     */
    public function testDeviceCheckLock()
    {
      $this->expectExceptionCode(3026);
      Cache::put('device-login-error-account-', '1,2,3,4,5', 300);
      UserSecurityService::$request = null;
      UserSecurityService::$udid  = null;
      UserSecurityService::deviceCheck(self::$userId);
    }
  }

断言成功结果:

PHPUnit + Laravel单元测试常用技能

4. 测试私有属性和私有方法使用反射机制

如果只测试私有方法可使用ReflectionMethod()反射方法,使用setAccessible(true)设置方法可访问,并使用invokeArgs()或invoke()调用方法(invokeArgs将参数作为数组传递)。例如检测IP是否在白名单中。

示例代码:

被检测代码:

namespace App\Facades\Services;

  /**
   * Class WebDefender
   */
  class WebDefenderService extends BaseService
  {
     //ip白名单
    private $ipWhiteList = [
      '10.*', 
      '172.18.*', 
      '127.0.0.1' 
    ];

    /**
     * ip是否在白名单中
     *
     * @param string $ip
     *
     * @return bool
     */
    private function checkIPWhiteList($ip)
    {
      if (!$this->ipWhiteList || !is_array($this->ipWhiteList)) {
        return false;
      }
      foreach ($this->ipWhiteList as $item) {
        if (preg_match("/{$item}/", $ip)) {
          return true;
        }
      }

      return false;
    }
   }

检测方法:

<?php

  namespace Tests\Unit;

  use App\Facades\Services\WebDefenderService;
  use Tests\TestCase;

  class WebDefenderTest extends TestCase
  {
    /**
     * 测试IP白名单
     * @dataProvider additionProviderIp
     *
     * @param $ip
     * @param $result
     *
     * @throws \ReflectionException
     */
    public function testIPWhite($ip, $result)
    {
      $checkIPWhiteList = new \ReflectionMethod(WebDefenderService::class, 'checkIPWhiteList');
      $checkIPWhiteList->setAccessible(true);
      $this->assertEquals($result, $checkIPWhiteList->invokeArgs(new WebDefenderService(), [$ip]));
    }

    public function additionProviderIp()
    {
      return [
        '10 ip' => ['10.1.1.7', true],
        '172 ip' => ['172.18.2.5', true],
        '127 ip' => ['127.0.0.1', true],
        '192 ip' => ['192.168.0.1', false]
      ];
    }
   }

测试私有属性可使用ReflectionClass(), 获取属性用getProperty(), 设置属性的值用setValue(), 获取方法用getMethod(), 设置属性和方法可被访问使用setAccessible(true)。例如检测白名单路径。

示例代码:

被检测代码:

<?php
  namespace App\Facades\Services;

  use App\Exceptions\ExceptionCode;
  use App\Exceptions\UserException;
  use Illuminate\Support\Facades\Cache;

  /**
   * CC攻击防御器
   * Class WebDefender
   */
  class WebDefenderService extends BaseService
  {
    //路径白名单(正则)
    private $pathWhiteList = [
      //'^auth\/(.*)',
    ];

    private static $request = null;

     /**
     * 请求路径是否在白名单中
     *
     * @return bool
     */
    private function checkPathWhiteList()
    {
      $path = ltrim(self::$request->getPathInfo(), '/');
      if (!$path || !$this->pathWhiteList || !is_array($this->pathWhiteList)) {
        return false;
      }
      foreach ($this->pathWhiteList as $item) {
        if (preg_match("/$item/", $path)) {
          return true;
        }
      }

      return false;
    }
  }

检测方法:

<?php
  namespace Tests\Unit;

  use App\Facades\Services\WebDefenderService;
  use Illuminate\Http\Request;
  use Tests\TestCase;

  class WebDefenderTest extends TestCase
  {
     /**
     * 检测白名单路径
     * @dataProvider additionProviderPathWhiteList
     *
     * @param $pathProperty
     * @param $request
     * @param $result
     *
     * @throws \ReflectionException
     */
    public function testCheckPathWhiteList($pathProperty, $request, $result)
    {
      $reflectedClass = new \ReflectionClass('App\Facades\Services\WebDefenderService');

      $webDefenderService   = new WebDefenderService();
      $reflectedPathWhiteList = $reflectedClass->getProperty('pathWhiteList');
      $reflectedPathWhiteList->setAccessible(true);
      $reflectedPathWhiteList->setValue($webDefenderService, $pathProperty);

      $reflectedRequest = $reflectedClass->getProperty('request');
      $reflectedRequest->setAccessible(true);
      $reflectedRequest->setValue($request);

      $reflectedMethod = $reflectedClass->getMethod('checkPathWhiteList');
      $reflectedMethod->setAccessible(true);
      $this->assertEquals($result, $reflectedMethod->invoke($webDefenderService));
    }

    public function additionProviderPathWhiteList()
    {
      $allPath      = ['.*'];
      $checkPath     = ['^auth\/(.*)'];
      $authSendSmsRequest = new Request([], [], [], [], [], ['HTTP_HOST' => 'api.dev.com', 'REQUEST_URI' => '/auth/sendSms']);
      $indexRequest    = new Request([], [], [], [], [], ['HTTP_HOST' => 'api.dev.com', 'REQUEST_URI' => '/']);
      $noMatchRequest   = new Request([], [], [], [], [], ['HTTP_HOST' => 'api.dev.com', 'REQUEST_URI' => '/product/sendSms']);

      return [
        'index'        => [[], $authSendSmsRequest, false],
        'no request'     => [$allPath, $indexRequest, false],
        'all request'     => [$allPath, $authSendSmsRequest, true],
        'check auth sms'   => [$checkPath, $authSendSmsRequest, true],
        'check path no match' => [$checkPath, $noMatchRequest, false]
      ];
    }
  }

5. 代码覆盖率

使用--coverage-html导出的报告含有类与特质覆盖率、行覆盖率、函数与方法覆盖率。可查看当前单元测试覆盖的范围。例如输出WebDefenderTest的代码覆盖率到桌面(phpunit tests/unit/WebDefenderTest --coverage-html ~/Desktop/test)

PHPUnit + Laravel单元测试常用技能

6. 指定代码覆盖率报告要包含哪些文件

在配置文件(phpunit.xml)里设置whitelist中的processUncoveredFilesFromWhitelist=true, 设置目录用<directory>标签,设置文件用<file>标签。例如指定app/Services目录下的所有文件和app/Facades/Services/WebDefenderService.php在报告中。

示例代码:

<?xml version="1.0" encoding="UTF-8"?>
  <phpunit backupGlobals="false"
       backupStaticAttributes="false"
       bootstrap="tests/bootstrap.php"
       colors="true"
       convertErrorsToExceptions="true"
       convertNoticesToExceptions="true"
       convertWarningsToExceptions="true"
       processIsolation="false"
       stopOnFailure="false">
    <testsuites>
      <testsuite name="Unit">
        <directory suffix="Test.php">./tests/Unit</directory>
      </testsuite>

      <testsuite name="Feature">
        <directory suffix="Test.php">./tests/Feature</directory>
      </testsuite>
    </testsuites>
    <filter>
      <whitelist processUncoveredFilesFromWhitelist="true">
        <directory suffix=".php">./app/Services</directory>
        <file>./app/Facades/Services/WebDefenderService.php</file>
      </whitelist>
    </filter>
    <php>
      <server name="APP_ENV" value="local"/>
      <server name="BCRYPT_ROUNDS" value="4"/>
      <server name="CACHE_DRIVER" value="credis"/>
      <server name="MAIL_DRIVER" value="array"/>
      <server name="QUEUE_CONNECTION" value="sync"/>
      <server name="SESSION_DRIVER" value="array"/>
      <server name="APP_CONFIG_CACHE" value="bootstrap/cache/config.phpunit.php"/>
      <server name="APP_SERVICES_CACHE" value="bootstrap/cache/services.phpunit.php"/>
      <server name="APP_PACKAGES_CACHE" value="bootstrap/cache/packages.phpunit.php"/>
      <server name="APP_ROUTES_CACHE" value="bootstrap/cache/routes.phpunit.php"/>
      <server name="APP_EVENTS_CACHE" value="bootstrap/cache/events.phpunit.php"/>
    </php>
  </phpunit>

7. 参考文档

PHPUnit官方文档 https://phpunit.readthedocs.io/zh_CN/latest/index.html
反射类 https://www.php.net/manual/en/class.reflectionclass.php
反射方法 https://www.php.net/manual/en/class.reflectionmethod.php

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
PHP开启gzip页面压缩实例代码
Mar 11 PHP
PHP无限分类代码,支持数组格式化、直接输出菜单两种方式
May 18 PHP
php中获取指定IP的物理地址的代码(正则表达式)
Jun 23 PHP
PHP函数篇之掌握ord()与chr()函数应用
Dec 05 PHP
PHP连接MongoDB示例代码
Sep 06 PHP
Window下PHP三种运行方式图文详解
Jun 11 PHP
php生成图片验证码的实例讲解
Aug 03 PHP
PHP实现搜索地理位置及计算两点地理位置间距离的实例
Jan 08 PHP
PHP实现带重试功能的curl连接示例
Jul 28 PHP
php获取当前url地址的方法小结
Jan 10 PHP
php实现表单提交上传文件功能
May 28 PHP
JS(jQuery)实现聊天接收到消息语言自动提醒功能详解【提示“您有新的消息请注意查收”】
Apr 16 PHP
PHP用swoole+websocket和redis实现web一对一聊天
Nov 05 #PHP
基于thinkphp6.0的success、error实现方法
Nov 05 #PHP
php实现JWT(json web token)鉴权实例详解
Nov 05 #PHP
详解Laravel服务容器的绑定与解析
Nov 05 #PHP
php+laravel依赖注入知识点总结
Nov 04 #PHP
PHP保存Base64图片base64_decode的问题整理
Nov 04 #PHP
详解laravel passport OAuth2.0的4种模式
Nov 04 #PHP
You might like
php 操作excel文件的方法小结
2009/12/31 PHP
php中过滤非法字符的具体实现
2013/10/29 PHP
smarty循环嵌套用法示例分析
2016/07/19 PHP
php中yii框架实例用法
2020/12/22 PHP
JavaScript 实现??打印?理
2007/04/28 Javascript
23个Javascript弹出窗口特效整理
2011/02/25 Javascript
JQuery扩展插件Validate 3通过参数设置错误信息
2011/09/05 Javascript
读JavaScript DOM编程艺术笔记
2011/11/15 Javascript
JS的replace方法详细介绍
2012/11/09 Javascript
使用JavaScript动态设置样式实现代码(2)
2013/01/25 Javascript
js中parseInt函数浅谈
2013/07/31 Javascript
IE6浏览器中window.location.href无效的解决方法
2014/11/20 Javascript
javascript实现文字无缝滚动
2016/12/27 Javascript
canvas简单快速的实现知乎登录页背景效果
2017/05/08 Javascript
angular动态删除ng-repaeat添加的dom节点的方法
2017/07/20 Javascript
Javascript实现跨域后台设置拦截的方法详解
2017/08/04 Javascript
vue组件中使用iframe元素的示例代码
2017/12/13 Javascript
webpack热模块替换(HMR)/热更新的方法
2018/04/05 Javascript
vue路由事件beforeRouteLeave及组件内定时器的清除方法
2018/09/29 Javascript
使用vue-router在Vue页面之间传递数据的方法
2019/07/15 Javascript
DataFrame中的object转换成float的方法
2018/04/10 Python
Linux下python3.6.1环境配置教程
2018/09/26 Python
对python中的six.moves模块的下载函数urlretrieve详解
2018/12/19 Python
python中 * 的用法详解
2019/07/10 Python
使用CSS3制作版头动画效果
2020/12/24 HTML / CSS
美国学校校服,儿童和婴儿服装:Cookie’s Kids
2016/10/14 全球购物
美国领先的眼镜和太阳镜在线零售商:Glasses.com
2019/08/26 全球购物
Nike意大利官网:Nike.com IT
2020/01/19 全球购物
JSF面试题:Jsf中导航的标签是什么
2013/04/20 面试题
简历中求职的个人自我评价
2013/12/03 职场文书
网络宣传方案
2014/03/15 职场文书
三严三实对照检查材料
2014/09/22 职场文书
优秀大学生事迹材料
2014/12/24 职场文书
2015个人半年总结范文
2015/03/09 职场文书
小学生家长意见
2015/06/03 职场文书
Mysql分析设计表主键为何不用uuid
2022/03/31 MySQL