欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

使用PHPUnit进行单元测试并生成代码覆盖率报告的方法

程序员文章站 2022-11-14 20:11:16
安装phpunit 使用 composer 安装 phpunit #查看composer的全局bin目录 将其加入系统 path 路径 方便后续直接运行安装的命...

安装phpunit

使用 composer 安装 phpunit

#查看composer的全局bin目录 将其加入系统 path 路径 方便后续直接运行安装的命令
composer global config bin-dir --absolute
#全局安装 phpunit
composer global require --dev phpunit/phpunit
#查看版本
phpunit --version

使用composer构建你的项目

我们将新建一个unit项目用于演示单元测试的基本工作流

创建项目结构

mkdir unit && cd unit && mkdir app tests reports
#结构如下
./
├── app #存放业务代码
├── reports #存放覆盖率报告
└── tests #存放单元测试

使用composer构建工程

#一路回车即可
composer init

#注册命名空间
vi composer.json
...
  "autoload": {
    "psr-4": {
      "app\\": "app/",
      "tests\\": "tests/"
    }
  }
...
#更新命名空间
composer dump-autoload

#安装 phpunit 组件库
composer require --dev phpunit/phpunit

到此我们就完成项目框架的构建,下面开始写业务和测试用例。

编写测试用例

创建文件app/example.php 这里我为节省排版就不写注释了

<?php
namespace app;

class example
{
  private $msg = "hello world";

  public function gettrue()
  {
    return true;
  }

  public function getfalse()
  {
    return false;
  }

  public function setmsg($value)
  {
    $this->msg = $value;
  }

  public function getmsg()
  {
    return $this->msg;
  }
}

创建相应的测试文件tests/exampletest.php

<?php
namespace tests;

use phpunit\framework\testcase as basetestcase;
use app\example;

class exampletest extends basetestcase
{
  public function testgettrue()
  {
    $example = new example();
    $result = $example->gettrue();
    $this->asserttrue($result);
  }
  
  public function testgetfalse()
  {
    $example = new example();
    $result = $example->getfalse();
    $this->assertfalse($result);
  }
  
  public function testgetmsg()
  {
    $example = new example();
    $result = $example->gettrue();
    // $result is world not big_cat
    $this->assertequals($result, "hello big_cat");
  }
}

执行单元测试

[root@localhost unit]# phpunit --bootstrap=vendor/autoload.php \
tests/

phpunit 6.5.14 by sebastian bergmann and contributors.

..f                                 3 / 3 (100%)

time: 61 ms, memory: 4.00mb

there was 1 failure:

1) tests\exampletest::testgetmsg
failed asserting that 'hello big_cat' matches expected true.

/opt/unit/tests/exampletest.php:27
/root/.config/composer/vendor/phpunit/phpunit/src/textui/command.php:195
/root/.config/composer/vendor/phpunit/phpunit/src/textui/command.php:148

failures!
tests: 3, assertions: 3, failures: 1.

这是一个非常简单的测试用例类,可以看到,执行了共3个测试用例,共3个断言,共1个失败,可以参照phpunit手册学习更多高级用法。

代码覆盖率

代码覆盖率反应的是测试用例测试对象行,函数/方法,类/特质的访问率是多少(php_codecoverage 尚不支持 opcode覆盖率、分支覆盖率 及 路径覆盖率),虽然有很多人认为过分看重覆盖率是不对的,但我们初入测试还是俗气的追求一下吧。

测试覆盖率的检测对象是我们的业务代码,phpunit通过检测我们编写的测试用例调用了哪些函数,哪些类,哪些方法,每一个控制流程是否都执行了一遍来计算覆盖率。

phpunit 的覆盖率依赖 xdebug,可以生成多种格式:

--coverage-clover <file>  generate code coverage report in clover xml format.
--coverage-crap4j <file>  generate code coverage report in crap4j xml format.
--coverage-html <dir>    generate code coverage report in html format.
--coverage-php <file>    export php_codecoverage object to file.
--coverage-text=<file>   generate code coverage report in text format.
--coverage-xml <dir>    generate code coverage report in phpunit xml format.

同时需要使用 --whitelist dir参数来设定我们需要检测覆盖率的业务代码路径,下面演示一下具体操作:

phpunit \
--bootstrap vendor/autoload.php \
--coverage-html=reports/ \
--whitelist app/ \
tests/
#查看覆盖率报告
cd reports/ && php -s 0.0.0.0:8899

使用PHPUnit进行单元测试并生成代码覆盖率报告的方法

使用PHPUnit进行单元测试并生成代码覆盖率报告的方法

这样我们就对业务代码app\example做单元测试,并且获得我们单元测试的代码覆盖率,现在自然是百分之百,因为我的测试用例已经访问了app\example的所有方法,没有遗漏的,开发中则能体现出你的测试时用力对业务代码测试度的完善性。

基境共享测试数据

可能你会发现我们在每个测试方法中都创建了app\example对象,在一些场景下是重复劳动,为什么不能只创建一次然后供其他测试方法访问呢?这需要理解 phpunit 执行测试用例的工作流程。

我们没有办法在不同的测试方法中通过某成员属性来传递数据,因为每个测试方法的执行都是新建一个测试类对象,然后调用相应的测试方法

即测试的执行模式并不是

testobj = new exampletest();
testobj->testmethod1();
testobj->testmethod2();

而是

testobj1 = new exampletest();
testobj1->testmethod1();

testobj2 = new exampletest();
testobj2->testmethod2();

所以testmethod1()修改的属性状态无法传递给 testmethod2()使用。

phpunit则为我们提供了全面的hook接口:

public static function setupbeforeclass()/teardownafterclass()//测试类构建/解构时调用
protected function setup()/teardown()//测试方法执行前/后调用
protected function assertpreconditions()/assertpostconditions()//断言前/后调用

当运行测试时,每个测试类大致就是如下的执行步骤

#测试类基境构建
setupbeforeclass

#new一个测试类对象
#第一个测试用例
setup
assertpreconditions
assertpostconditions
teardown

#new一个测试类对象
#第二个测试用例
setup
assertpreconditions
assertpostconditions
teardown
...

#测试类基境解构
teardownafterclass

所以我们可以在测试类构建时使用setupbeforeclass创建一个 app\example 对象作为测试类的静态成员变量(teardownafterclass主要用于一些资源清理,比如关闭文件,数据库连接),然后让每一个测试方法用例使用它:

<?php
namespace tests;

use app\example;
use phpunit\framework\testcase as basetestcase;

class exampletest extends basetestcase
{
  // 类静态属性
  private static $example;

  public static function setupbeforeclass()
  {
    self::$example = new example();
  }

  public function testgettrue()
  {
    // 类的静态属性更新
    self::$example->setmsg("hello big_cat");
    $result = self::$example->gettrue();
    $this->asserttrue($result);
  }

  public function testgetfalse()
  {
    $result = self::$example->getfalse();
    $this->assertfalse($result);
  }

  /**
   * 依赖 testgettrue 执行完毕
   * @depends testgettrue
   * @return [type] [description]
   */
  public function testgetmsg()
  {
    $result = self::$example->getmsg();
    $this->assertequals($result, "hello big_cat");
  }
}

或者使用@depends注解来声明二者的执行顺序,并使用传递参数的方式来满足需求。

public function testmethod1()
{
  $this->asserttrue(true);
  return "hello";
}

/**
 * @depends testmethod1
 */
public function testmethod2($str)
{
  $this->assertequals("hello", $str);
}
#执行模式大概如下
testobj1 = new test;
$str = testobj1->testmethod1();

testobj2 = new test;
testobj2->testmethod2($str);

理解测试执行的模式还是很有帮助的,其他高级特性请浏览官方文档

使用phpunit.xml编排测试套件

使用测试套件来管理测试,vi phpunit.xml

<?xml version="1.0" encoding="utf-8"?>
<phpunit backupglobals="false"
     backupstaticattributes="false"
     bootstrap="./vendor/autoload.php"
     colors="true"
     converterrorstoexceptions="true"
     convertnoticestoexceptions="true"
     convertwarningstoexceptions="true"
     processisolation="false"
     stoponfailure="false">
  <testsuites>
    <!--可以定义多个 suffix 用于指定待执行的测试类文件后缀-->
    <testsuite name="tests">
      <directory suffix="test.php">./test</directory>
    </testsuite>
  </testsuites>
  <filter>
    <whitelist processuncoveredfilesfromwhitelist="true">
      <!--可以定义多个 对./app下的业务代码做覆盖率统计-->
      <directory suffix=".php">./app</directory>
    </whitelist>
  </filter>
  <logging>
    <!--覆盖率报告生成类型和输出目录 lowupperbound低覆盖率阈值 highlowerbound高覆盖率阈值-->
    <log type="coverage-html" target="./reports" lowupperbound="35" highlowerbound="70"/>
  </logging>
</phpunit>

然后直接运phpunit行即可:

[root@localhost unit]# phpunit 
phpunit 6.5.14 by sebastian bergmann and contributors.

time: 81 ms, memory: 4.00mb

no tests executed!

generating code coverage report in html format ... done

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