PHPUnit + Laravel单元测试常用技能

2022-10-09,,,,

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]]
      ];
    }
  }

断言成功结果:

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]
      ];
    }
  }

断言成功结果:

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);
    }
  }

断言成功结果:

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)

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
反射类
反射方法

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

《PHPUnit + Laravel单元测试常用技能.doc》

下载本文的Word格式文档,以方便收藏与打印。