在 Laravel 中基于 PHPUnit 进行代码测试:目录结构及测试编排文件 phpunit.xml 详解

目录结构

Laravel 框架基于 PHPUnit 提供了开箱即用的测试功能,对代码测试的支持非常有好:

Laravel PHPUnit 测试

以 Laravel 5.8 为例,在框架初始化过程中通过 Composer 安装了 PHPUnit 7(也可以手动升级到 PHPUnit 8),并且在项目根目下创建了 tests 目录用于存放测试文件:

Laravel测试目录结构

在该目录中包含的 UnitFeature 子目录下存放的测试用例分别用于单元测试和功能测试,二者都是基于 PHPUnit 实现,对应的测试用例的根类都是 PHPUnit\Framework\TestCase。而 Broswer 目录下存放的则是基于 Laravel Dusk(底层基于 Selenium)实现的浏览器测试文件(后面我们再介绍)。

此外,在 Laravel 项目根目录下还有一个与 PHPUnit 息息县关的 phpunit.xml 文件,该文件我们在上篇教程中简单介绍过,是 PHPUnit 的编排文件,用于编排和初始化 PHPUnit 的测试行为,PHPUnit 在执行测试之前会基于这个文件进行初始化设置,你可以将其看作是 PHPUnit 的配置文件,下面我们就从这个文件为入口,分析 Laravel 框架如何集成 PHPUnit 进行单元测试和功能测试。

通过 phpunit.xml 编排 PHPUnit

在上一篇 PHPUnit 入门教程中,我们已经介绍过,可以通过 XML 配置文件来编排 PHPUnit 的测试,对应的 XML 文件位于项目根目录下的 phpunit.xml,Laravel 框架已经为我们做好了如下初始化设置:

<?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>
        <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</directory>
        </whitelist>
    </filter>
    <php>
        <server name="APP_ENV" value="testing"/>
        <server name="BCRYPT_ROUNDS" value="4"/>
        <server name="CACHE_DRIVER" value="array"/>
        <server name="MAIL_DRIVER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>
    </php>
</phpunit>

该文件的第一行是 XML 文件的版本和编码描述信息,从第二行开始的 <phpunit> 元素则正式开始配置 PHPUnit 的核心功能,在该元素里面还嵌套定义了其它子元素,用于配置测试套件、过滤器、PHP 变量等其它信息。下面我们逐一来介绍这些元素和属性。

通用配置

首先来看 phpunit 元素上的属性,其中很多属性其实都可以在执行 phpunit 命令时通过命令行参数的形式传入,但是如果参数太多,且每次传入参数都是一样的,显然配置到 phpunit.xml 中更方便,也更加易于维护,PHPUnit 执行的命令行参数可以在这里查看,或者通过 phpunit --help 在命令行查看:

  • backupGlobals 属性对应命令行参数里的 --globals-backup,用于在每个测试中备份和恢复 PHP 超全局变量 $GLOBALS,这里设置为 false 表示不做相应的备份和恢复操作。
  • backupStaticAttributes 属性对应命令行参数里的 static-backup,用于在每个测试中备份和恢复静态属性,这里设置为 false 表示不做相应的备份和恢复操作。
  • bootstrap 属性对应命令行参数里面的 --bootstrap <file>,用于指定测试运行前需要引入的文件,这里配置为 vendor/autoload.php 表示会引入 Composer 自动加载和管理的所有依赖,以便在测试文件中使用。
  • colors 属性对应命令行参数里的 --colors=<flag>,用于指示在输出中是否用颜色进行标识。
  • processIsolation 属性对应命令行参数里的 --process-isolation,用于表示是否在隔离的 PHP 进程中执行测试。
  • stopOnFailure 属性对应命令行参数里的 --stop-on-failure,用于表示测试出错或失败时是否退出脚本执行,配置为 false 表示不退出。

接下来是一些不能通过命令行参数指定的属性:

  • convertErrorsToExceptions 属性用于定义是否将 PHP ERROR 级别错误转化为异常,默认会转化为异常的错误类型包括:E_WARNINGE_NOTICEE_USER_ERRORE_USER_WARNINGE_USER_NOTICEE_STRICTE_RECOVERABLE_ERRORE_DEPRECATEDE_USER_DEPRECATED,这里将该属性设置为 true 表示启用该功能。
  • convertNoticesToExceptions 属性用于定义是否将 PHP NOTICE 级别错误转化为异常,设置为 true 表示会将 E_NOTICEE_USER_NOTICEE_STRICT 三种级别错误转化为异常。
  • convertWarningsToExceptions 属性用于定义是否将 PHP WARNING 级别错误转化为异常,设置为 true 表示会将 E_WARNINGE_USER_WARNING 级别错误转化为异常。

当然,这里只包含了 PHPUnit 所支持的 phpunit 配置的一部分属性,更多配置请参考官方文档PHPUnit 命令行参数配置

测试套件

接下来,我们看 Laravel 框架为我们默认配置的测试套件,它们定义在子元素 <testsuites> 中,在 PHPUnit 中,我们可以像上篇教程那样配置单个 <testsuite>,也可以像 Laravel 框架这样通过 <testsuites> 配置多个 <testsuite>,这取决于项目的复杂度或者你的需求。

Laravel 框架默认通过 <testsuites> 定义了两个 <testsuite>,分别是用于单元测试的 Unit 和用于功能的测试的 Feature,在它们各自的测试套件中,通过 directory 子元素指定对应测试文件所在的目录,并通过 suffix 属性指定测试文件的文件名后缀,这样,当运行 phpunit 命令时,PHPUnit 会从指定目录下匹配指定后缀的测试文件进行测试。

在运行 phpunit 命令时,我们可以通过相应测试套件的名称匹配要执行的测试用例:

更多测试套件的配置选项可以参考官方文档

过滤器

另外,Laravel 框架还通过 <filter> 元素配置了过滤器,在该元素中我们可以通过 whitelist 子元素指定用于配置代码覆盖率报告分析所使用的白名单,代码覆盖率是代码测试中一个很重要的概念,我们的测试代码要尽可能覆盖到 100% 的业务代码,这样的测试才有意义,而 Laravel 应用代码都位于项目根目录下的 app 目录中,并且我们只测试 PHP 代码,所以在 <whitelist> 中通过 directory 子元素做了相应的配置。

这样,我们在运行 phpunit 时加上 --coverage-html . 参数,就可以在根目录下生成 HTML 格式的测试覆盖率报告文档了:

打开对应的 index.html 文档,就可以在浏览器中看到测试覆盖率报告页面:

Laravel PHPUnit 代码覆盖率

PHP 变量

最后,Laravel 框架还通过 <php> 元素为我们初始化了一些 PHPUnit 测试环境下的 PHP 常量:

<php>
    <server name="APP_ENV" value="testing"/>
    <server name="BCRYPT_ROUNDS" value="4"/>
    <server name="CACHE_DRIVER" value="array"/>
    <server name="MAIL_DRIVER" value="array"/>
    <server name="QUEUE_CONNECTION" value="sync"/>
    <server name="SESSION_DRIVER" value="array"/>
</php>

上述配置相当于以下 PHP 代码:

$_SERVER['APP_ENV'] = 'testing';
$_SERVER['BCRYPT_ROUNDS'] = '4';
$_SERVER['CACHE_DRIVER'] = 'array';
$_SERVER['MAIL_DRIVER'] = 'array';
$_SERVER['QUEUE_CONNECTION'] = 'sync';
$_SERVER['SESSION_DRIVER'] = 'array';

通过上述配置我们可以得知,在 Laravel 测试环境下,APP_ENV 的值是 testing,因此,我们可以在根目录下创建一个 .env.testing 文件作为测试环境下的环境配置文件,运行 phpunit 时实际执行的是控制台应用的 Kernel 来启动应用,这样,系统就会通过 .env.testing 读取环境配置。

缓存、邮件、会话驱动都是通过数组模拟,因而不会持久化到硬盘,此外队列驱动是 sync,表示会同步执行推送到队列的任务。

除此之外,还可以初始化 PHP 请求、常量、INI 设置、Cookie、超全局变量等信息,更多使用明细请参考官方文档

如果需要的话,你还可以在 phpunit.xml 在编排 PHPUnit 的日志监听器,由于 Laravel 框架默认没有提供这方面的配置,我们这里就不详细展开了,后面如果有用到会提及。

有了以上的概念和基础,下一篇我们将开始在 Laravel 框架中通过 PHPUnit 编写单元测试代码。

上一篇: 从基于 PHPUnit 编写单元测试开始

下一篇: 在 Laravel 中基于 PHPUnit 进行代码测试:单元测试篇