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 相关文章推荐
Windows下PHP5和Apache的安装与配置
Sep 05 PHP
搜索和替换文件或目录的一个好类--很实用
Oct 09 PHP
php完全过滤HTML,JS,CSS等标签
Jan 16 PHP
Mysql的Root密码忘记,查看或修改的解决方法(图文介绍)
Jun 14 PHP
php判断页面是否是微信打开的示例(微信打开网页)
Apr 25 PHP
php过滤HTML标签、属性等正则表达式汇总
Sep 22 PHP
常用PHP框架功能对照表
Oct 23 PHP
PHP内存缓存Memcached类实例
Dec 08 PHP
Zend Framework基本页面布局分析
Mar 19 PHP
PHP常用字符串函数小结(推荐)
Aug 05 PHP
PHP中如何使用Redis接管文件存储Session详解
Nov 28 PHP
php中加密解密DES类的简单使用方法示例
Mar 26 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中把美国时间转为北京时间的自定义函数分享
2014/07/28 PHP
ThinkPHP V2.2说明文档没有说明的那些事实例小结
2015/07/01 PHP
两款万能的php分页类
2015/11/12 PHP
PHP记录和读取JSON格式日志文件
2016/07/07 PHP
PHP简单预防sql注入的方法
2016/09/27 PHP
CI框架常用函数封装实例
2016/11/21 PHP
让textarea控件的滚动条怎是位与最下方
2007/04/20 Javascript
javascript 一段左右两边随屏滚动的代码
2009/06/18 Javascript
用Javascript实现锚点(Anchor)间平滑跳转
2009/09/08 Javascript
精选的10款用于构建良好易用性网站的jQuery插件
2011/01/23 Javascript
js 去掉空格实例 Trim() LTrim() RTrim()
2014/01/07 Javascript
在JavaScript中使用timer示例
2014/05/08 Javascript
JavaScript动态创建form表单并提交的实现方法
2015/12/10 Javascript
JS模拟bootstrap下拉菜单效果实例
2016/06/17 Javascript
VC调用javascript的几种方法(推荐)
2016/08/09 Javascript
Angular X中使用ngrx的方法详解(附源码)
2017/07/10 Javascript
浅谈angular4生命周期钩子
2017/09/05 Javascript
vue组件生命周期详解
2017/11/07 Javascript
element-ui 表格实现单元格可编辑的示例
2018/02/26 Javascript
获取layer.open弹出层的返回值方法
2018/08/20 Javascript
JavaScript中Dom操作实例详解
2019/07/08 Javascript
微信小程序手动添加收货地址省市区联动
2020/05/18 Javascript
使用JavaScript和MQTT开发物联网应用示例解析
2020/08/07 Javascript
Vue 事件的$event参数=事件的值案例
2021/01/29 Vue.js
[08:42]DOTA2每周TOP10 精彩击杀集锦vol.2
2014/06/25 DOTA
Python实现字典按key或者value进行排序操作示例【sorted】
2019/05/03 Python
python初步实现word2vec操作
2020/06/09 Python
Css3圆角边框制作代码
2015/11/18 HTML / CSS
美国著名的团购网站:Woot
2016/08/02 全球购物
澳大利亚礼品篮网站:Macarthur Baskets
2019/10/14 全球购物
人事科岗位职责范本
2014/03/02 职场文书
尊老爱幼演讲稿
2014/09/04 职场文书
群众路线查摆问题及整改措施
2014/10/10 职场文书
教师党的群众路线教育实践活动个人整改方案
2014/10/31 职场文书
2014年体育教学工作总结
2014/12/09 职场文书
Python实现天气查询软件
2021/06/07 Python