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 编程请选择正确的文本编辑软件
Dec 21 PHP
php Undefined index的问题
Jun 01 PHP
学习php笔记 字符串处理
Oct 19 PHP
php根据操作系统转换文件名大小写的方法
Feb 24 PHP
phpstorm编辑器乱码问题解决
Dec 01 PHP
smarty中常用方法实例总结
Aug 07 PHP
PHP下载远程图片并保存到本地方法总结
Jan 22 PHP
php的闭包(Closure)匿名函数初探
Feb 14 PHP
php array_walk_recursive 使用自定的函数处理数组中的每一个元素
Nov 16 PHP
CakePHP框架Model关联对象用法分析
Aug 04 PHP
python进程与线程小结实例分析
Nov 11 PHP
Yii2.0实现的批量更新及批量插入功能示例
Jan 29 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中使用cURL实现Get和Post请求的方法
2013/03/13 PHP
php循环创建目录示例分享(php创建多级目录)
2014/03/04 PHP
微信公众平台接口开发入门示例
2014/12/24 PHP
部署PHP时的4个配置修改说明
2015/10/19 PHP
PHP检测数据类型的几种方法(总结)
2017/03/04 PHP
Laravel构建即时应用的一种实现方法详解
2017/08/31 PHP
PHP实现Snowflake生成分布式唯一ID的方法示例
2020/08/30 PHP
JavaScript 空位补零实现代码
2010/02/26 Javascript
jQuery下的几个你可能没用过的功能
2010/08/29 Javascript
快速排序 php与javascript的不同之处
2011/02/22 Javascript
让innerText在firefox火狐和IE浏览器都能用的写法
2011/05/14 Javascript
javaScript 利用闭包模拟对象的私有属性
2011/12/29 Javascript
js自定义方法通过隐藏iframe实现文件下载
2013/02/21 Javascript
5个数组Array方法: indexOf、filter、forEach、map、reduce使用实例
2015/01/29 Javascript
详解Backbone.js框架中的模型Model与其集合collection
2016/05/05 Javascript
详解小程序不同页面之间通讯的解决方案
2018/11/23 Javascript
前端天气插件tpwidget使用方法详解
2019/06/24 Javascript
vue实现手机端省市区区域选择
2019/09/27 Javascript
浅析vue中的nextTick
2020/12/28 Vue.js
vue3+typeScript穿梭框的实现示例
2020/12/29 Vue.js
详解Python中的相对导入和绝对导入
2017/01/06 Python
python嵌套字典比较值与取值的实现示例
2017/11/03 Python
Python wxPython库使用wx.ListBox创建列表框示例
2018/09/03 Python
Python之pymysql的使用小结
2019/07/01 Python
flask框架蓝图和子域名配置详解
2020/01/25 Python
python Yaml、Json、Dict之间的转化
2020/10/19 Python
英国领先的亚洲旅游专家:Wendy Wu Tours
2018/01/21 全球购物
荣耀俄罗斯官网:HONOR俄罗斯
2020/10/31 全球购物
竞争性谈判邀请书
2014/02/06 职场文书
家长会主持词
2014/03/26 职场文书
团组织推优材料
2014/12/29 职场文书
今日说法观后感
2015/06/08 职场文书
干货:我将这样书写我的演讲稿!
2019/05/09 职场文书
php实例化对象的实例方法
2021/11/17 PHP
Java 超详细讲解IO操作字节流与字符流
2022/03/25 Java/Android
SQL Server携程核心系统无感迁移到MySQL实战
2022/06/01 SQL Server