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 相关文章推荐
我常用的几个类
Oct 09 PHP
php的计数器程序
Oct 09 PHP
使用bcompiler对PHP文件进行加密的代码
Aug 29 PHP
php获取post中的json数据的实现方法
Jun 08 PHP
关于PHP中Object对象的笔记分享
Jun 28 PHP
thinkphp循环结构用法实例
Nov 24 PHP
PHP实现的简单日历类
Nov 29 PHP
php递归删除目录与文件的方法
Jan 30 PHP
php打印一个边长为N的实心和空心菱型的方法
Mar 02 PHP
php基于自定义函数记录log日志方法
Jul 21 PHP
php生成静态页面并实现预览功能
Jun 27 PHP
如何理解PHP核心特性命名空间
May 28 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 从指定数字中获取随机组合的简单方法(推荐)
2017/04/05 PHP
addRule在firefox下的兼容写法
2006/11/30 Javascript
javascript web对话框与弹出窗口
2009/02/22 Javascript
js获取class的所有元素
2013/03/28 Javascript
js使浏览器窗口最大化实现代码(适用于IE)
2013/08/07 Javascript
基于javascript实现窗口抖动效果
2016/01/03 Javascript
简单实现的JQuery文本框水印插件
2016/06/14 Javascript
jquery日历插件e-calendar升级版
2016/11/10 Javascript
简述vue状态管理模式之vuex
2018/08/29 Javascript
angular中如何绑定iframe中src的方法
2019/02/01 Javascript
Vue在 Nuxt.js 中重定向 404 页面的方法
2019/04/23 Javascript
Node.js 路由的实现方法
2019/06/05 Javascript
深入解析koa之中间件流程控制
2019/06/17 Javascript
使用easyui从servlet传递json数据到前端页面的两种方法
2019/09/05 Javascript
JavaScript实现省市联动效果
2019/11/22 Javascript
JavaScript ES6 Class类实现原理详解
2020/05/08 Javascript
vue3弹出层V3Popup实例详解
2021/01/04 Vue.js
python使用beautifulsoup从爱奇艺网抓取视频播放
2014/01/23 Python
python实现定时自动备份文件到其他主机的实例代码
2018/02/23 Python
python pandas.DataFrame选取、修改数据最好用.loc,.iloc,.ix实现
2018/06/11 Python
python3光学字符识别模块tesserocr与pytesseract的使用详解
2020/02/26 Python
python新手学习使用库
2020/06/11 Python
Flask缓存静态文件的具体方法
2020/08/02 Python
python 视频下载神器(you-get)的具体使用
2021/01/06 Python
省三好学生申请材料
2014/01/22 职场文书
九年级英语教学反思
2014/01/31 职场文书
职工代表大会主持词
2014/04/01 职场文书
经贸日语专业个人求职信范文
2014/04/29 职场文书
优秀应届毕业生自荐书
2014/06/29 职场文书
迎国庆演讲稿
2014/09/15 职场文书
教师师德师风自我剖析材料
2014/09/29 职场文书
欢迎词怎么写
2015/01/23 职场文书
2015秋季幼儿园开学通知
2015/07/16 职场文书
80后创业总结的9条职场用人思想,记得收藏
2019/08/13 职场文书
python3使用diagrams绘制架构图的步骤
2021/04/08 Python
CSS 鼠标选中文字后改变背景色的实现代码
2023/05/21 HTML / CSS